diff --git a/backend_api_python/app/routes/strategy.py b/backend_api_python/app/routes/strategy.py index 9f0dab5..433c31b 100644 --- a/backend_api_python/app/routes/strategy.py +++ b/backend_api_python/app/routes/strategy.py @@ -74,6 +74,171 @@ def create_strategy(): return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500 +@strategy_bp.route('/strategies/batch-create', methods=['POST']) +def batch_create_strategies(): + """ + 批量创建策略(多币种) + + 请求体: + strategy_name: 策略基础名称 + symbols: 币种数组,如 ["Crypto:BTC/USDT", "Crypto:ETH/USDT"] + ... 其他策略配置 + """ + try: + payload = request.get_json() or {} + payload['user_id'] = int(payload.get('user_id') or 1) + payload['strategy_type'] = payload.get('strategy_type') or 'IndicatorStrategy' + + result = get_strategy_service().batch_create_strategies(payload) + + if result['success']: + return jsonify({ + 'code': 1, + 'msg': f"成功创建 {result['total_created']} 个策略", + 'data': result + }) + else: + return jsonify({ + 'code': 0, + 'msg': '批量创建失败', + 'data': result + }) + except Exception as e: + logger.error(f"batch_create_strategies failed: {str(e)}") + logger.error(traceback.format_exc()) + return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500 + + +@strategy_bp.route('/strategies/batch-start', methods=['POST']) +def batch_start_strategies(): + """ + 批量启动策略 + + 请求体: + strategy_ids: 策略ID数组 + 或 + strategy_group_id: 策略组ID + """ + try: + payload = request.get_json() or {} + strategy_ids = payload.get('strategy_ids') or [] + strategy_group_id = payload.get('strategy_group_id') + + # 如果提供了策略组ID,获取组内所有策略 + if strategy_group_id and not strategy_ids: + strategy_ids = get_strategy_service().get_strategies_by_group(strategy_group_id) + + if not strategy_ids: + return jsonify({'code': 0, 'msg': '请提供策略ID', 'data': None}), 400 + + # 先更新数据库状态 + result = get_strategy_service().batch_start_strategies(strategy_ids) + + # 然后启动执行器 + executor = get_trading_executor() + for sid in result.get('success_ids', []): + try: + executor.start_strategy(sid) + except Exception as e: + logger.error(f"Failed to start executor for strategy {sid}: {e}") + + return jsonify({ + 'code': 1 if result['success'] else 0, + 'msg': f"成功启动 {len(result.get('success_ids', []))} 个策略", + 'data': result + }) + except Exception as e: + logger.error(f"batch_start_strategies failed: {str(e)}") + logger.error(traceback.format_exc()) + return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500 + + +@strategy_bp.route('/strategies/batch-stop', methods=['POST']) +def batch_stop_strategies(): + """ + 批量停止策略 + + 请求体: + strategy_ids: 策略ID数组 + 或 + strategy_group_id: 策略组ID + """ + try: + payload = request.get_json() or {} + strategy_ids = payload.get('strategy_ids') or [] + strategy_group_id = payload.get('strategy_group_id') + + if strategy_group_id and not strategy_ids: + strategy_ids = get_strategy_service().get_strategies_by_group(strategy_group_id) + + if not strategy_ids: + return jsonify({'code': 0, 'msg': '请提供策略ID', 'data': None}), 400 + + # 先停止执行器 + executor = get_trading_executor() + for sid in strategy_ids: + try: + executor.stop_strategy(sid) + except Exception as e: + logger.error(f"Failed to stop executor for strategy {sid}: {e}") + + # 然后更新数据库状态 + result = get_strategy_service().batch_stop_strategies(strategy_ids) + + return jsonify({ + 'code': 1 if result['success'] else 0, + 'msg': f"成功停止 {len(result.get('success_ids', []))} 个策略", + 'data': result + }) + except Exception as e: + logger.error(f"batch_stop_strategies failed: {str(e)}") + logger.error(traceback.format_exc()) + return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500 + + +@strategy_bp.route('/strategies/batch-delete', methods=['DELETE']) +def batch_delete_strategies(): + """ + 批量删除策略 + + 请求体: + strategy_ids: 策略ID数组 + 或 + strategy_group_id: 策略组ID + """ + try: + payload = request.get_json() or {} + strategy_ids = payload.get('strategy_ids') or [] + strategy_group_id = payload.get('strategy_group_id') + + if strategy_group_id and not strategy_ids: + strategy_ids = get_strategy_service().get_strategies_by_group(strategy_group_id) + + if not strategy_ids: + return jsonify({'code': 0, 'msg': '请提供策略ID', 'data': None}), 400 + + # 先停止执行器 + executor = get_trading_executor() + for sid in strategy_ids: + try: + executor.stop_strategy(sid) + except Exception as e: + pass # 忽略停止错误 + + # 然后删除 + result = get_strategy_service().batch_delete_strategies(strategy_ids) + + return jsonify({ + 'code': 1 if result['success'] else 0, + 'msg': f"成功删除 {len(result.get('success_ids', []))} 个策略", + 'data': result + }) + except Exception as e: + logger.error(f"batch_delete_strategies failed: {str(e)}") + logger.error(traceback.format_exc()) + return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500 + + @strategy_bp.route('/strategies/update', methods=['PUT']) def update_strategy(): try: diff --git a/backend_api_python/app/services/strategy.py b/backend_api_python/app/services/strategy.py index 142d147..19eef83 100644 --- a/backend_api_python/app/services/strategy.py +++ b/backend_api_python/app/services/strategy.py @@ -2,6 +2,7 @@ import os import time import json import threading +import uuid from typing import List, Dict, Any, Optional from datetime import datetime @@ -534,6 +535,10 @@ class StrategyService: trading_config = payload.get('trading_config') or {} exchange_config = payload.get('exchange_config') or {} + # 策略组字段 + strategy_group_id = payload.get('strategy_group_id') or '' + group_base_name = payload.get('group_base_name') or '' + # Denormalized fields for quick list rendering symbol = (trading_config or {}).get('symbol') timeframe = (trading_config or {}).get('timeframe') @@ -549,8 +554,9 @@ class StrategyService: (strategy_name, strategy_type, market_category, execution_mode, notification_config, status, symbol, timeframe, initial_capital, leverage, market_type, exchange_config, indicator_config, trading_config, ai_model_config, decide_interval, + strategy_group_id, group_base_name, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( name, @@ -569,6 +575,8 @@ class StrategyService: self._dump_json_or_encrypt(trading_config, encrypt=False), self._dump_json_or_encrypt(payload.get('ai_model_config') or {}, encrypt=False), int(payload.get('decide_interval') or 300), + strategy_group_id, + group_base_name, now, now ) @@ -578,6 +586,150 @@ class StrategyService: cur.close() return int(new_id) + def batch_create_strategies(self, payload: Dict[str, Any]) -> Dict[str, Any]: + """ + 批量创建策略(多币种) + + Args: + payload: 包含 symbols(数组)和其他策略配置 + + Returns: + { + 'success': True/False, + 'strategy_group_id': '...', + 'created_ids': [1, 2, 3], + 'failed_symbols': [] + } + """ + symbols = payload.get('symbols') or [] + if not symbols or not isinstance(symbols, list): + raise ValueError("symbols array is required") + + base_name = (payload.get('strategy_name') or '').strip() + if not base_name: + raise ValueError("strategy_name is required") + + # 生成策略组ID + strategy_group_id = str(uuid.uuid4())[:8] + + created_ids = [] + failed_symbols = [] + + for symbol in symbols: + try: + # 为每个币种创建单独的策略 + single_payload = dict(payload) + + # 解析 symbol(可能是 "Market:SYMBOL" 格式) + if isinstance(symbol, str) and ':' in symbol: + parts = symbol.split(':', 1) + market_category = parts[0] + symbol_name = parts[1] + else: + market_category = payload.get('market_category') or 'Crypto' + symbol_name = symbol + + # 策略名称加币种后缀 + single_payload['strategy_name'] = f"{base_name}-{symbol_name}" + single_payload['strategy_group_id'] = strategy_group_id + single_payload['group_base_name'] = base_name + single_payload['market_category'] = market_category + + # 更新 trading_config 中的 symbol + trading_config = dict(single_payload.get('trading_config') or {}) + trading_config['symbol'] = symbol_name + single_payload['trading_config'] = trading_config + + new_id = self.create_strategy(single_payload) + created_ids.append(new_id) + + except Exception as e: + logger.error(f"Failed to create strategy for symbol {symbol}: {e}") + failed_symbols.append({'symbol': symbol, 'error': str(e)}) + + return { + 'success': len(created_ids) > 0, + 'strategy_group_id': strategy_group_id, + 'group_base_name': base_name, + 'created_ids': created_ids, + 'failed_symbols': failed_symbols, + 'total_created': len(created_ids), + 'total_failed': len(failed_symbols) + } + + def batch_start_strategies(self, strategy_ids: List[int]) -> Dict[str, Any]: + """批量启动策略""" + success_ids = [] + failed_ids = [] + + for sid in strategy_ids: + try: + self.update_strategy_status(sid, 'running') + success_ids.append(sid) + except Exception as e: + logger.error(f"Failed to start strategy {sid}: {e}") + failed_ids.append({'id': sid, 'error': str(e)}) + + return { + 'success': len(success_ids) > 0, + 'success_ids': success_ids, + 'failed_ids': failed_ids + } + + def batch_stop_strategies(self, strategy_ids: List[int]) -> Dict[str, Any]: + """批量停止策略""" + success_ids = [] + failed_ids = [] + + for sid in strategy_ids: + try: + self.update_strategy_status(sid, 'stopped') + success_ids.append(sid) + except Exception as e: + logger.error(f"Failed to stop strategy {sid}: {e}") + failed_ids.append({'id': sid, 'error': str(e)}) + + return { + 'success': len(success_ids) > 0, + 'success_ids': success_ids, + 'failed_ids': failed_ids + } + + def batch_delete_strategies(self, strategy_ids: List[int]) -> Dict[str, Any]: + """批量删除策略""" + success_ids = [] + failed_ids = [] + + for sid in strategy_ids: + try: + self.delete_strategy(sid) + success_ids.append(sid) + except Exception as e: + logger.error(f"Failed to delete strategy {sid}: {e}") + failed_ids.append({'id': sid, 'error': str(e)}) + + return { + 'success': len(success_ids) > 0, + 'success_ids': success_ids, + 'failed_ids': failed_ids + } + + def get_strategies_by_group(self, strategy_group_id: str) -> List[Dict[str, Any]]: + """获取策略组内的所有策略""" + try: + with get_db_connection() as db: + cur = db.cursor() + cur.execute( + "SELECT id FROM qd_strategies_trading WHERE strategy_group_id = ?", + (strategy_group_id,) + ) + rows = cur.fetchall() or [] + cur.close() + return [row['id'] for row in rows] + except Exception as e: + logger.error(f"get_strategies_by_group failed: {e}") + return [] + def update_strategy(self, strategy_id: int, payload: Dict[str, Any]) -> bool: now = int(time.time()) existing = self.get_strategy(strategy_id) diff --git a/backend_api_python/app/utils/db.py b/backend_api_python/app/utils/db.py index 02af32e..6f6a096 100644 --- a/backend_api_python/app/utils/db.py +++ b/backend_api_python/app/utils/db.py @@ -104,7 +104,9 @@ def _init_db_schema(conn): ensure_columns("qd_strategies_trading", { "market_category": "TEXT DEFAULT 'Crypto'", "execution_mode": "TEXT DEFAULT 'signal'", - "notification_config": "TEXT DEFAULT ''" + "notification_config": "TEXT DEFAULT ''", + "strategy_group_id": "TEXT DEFAULT ''", # 策略组ID,批量创建的策略共享同一个组ID + "group_base_name": "TEXT DEFAULT ''" # 策略组基础名称(用于显示) }) # 2. 持仓表 diff --git a/quantdinger_vue/src/api/strategy.js b/quantdinger_vue/src/api/strategy.js index 4064c45..6c2abae 100644 --- a/quantdinger_vue/src/api/strategy.js +++ b/quantdinger_vue/src/api/strategy.js @@ -5,10 +5,14 @@ const api = { strategies: '/api/strategies', strategyDetail: '/api/strategies/detail', createStrategy: '/api/strategies/create', + batchCreateStrategies: '/api/strategies/batch-create', updateStrategy: '/api/strategies/update', stopStrategy: '/api/strategies/stop', startStrategy: '/api/strategies/start', deleteStrategy: '/api/strategies/delete', + batchStartStrategies: '/api/strategies/batch-start', + batchStopStrategies: '/api/strategies/batch-stop', + batchDeleteStrategies: '/api/strategies/batch-delete', testConnection: '/api/strategies/test-connection', trades: '/api/strategies/trades', positions: '/api/strategies/positions', @@ -59,6 +63,20 @@ export function createStrategy (data) { }) } +/** + * 批量创建策略(多币种) + * @param {Object} data - 策略数据 + * @param {string} data.strategy_name - 策略基础名称 + * @param {Array} data.symbols - 币种数组,如 ["Crypto:BTC/USDT", "Crypto:ETH/USDT"] + */ +export function batchCreateStrategies (data) { + return request({ + url: api.batchCreateStrategies, + method: 'post', + data + }) +} + /** * 更新策略 * @param {number} id - 策略ID @@ -113,6 +131,48 @@ export function deleteStrategy (id) { }) } +/** + * 批量启动策略 + * @param {Object} data + * @param {Array} data.strategy_ids - 策略ID数组 + * @param {string} data.strategy_group_id - 策略组ID(可选,与strategy_ids二选一) + */ +export function batchStartStrategies (data) { + return request({ + url: api.batchStartStrategies, + method: 'post', + data + }) +} + +/** + * 批量停止策略 + * @param {Object} data + * @param {Array} data.strategy_ids - 策略ID数组 + * @param {string} data.strategy_group_id - 策略组ID(可选,与strategy_ids二选一) + */ +export function batchStopStrategies (data) { + return request({ + url: api.batchStopStrategies, + method: 'post', + data + }) +} + +/** + * 批量删除策略 + * @param {Object} data + * @param {Array} data.strategy_ids - 策略ID数组 + * @param {string} data.strategy_group_id - 策略组ID(可选,与strategy_ids二选一) + */ +export function batchDeleteStrategies (data) { + return request({ + url: api.batchDeleteStrategies, + method: 'delete', + data + }) +} + /** * 测试交易所连接 * @param {Object} exchangeConfig - 交易所配置 diff --git a/quantdinger_vue/src/locales/lang/en-US.js b/quantdinger_vue/src/locales/lang/en-US.js index 2c0bca1..f9681d4 100644 --- a/quantdinger_vue/src/locales/lang/en-US.js +++ b/quantdinger_vue/src/locales/lang/en-US.js @@ -1170,6 +1170,10 @@ const locale = { 'trading-assistant.stopStrategy': 'Stop Strategy', 'trading-assistant.editStrategy': 'Edit Strategy', 'trading-assistant.deleteStrategy': 'Delete Strategy', + 'trading-assistant.startAll': 'Start All', + 'trading-assistant.stopAll': 'Stop All', + 'trading-assistant.deleteAll': 'Delete All', + 'trading-assistant.symbolCount': 'symbols', 'trading-assistant.status.running': 'Running', 'trading-assistant.status.stopped': 'Stopped', 'trading-assistant.status.error': 'Error', @@ -1202,7 +1206,9 @@ const locale = { 'trading-assistant.form.testConnection': 'Test Connection', 'trading-assistant.form.strategyName': 'Strategy Name', 'trading-assistant.form.symbol': 'Trading Pair', + 'trading-assistant.form.symbols': 'Trading Pairs (Multi-select)', 'trading-assistant.form.symbolHint': 'Symbol format depends on the selected market', + 'trading-assistant.form.symbolsHint': 'Select multiple pairs to create strategies for each', 'trading-assistant.form.symbolHintCrypto': 'Crypto: use trading pairs like BTC/USDT', 'trading-assistant.form.symbolHintGeneral': 'Enter the symbol for the selected market (e.g. 600519, AAPL, EURUSD).', 'trading-assistant.form.initialCapital': 'Initial Capital', @@ -1312,6 +1318,14 @@ const locale = { 'trading-assistant.messages.loadIndicatorsFailed': 'Failed to load indicator list, please try again later', 'trading-assistant.messages.spotLimitations': 'Spot trading has been automatically set to long only with 1x leverage', 'trading-assistant.messages.autoFillApiConfig': 'Auto-filled API configuration for this exchange from history', + 'trading-assistant.messages.batchCreateSuccess': 'Successfully created {count} strategies', + 'trading-assistant.messages.batchStartSuccess': 'Successfully started {count} strategies', + 'trading-assistant.messages.batchStartFailed': 'Failed to start strategies', + 'trading-assistant.messages.batchStopSuccess': 'Successfully stopped {count} strategies', + 'trading-assistant.messages.batchStopFailed': 'Failed to stop strategies', + 'trading-assistant.messages.batchDeleteSuccess': 'Successfully deleted {count} strategies', + 'trading-assistant.messages.batchDeleteFailed': 'Failed to delete strategies', + 'trading-assistant.messages.batchDeleteConfirm': 'Are you sure you want to delete {count} strategies in group "{name}"? This action cannot be undone.', 'trading-assistant.placeholders.selectIndicator': 'Please select an indicator', 'trading-assistant.placeholders.selectExchange': 'Please select an exchange', 'trading-assistant.placeholders.selectMarketCategory': 'Please select market category', @@ -1320,6 +1334,7 @@ const locale = { 'trading-assistant.placeholders.inputPassphrase': 'Please enter Passphrase', 'trading-assistant.placeholders.inputStrategyName': 'Please enter strategy name', 'trading-assistant.placeholders.selectSymbol': 'Please select trading pair', + 'trading-assistant.placeholders.selectSymbols': 'Please select trading pairs (multi-select)', 'trading-assistant.placeholders.inputSymbol': 'Please enter symbol', 'trading-assistant.placeholders.selectTimeframe': 'Please select timeframe', 'trading-assistant.placeholders.selectKlinePeriod': 'Please select K-line period', @@ -1341,6 +1356,7 @@ const locale = { 'trading-assistant.validation.testConnectionFailed': 'Connection test failed, please check the configuration and test again', 'trading-assistant.validation.strategyNameRequired': 'Please enter strategy name', 'trading-assistant.validation.symbolRequired': 'Please select trading pair', + 'trading-assistant.validation.symbolsRequired': 'Please select at least one trading pair', 'trading-assistant.validation.initialCapitalRequired': 'Please enter initial capital', 'trading-assistant.validation.leverageRequired': 'Please enter leverage', 'trading-assistant.validation.emailInvalid': 'Invalid email address', diff --git a/quantdinger_vue/src/locales/lang/zh-CN.js b/quantdinger_vue/src/locales/lang/zh-CN.js index 1457f2b..d8944d1 100644 --- a/quantdinger_vue/src/locales/lang/zh-CN.js +++ b/quantdinger_vue/src/locales/lang/zh-CN.js @@ -1079,6 +1079,10 @@ const locale = { 'trading-assistant.stopStrategy': '停止策略', 'trading-assistant.editStrategy': '编辑策略', 'trading-assistant.deleteStrategy': '删除策略', +'trading-assistant.startAll': '全部启动', +'trading-assistant.stopAll': '全部停止', +'trading-assistant.deleteAll': '全部删除', +'trading-assistant.symbolCount': '个币种', 'trading-assistant.status.running': '运行中', 'trading-assistant.status.stopped': '已停止', 'trading-assistant.status.error': '错误', @@ -1091,6 +1095,8 @@ const locale = { 'trading-assistant.form.step1': '选择指标', 'trading-assistant.form.step2': '交易所配置', 'trading-assistant.form.step3': '策略参数', +'trading-assistant.form.step2Params': '策略参数', +'trading-assistant.form.step3Signal': '信号通知', 'trading-assistant.form.indicator': '选择指标', 'trading-assistant.form.indicatorHint': '只能选择您已购买或创建的技术指标', 'trading-assistant.form.qdtCostHints': '使用策略将消耗QDT,请确保账户有足够的QDT余额', @@ -1103,7 +1109,10 @@ const locale = { 'trading-assistant.form.testConnection': '测试连接', 'trading-assistant.form.strategyName': '策略名称', 'trading-assistant.form.symbol': '交易对', +'trading-assistant.form.symbols': '交易对(多选)', 'trading-assistant.form.symbolHint': '目前仅支持加密货币交易对', +'trading-assistant.form.symbolHintCrypto': '加密货币:使用BTC/USDT等交易对格式', +'trading-assistant.form.symbolsHint': '选择多个交易对,将自动创建多个策略', 'trading-assistant.form.initialCapital': '投入金额', 'trading-assistant.form.marketType': '市场类型', 'trading-assistant.form.marketTypeFutures': '合约', @@ -1135,6 +1144,23 @@ const locale = { 'trading-assistant.form.enableAiFilterHint': '启用后,指标信号将经过AI智能过滤,提高交易质量', 'trading-assistant.form.aiFilterPrompt': '自定义提示词', 'trading-assistant.form.aiFilterPromptHint': '为AI过滤提供自定义指令,留空则使用系统默认', +'trading-assistant.form.executionMode': '执行模式', +'trading-assistant.form.executionModeSignal': '仅信号通知', +'trading-assistant.form.executionModeLive': '实盘自动交易', +'trading-assistant.form.liveTradingCryptoOnlyHint': '实盘交易功能仅支持加密货币市场', +'trading-assistant.form.notifyChannels': '通知渠道', +'trading-assistant.form.notifyChannelsHint': '选择信号触发时的通知方式', +'trading-assistant.form.notifyEmail': '邮箱地址', +'trading-assistant.form.notifyPhone': '手机号码', +'trading-assistant.form.notifyTelegram': 'Telegram ID', +'trading-assistant.form.notifyDiscord': 'Discord Webhook', +'trading-assistant.form.notifyWebhook': 'Webhook 地址', +'trading-assistant.form.liveTradingConfigTitle': '实盘交易配置', +'trading-assistant.form.liveTradingConfigHint': '请填写交易所API信息,实盘交易将使用您的API进行真实交易', +'trading-assistant.form.savedCredential': '已保存的凭证', +'trading-assistant.form.savedCredentialHint': '选择已保存的交易所凭证,自动填充API配置', +'trading-assistant.form.saveCredential': '保存此凭证以便下次使用', +'trading-assistant.form.credentialName': '凭证名称', 'trading-assistant.validation.strategyTypeRequired': '请选择策略类型', 'trading-assistant.form.advancedSettings': '高级设置', 'trading-assistant.form.orderMode': '下单模式', @@ -1183,6 +1209,20 @@ const locale = { 'trading-assistant.messages.loadIndicatorsFailed': '加载指标列表失败,请稍后重试', 'trading-assistant.messages.spotLimitations': '现货交易已自动设置为仅做多、1倍杠杆', 'trading-assistant.messages.autoFillApiConfig': '已自动填充该交易所的历史API配置', +'trading-assistant.messages.batchCreateSuccess': '成功创建 {count} 个策略', +'trading-assistant.messages.batchStartSuccess': '成功启动 {count} 个策略', +'trading-assistant.messages.batchStartFailed': '批量启动策略失败', +'trading-assistant.messages.batchStopSuccess': '成功停止 {count} 个策略', +'trading-assistant.messages.batchStopFailed': '批量停止策略失败', +'trading-assistant.messages.batchDeleteSuccess': '成功删除 {count} 个策略', +'trading-assistant.messages.batchDeleteFailed': '批量删除策略失败', +'trading-assistant.messages.batchDeleteConfirm': '确定要删除策略组"{name}"下的 {count} 个策略吗?此操作不可恢复。', +'trading-assistant.notify.browser': '浏览器通知', +'trading-assistant.notify.email': '邮箱', +'trading-assistant.notify.phone': '短信', +'trading-assistant.notify.telegram': 'Telegram', +'trading-assistant.notify.discord': 'Discord', +'trading-assistant.notify.webhook': 'Webhook', 'trading-assistant.placeholders.selectIndicator': '请选择指标', 'trading-assistant.placeholders.selectExchange': '请选择交易所', 'trading-assistant.placeholders.inputApiKey': '请输入API Key', @@ -1190,9 +1230,17 @@ const locale = { 'trading-assistant.placeholders.inputPassphrase': '请输入Passphrase', 'trading-assistant.placeholders.inputStrategyName': '请输入策略名称', 'trading-assistant.placeholders.selectSymbol': '请选择交易对', +'trading-assistant.placeholders.selectSymbols': '请选择交易对(支持多选)', 'trading-assistant.placeholders.selectTimeframe': '请选择时间周期', 'trading-assistant.placeholders.selectKlinePeriod': '请选择K线周期', 'trading-assistant.placeholders.inputAiFilterPrompt': '请输入自定义提示词(可选)', +'trading-assistant.placeholders.selectSavedCredential': '请选择已保存的凭证', +'trading-assistant.placeholders.inputEmail': '请输入邮箱地址', +'trading-assistant.placeholders.inputPhone': '请输入手机号码', +'trading-assistant.placeholders.inputTelegram': '请输入Telegram ID或Chat ID', +'trading-assistant.placeholders.inputDiscord': '请输入Discord Webhook地址', +'trading-assistant.placeholders.inputWebhook': '请输入Webhook地址', +'trading-assistant.placeholders.inputCredentialName': '请输入凭证名称(如:我的OKX账户)', 'trading-assistant.validation.indicatorRequired': '请选择指标', 'trading-assistant.validation.exchangeRequired': '请选择交易所', 'trading-assistant.validation.apiKeyRequired': '请输入API Key', @@ -1203,6 +1251,9 @@ const locale = { 'trading-assistant.validation.testConnectionFailed': '连接测试失败,请检查配置后重新测试', 'trading-assistant.validation.strategyNameRequired': '请输入策略名称', 'trading-assistant.validation.symbolRequired': '请选择交易对', +'trading-assistant.validation.symbolsRequired': '请至少选择一个交易对', +'trading-assistant.validation.emailInvalid': '请输入有效的邮箱地址', +'trading-assistant.validation.notifyChannelRequired': '请至少选择一个通知渠道', 'trading-assistant.validation.initialCapitalRequired': '请输入投入金额', 'trading-assistant.validation.leverageRequired': '请输入杠杆倍数', 'trading-assistant.table.time': '时间', diff --git a/quantdinger_vue/src/locales/lang/zh-TW.js b/quantdinger_vue/src/locales/lang/zh-TW.js index 3cb4aab..9155b8d 100644 --- a/quantdinger_vue/src/locales/lang/zh-TW.js +++ b/quantdinger_vue/src/locales/lang/zh-TW.js @@ -1079,6 +1079,10 @@ const locale = { 'trading-assistant.stopStrategy': '停止策略', 'trading-assistant.editStrategy': '編輯策略', 'trading-assistant.deleteStrategy': '刪除策略', + 'trading-assistant.startAll': '全部啟動', + 'trading-assistant.stopAll': '全部停止', + 'trading-assistant.deleteAll': '全部刪除', + 'trading-assistant.symbolCount': '個幣種', 'trading-assistant.status.running': '運行中', 'trading-assistant.status.stopped': '已停止', 'trading-assistant.status.error': '錯誤', @@ -1091,6 +1095,8 @@ const locale = { 'trading-assistant.form.step1': '選擇指標', 'trading-assistant.form.step2': '交易所配置', 'trading-assistant.form.step3': '策略參數', + 'trading-assistant.form.step2Params': '策略參數', + 'trading-assistant.form.step3Signal': '信號通知', 'trading-assistant.form.indicator': '選擇指標', 'trading-assistant.form.indicatorHint': '只能選擇您已購買或創建的技術指標', 'trading-assistant.form.qdtCostHints': '使用策略將消耗QDT,請確保賬戶有足夠的QDT餘額', @@ -1103,7 +1109,10 @@ const locale = { 'trading-assistant.form.testConnection': '測試連接', 'trading-assistant.form.strategyName': '策略名稱', 'trading-assistant.form.symbol': '交易對', + 'trading-assistant.form.symbols': '交易對(多選)', 'trading-assistant.form.symbolHint': '目前僅支持加密貨幣交易對', + 'trading-assistant.form.symbolHintCrypto': '加密貨幣:使用BTC/USDT等交易對格式', + 'trading-assistant.form.symbolsHint': '選擇多個交易對,將自動創建多個策略', 'trading-assistant.form.initialCapital': '投入金額', 'trading-assistant.form.marketType': '市場類型', 'trading-assistant.form.marketTypeFutures': '合約', @@ -1135,6 +1144,23 @@ const locale = { 'trading-assistant.form.enableAiFilterHint': '啟用後,指標信號將經過AI智能過濾,提高交易質量', 'trading-assistant.form.aiFilterPrompt': '自定義提示詞', 'trading-assistant.form.aiFilterPromptHint': '為AI過濾提供自定義指令,留空則使用系統默認', + 'trading-assistant.form.executionMode': '執行模式', + 'trading-assistant.form.executionModeSignal': '僅信號通知', + 'trading-assistant.form.executionModeLive': '實盤自動交易', + 'trading-assistant.form.liveTradingCryptoOnlyHint': '實盤交易功能僅支持加密貨幣市場', + 'trading-assistant.form.notifyChannels': '通知渠道', + 'trading-assistant.form.notifyChannelsHint': '選擇信號觸發時的通知方式', + 'trading-assistant.form.notifyEmail': '郵箱地址', + 'trading-assistant.form.notifyPhone': '手機號碼', + 'trading-assistant.form.notifyTelegram': 'Telegram ID', + 'trading-assistant.form.notifyDiscord': 'Discord Webhook', + 'trading-assistant.form.notifyWebhook': 'Webhook 地址', + 'trading-assistant.form.liveTradingConfigTitle': '實盤交易配置', + 'trading-assistant.form.liveTradingConfigHint': '請填寫交易所API資訊,實盤交易將使用您的API進行真實交易', + 'trading-assistant.form.savedCredential': '已儲存的憑證', + 'trading-assistant.form.savedCredentialHint': '選擇已儲存的交易所憑證,自動填充API配置', + 'trading-assistant.form.saveCredential': '儲存此憑證以便下次使用', + 'trading-assistant.form.credentialName': '憑證名稱', 'trading-assistant.validation.strategyTypeRequired': '請選擇策略類型', 'trading-assistant.form.advancedSettings': '高級設置', 'trading-assistant.form.orderMode': '下單模式', @@ -1183,6 +1209,20 @@ const locale = { 'trading-assistant.messages.loadIndicatorsFailed': '加載指標列表失敗,請稍後重試', 'trading-assistant.messages.spotLimitations': '現貨交易已自動設置爲僅做多、1倍槓杆', 'trading-assistant.messages.autoFillApiConfig': '已自動填充該交易所的歷史API配置', + 'trading-assistant.messages.batchCreateSuccess': '成功創建 {count} 個策略', + 'trading-assistant.messages.batchStartSuccess': '成功啟動 {count} 個策略', + 'trading-assistant.messages.batchStartFailed': '批量啟動策略失敗', + 'trading-assistant.messages.batchStopSuccess': '成功停止 {count} 個策略', + 'trading-assistant.messages.batchStopFailed': '批量停止策略失敗', + 'trading-assistant.messages.batchDeleteSuccess': '成功刪除 {count} 個策略', + 'trading-assistant.messages.batchDeleteFailed': '批量刪除策略失敗', + 'trading-assistant.messages.batchDeleteConfirm': '確定要刪除策略組"{name}"下的 {count} 個策略嗎?此操作不可恢復。', + 'trading-assistant.notify.browser': '瀏覽器通知', + 'trading-assistant.notify.email': '郵箱', + 'trading-assistant.notify.phone': '簡訊', + 'trading-assistant.notify.telegram': 'Telegram', + 'trading-assistant.notify.discord': 'Discord', + 'trading-assistant.notify.webhook': 'Webhook', 'trading-assistant.placeholders.selectIndicator': '請選擇指標', 'trading-assistant.placeholders.selectExchange': '請選擇交易所', 'trading-assistant.placeholders.inputApiKey': '請輸入API Key', @@ -1190,9 +1230,17 @@ const locale = { 'trading-assistant.placeholders.inputPassphrase': '請輸入Passphrase', 'trading-assistant.placeholders.inputStrategyName': '請輸入策略名稱', 'trading-assistant.placeholders.selectSymbol': '請選擇交易對', + 'trading-assistant.placeholders.selectSymbols': '請選擇交易對(支持多選)', 'trading-assistant.placeholders.selectTimeframe': '請選擇時間周期', 'trading-assistant.placeholders.selectKlinePeriod': '請選擇K線周期', 'trading-assistant.placeholders.inputAiFilterPrompt': '請輸入自定義提示詞(可選)', + 'trading-assistant.placeholders.selectSavedCredential': '請選擇已儲存的憑證', + 'trading-assistant.placeholders.inputEmail': '請輸入郵箱地址', + 'trading-assistant.placeholders.inputPhone': '請輸入手機號碼', + 'trading-assistant.placeholders.inputTelegram': '請輸入Telegram ID或Chat ID', + 'trading-assistant.placeholders.inputDiscord': '請輸入Discord Webhook地址', + 'trading-assistant.placeholders.inputWebhook': '請輸入Webhook地址', + 'trading-assistant.placeholders.inputCredentialName': '請輸入憑證名稱(如:我的OKX帳戶)', 'trading-assistant.validation.indicatorRequired': '請選擇指標', 'trading-assistant.validation.exchangeRequired': '請選擇交易所', 'trading-assistant.validation.apiKeyRequired': '請輸入API Key', @@ -1203,6 +1251,9 @@ const locale = { 'trading-assistant.validation.testConnectionFailed': '連接測試失敗,請檢查配置後重新測試', 'trading-assistant.validation.strategyNameRequired': '請輸入策略名稱', 'trading-assistant.validation.symbolRequired': '請選擇交易對', + 'trading-assistant.validation.symbolsRequired': '請至少選擇一個交易對', + 'trading-assistant.validation.emailInvalid': '請輸入有效的郵箱地址', + 'trading-assistant.validation.notifyChannelRequired': '請至少選擇一個通知渠道', 'trading-assistant.validation.initialCapitalRequired': '請輸入投入金額', 'trading-assistant.validation.leverageRequired': '請輸入槓杆倍數', 'trading-assistant.table.time': '時間', diff --git a/quantdinger_vue/src/views/trading-assistant/index.vue b/quantdinger_vue/src/views/trading-assistant/index.vue index b3567b5..41f0b3e 100644 --- a/quantdinger_vue/src/views/trading-assistant/index.vue +++ b/quantdinger_vue/src/views/trading-assistant/index.vue @@ -20,69 +20,153 @@ - - + +
+ +
+
+ + + {{ group.baseName }} + {{ group.strategies.length }} {{ $t('trading-assistant.symbolCount') }} +
+
+ + {{ group.runningCount }} {{ $t('trading-assistant.status.running') }} + + + {{ group.stoppedCount }} {{ $t('trading-assistant.status.stopped') }} + + + + + + {{ $t('trading-assistant.startAll') }} + + + + {{ $t('trading-assistant.stopAll') }} + + + + + {{ $t('trading-assistant.deleteAll') }} + + + + +
+
+ +
+
+
+
+
+ + + {{ item.trading_config.symbol }} + + + {{ getStatusText(item.status) }} + +
+
+
+
+ + + + + {{ $t('trading-assistant.startStrategy') }} + + + + {{ $t('trading-assistant.stopStrategy') }} + + + + + {{ $t('trading-assistant.editStrategy') }} + + + + + {{ $t('trading-assistant.deleteStrategy') }} + + + + +
+
+
+
+ + +
- - - - - - - +
+ +
@@ -312,8 +396,10 @@ /> - + + + + + +
+ + {{ item.market }} + + {{ item.symbol }} + {{ item.name }} +
+
+
- {{ $t('trading-assistant.form.symbolHintCrypto') }} + {{ isEditMode ? $t('trading-assistant.form.symbolHintCrypto') : $t('trading-assistant.form.symbolsHint') }}
@@ -991,7 +1104,7 @@