Skip to content

Commit e7cb9c6

Browse files
committed
feat(i18n): add missing translations for trading-assistant modal
- Add step2Params and step3Signal translations - Add execution mode related translations (signal/live) - Add notification channel translations (browser/email/phone/telegram/discord/webhook) - Add live trading config translations (savedCredential, credentialName, etc.) - Add batch operation message translations (batchCreateSuccess, batchStart/Stop/Delete) - Add placeholders for email, phone, telegram, discord, webhook inputs - Add validation messages for email and notify channel - Add strategy group translations (startAll, stopAll, deleteAll, symbolCount) - Support both zh-CN (Simplified Chinese) and zh-TW (Traditional Chinese)
1 parent 74138b4 commit e7cb9c6

8 files changed

Lines changed: 972 additions & 85 deletions

File tree

backend_api_python/app/routes/strategy.py

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,171 @@ def create_strategy():
7474
return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500
7575

7676

77+
@strategy_bp.route('/strategies/batch-create', methods=['POST'])
78+
def batch_create_strategies():
79+
"""
80+
批量创建策略(多币种)
81+
82+
请求体:
83+
strategy_name: 策略基础名称
84+
symbols: 币种数组,如 ["Crypto:BTC/USDT", "Crypto:ETH/USDT"]
85+
... 其他策略配置
86+
"""
87+
try:
88+
payload = request.get_json() or {}
89+
payload['user_id'] = int(payload.get('user_id') or 1)
90+
payload['strategy_type'] = payload.get('strategy_type') or 'IndicatorStrategy'
91+
92+
result = get_strategy_service().batch_create_strategies(payload)
93+
94+
if result['success']:
95+
return jsonify({
96+
'code': 1,
97+
'msg': f"成功创建 {result['total_created']} 个策略",
98+
'data': result
99+
})
100+
else:
101+
return jsonify({
102+
'code': 0,
103+
'msg': '批量创建失败',
104+
'data': result
105+
})
106+
except Exception as e:
107+
logger.error(f"batch_create_strategies failed: {str(e)}")
108+
logger.error(traceback.format_exc())
109+
return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500
110+
111+
112+
@strategy_bp.route('/strategies/batch-start', methods=['POST'])
113+
def batch_start_strategies():
114+
"""
115+
批量启动策略
116+
117+
请求体:
118+
strategy_ids: 策略ID数组
119+
120+
strategy_group_id: 策略组ID
121+
"""
122+
try:
123+
payload = request.get_json() or {}
124+
strategy_ids = payload.get('strategy_ids') or []
125+
strategy_group_id = payload.get('strategy_group_id')
126+
127+
# 如果提供了策略组ID,获取组内所有策略
128+
if strategy_group_id and not strategy_ids:
129+
strategy_ids = get_strategy_service().get_strategies_by_group(strategy_group_id)
130+
131+
if not strategy_ids:
132+
return jsonify({'code': 0, 'msg': '请提供策略ID', 'data': None}), 400
133+
134+
# 先更新数据库状态
135+
result = get_strategy_service().batch_start_strategies(strategy_ids)
136+
137+
# 然后启动执行器
138+
executor = get_trading_executor()
139+
for sid in result.get('success_ids', []):
140+
try:
141+
executor.start_strategy(sid)
142+
except Exception as e:
143+
logger.error(f"Failed to start executor for strategy {sid}: {e}")
144+
145+
return jsonify({
146+
'code': 1 if result['success'] else 0,
147+
'msg': f"成功启动 {len(result.get('success_ids', []))} 个策略",
148+
'data': result
149+
})
150+
except Exception as e:
151+
logger.error(f"batch_start_strategies failed: {str(e)}")
152+
logger.error(traceback.format_exc())
153+
return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500
154+
155+
156+
@strategy_bp.route('/strategies/batch-stop', methods=['POST'])
157+
def batch_stop_strategies():
158+
"""
159+
批量停止策略
160+
161+
请求体:
162+
strategy_ids: 策略ID数组
163+
164+
strategy_group_id: 策略组ID
165+
"""
166+
try:
167+
payload = request.get_json() or {}
168+
strategy_ids = payload.get('strategy_ids') or []
169+
strategy_group_id = payload.get('strategy_group_id')
170+
171+
if strategy_group_id and not strategy_ids:
172+
strategy_ids = get_strategy_service().get_strategies_by_group(strategy_group_id)
173+
174+
if not strategy_ids:
175+
return jsonify({'code': 0, 'msg': '请提供策略ID', 'data': None}), 400
176+
177+
# 先停止执行器
178+
executor = get_trading_executor()
179+
for sid in strategy_ids:
180+
try:
181+
executor.stop_strategy(sid)
182+
except Exception as e:
183+
logger.error(f"Failed to stop executor for strategy {sid}: {e}")
184+
185+
# 然后更新数据库状态
186+
result = get_strategy_service().batch_stop_strategies(strategy_ids)
187+
188+
return jsonify({
189+
'code': 1 if result['success'] else 0,
190+
'msg': f"成功停止 {len(result.get('success_ids', []))} 个策略",
191+
'data': result
192+
})
193+
except Exception as e:
194+
logger.error(f"batch_stop_strategies failed: {str(e)}")
195+
logger.error(traceback.format_exc())
196+
return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500
197+
198+
199+
@strategy_bp.route('/strategies/batch-delete', methods=['DELETE'])
200+
def batch_delete_strategies():
201+
"""
202+
批量删除策略
203+
204+
请求体:
205+
strategy_ids: 策略ID数组
206+
207+
strategy_group_id: 策略组ID
208+
"""
209+
try:
210+
payload = request.get_json() or {}
211+
strategy_ids = payload.get('strategy_ids') or []
212+
strategy_group_id = payload.get('strategy_group_id')
213+
214+
if strategy_group_id and not strategy_ids:
215+
strategy_ids = get_strategy_service().get_strategies_by_group(strategy_group_id)
216+
217+
if not strategy_ids:
218+
return jsonify({'code': 0, 'msg': '请提供策略ID', 'data': None}), 400
219+
220+
# 先停止执行器
221+
executor = get_trading_executor()
222+
for sid in strategy_ids:
223+
try:
224+
executor.stop_strategy(sid)
225+
except Exception as e:
226+
pass # 忽略停止错误
227+
228+
# 然后删除
229+
result = get_strategy_service().batch_delete_strategies(strategy_ids)
230+
231+
return jsonify({
232+
'code': 1 if result['success'] else 0,
233+
'msg': f"成功删除 {len(result.get('success_ids', []))} 个策略",
234+
'data': result
235+
})
236+
except Exception as e:
237+
logger.error(f"batch_delete_strategies failed: {str(e)}")
238+
logger.error(traceback.format_exc())
239+
return jsonify({'code': 0, 'msg': str(e), 'data': None}), 500
240+
241+
77242
@strategy_bp.route('/strategies/update', methods=['PUT'])
78243
def update_strategy():
79244
try:

backend_api_python/app/services/strategy.py

Lines changed: 153 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import time
33
import json
44
import threading
5+
import uuid
56
from typing import List, Dict, Any, Optional
67
from datetime import datetime
78

@@ -534,6 +535,10 @@ def create_strategy(self, payload: Dict[str, Any]) -> int:
534535
trading_config = payload.get('trading_config') or {}
535536
exchange_config = payload.get('exchange_config') or {}
536537

538+
# 策略组字段
539+
strategy_group_id = payload.get('strategy_group_id') or ''
540+
group_base_name = payload.get('group_base_name') or ''
541+
537542
# Denormalized fields for quick list rendering
538543
symbol = (trading_config or {}).get('symbol')
539544
timeframe = (trading_config or {}).get('timeframe')
@@ -549,8 +554,9 @@ def create_strategy(self, payload: Dict[str, Any]) -> int:
549554
(strategy_name, strategy_type, market_category, execution_mode, notification_config,
550555
status, symbol, timeframe, initial_capital, leverage, market_type,
551556
exchange_config, indicator_config, trading_config, ai_model_config, decide_interval,
557+
strategy_group_id, group_base_name,
552558
created_at, updated_at)
553-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
559+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
554560
""",
555561
(
556562
name,
@@ -569,6 +575,8 @@ def create_strategy(self, payload: Dict[str, Any]) -> int:
569575
self._dump_json_or_encrypt(trading_config, encrypt=False),
570576
self._dump_json_or_encrypt(payload.get('ai_model_config') or {}, encrypt=False),
571577
int(payload.get('decide_interval') or 300),
578+
strategy_group_id,
579+
group_base_name,
572580
now,
573581
now
574582
)
@@ -578,6 +586,150 @@ def create_strategy(self, payload: Dict[str, Any]) -> int:
578586
cur.close()
579587
return int(new_id)
580588

589+
def batch_create_strategies(self, payload: Dict[str, Any]) -> Dict[str, Any]:
590+
"""
591+
批量创建策略(多币种)
592+
593+
Args:
594+
payload: 包含 symbols(数组)和其他策略配置
595+
596+
Returns:
597+
{
598+
'success': True/False,
599+
'strategy_group_id': '...',
600+
'created_ids': [1, 2, 3],
601+
'failed_symbols': []
602+
}
603+
"""
604+
symbols = payload.get('symbols') or []
605+
if not symbols or not isinstance(symbols, list):
606+
raise ValueError("symbols array is required")
607+
608+
base_name = (payload.get('strategy_name') or '').strip()
609+
if not base_name:
610+
raise ValueError("strategy_name is required")
611+
612+
# 生成策略组ID
613+
strategy_group_id = str(uuid.uuid4())[:8]
614+
615+
created_ids = []
616+
failed_symbols = []
617+
618+
for symbol in symbols:
619+
try:
620+
# 为每个币种创建单独的策略
621+
single_payload = dict(payload)
622+
623+
# 解析 symbol(可能是 "Market:SYMBOL" 格式)
624+
if isinstance(symbol, str) and ':' in symbol:
625+
parts = symbol.split(':', 1)
626+
market_category = parts[0]
627+
symbol_name = parts[1]
628+
else:
629+
market_category = payload.get('market_category') or 'Crypto'
630+
symbol_name = symbol
631+
632+
# 策略名称加币种后缀
633+
single_payload['strategy_name'] = f"{base_name}-{symbol_name}"
634+
single_payload['strategy_group_id'] = strategy_group_id
635+
single_payload['group_base_name'] = base_name
636+
single_payload['market_category'] = market_category
637+
638+
# 更新 trading_config 中的 symbol
639+
trading_config = dict(single_payload.get('trading_config') or {})
640+
trading_config['symbol'] = symbol_name
641+
single_payload['trading_config'] = trading_config
642+
643+
new_id = self.create_strategy(single_payload)
644+
created_ids.append(new_id)
645+
646+
except Exception as e:
647+
logger.error(f"Failed to create strategy for symbol {symbol}: {e}")
648+
failed_symbols.append({'symbol': symbol, 'error': str(e)})
649+
650+
return {
651+
'success': len(created_ids) > 0,
652+
'strategy_group_id': strategy_group_id,
653+
'group_base_name': base_name,
654+
'created_ids': created_ids,
655+
'failed_symbols': failed_symbols,
656+
'total_created': len(created_ids),
657+
'total_failed': len(failed_symbols)
658+
}
659+
660+
def batch_start_strategies(self, strategy_ids: List[int]) -> Dict[str, Any]:
661+
"""批量启动策略"""
662+
success_ids = []
663+
failed_ids = []
664+
665+
for sid in strategy_ids:
666+
try:
667+
self.update_strategy_status(sid, 'running')
668+
success_ids.append(sid)
669+
except Exception as e:
670+
logger.error(f"Failed to start strategy {sid}: {e}")
671+
failed_ids.append({'id': sid, 'error': str(e)})
672+
673+
return {
674+
'success': len(success_ids) > 0,
675+
'success_ids': success_ids,
676+
'failed_ids': failed_ids
677+
}
678+
679+
def batch_stop_strategies(self, strategy_ids: List[int]) -> Dict[str, Any]:
680+
"""批量停止策略"""
681+
success_ids = []
682+
failed_ids = []
683+
684+
for sid in strategy_ids:
685+
try:
686+
self.update_strategy_status(sid, 'stopped')
687+
success_ids.append(sid)
688+
except Exception as e:
689+
logger.error(f"Failed to stop strategy {sid}: {e}")
690+
failed_ids.append({'id': sid, 'error': str(e)})
691+
692+
return {
693+
'success': len(success_ids) > 0,
694+
'success_ids': success_ids,
695+
'failed_ids': failed_ids
696+
}
697+
698+
def batch_delete_strategies(self, strategy_ids: List[int]) -> Dict[str, Any]:
699+
"""批量删除策略"""
700+
success_ids = []
701+
failed_ids = []
702+
703+
for sid in strategy_ids:
704+
try:
705+
self.delete_strategy(sid)
706+
success_ids.append(sid)
707+
except Exception as e:
708+
logger.error(f"Failed to delete strategy {sid}: {e}")
709+
failed_ids.append({'id': sid, 'error': str(e)})
710+
711+
return {
712+
'success': len(success_ids) > 0,
713+
'success_ids': success_ids,
714+
'failed_ids': failed_ids
715+
}
716+
717+
def get_strategies_by_group(self, strategy_group_id: str) -> List[Dict[str, Any]]:
718+
"""获取策略组内的所有策略"""
719+
try:
720+
with get_db_connection() as db:
721+
cur = db.cursor()
722+
cur.execute(
723+
"SELECT id FROM qd_strategies_trading WHERE strategy_group_id = ?",
724+
(strategy_group_id,)
725+
)
726+
rows = cur.fetchall() or []
727+
cur.close()
728+
return [row['id'] for row in rows]
729+
except Exception as e:
730+
logger.error(f"get_strategies_by_group failed: {e}")
731+
return []
732+
581733
def update_strategy(self, strategy_id: int, payload: Dict[str, Any]) -> bool:
582734
now = int(time.time())
583735
existing = self.get_strategy(strategy_id)

backend_api_python/app/utils/db.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,9 @@ def ensure_columns(table: str, columns: Dict[str, str]) -> None:
104104
ensure_columns("qd_strategies_trading", {
105105
"market_category": "TEXT DEFAULT 'Crypto'",
106106
"execution_mode": "TEXT DEFAULT 'signal'",
107-
"notification_config": "TEXT DEFAULT ''"
107+
"notification_config": "TEXT DEFAULT ''",
108+
"strategy_group_id": "TEXT DEFAULT ''", # 策略组ID,批量创建的策略共享同一个组ID
109+
"group_base_name": "TEXT DEFAULT ''" # 策略组基础名称(用于显示)
108110
})
109111

110112
# 2. 持仓表

0 commit comments

Comments
 (0)