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)
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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. 持仓表
|
||||
|
||||
Reference in New Issue
Block a user