diff --git a/backend/api/routes/config.py b/backend/api/routes/config.py index bf6abaa..a019dd1 100644 --- a/backend/api/routes/config.py +++ b/backend/api/routes/config.py @@ -831,6 +831,130 @@ async def check_config_feasibility( raise HTTPException(status_code=500, detail=f"检查配置可行性失败: {str(e)}") +@router.put("/global/{key}") +async def update_global_config( + key: str, + item: ConfigUpdate, + user: Dict[str, Any] = Depends(get_current_user), +): + """更新单个全局策略配置(仅管理员)""" + if (user.get("role") or "user") != "admin": + raise HTTPException(status_code=403, detail="仅管理员可修改全局策略配置") + + try: + from database.models import GlobalStrategyConfig + + # 验证配置值 + config_type = item.type or "string" + if config_type == 'number': + try: + float(item.value) + except (ValueError, TypeError): + raise HTTPException(status_code=400, detail=f"Invalid number value for {key}") + elif config_type == 'boolean': + if not isinstance(item.value, bool): + if isinstance(item.value, str): + item.value = item.value.lower() in ('true', '1', 'yes', 'on') + else: + item.value = bool(item.value) + + # 特殊验证:百分比配置应该在0-1之间 + if ('PERCENT' in key or 'PCT' in key) and config_type == 'number': + if not (0 <= float(item.value) <= 1): + raise HTTPException( + status_code=400, + detail=f"{key} must be between 0 and 1 (0% to 100%)" + ) + + # 更新数据库 + GlobalStrategyConfig.set( + key, + item.value, + config_type, + item.category or "strategy", + item.description, + updated_by=user.get("username") + ) + + # 更新缓存 (ConfigManager) + try: + import config_manager + # 更新 GlobalStrategyConfigManager 的缓存 + if hasattr(config_manager, 'GlobalStrategyConfigManager'): + mgr = config_manager.GlobalStrategyConfigManager() + # 写入Redis (会更新 redis 和 _cache) + mgr._set_to_redis(key, item.value) + # 手动更新本地缓存,确保当前进程也生效 + mgr._cache[key] = item.value + logger.info(f"全局配置已更新到Redis缓存: {key} = {item.value}") + except Exception as e: + logger.warning(f"更新全局配置缓存失败: {e}") + + return { + "message": "全局配置已更新", + "key": key, + "value": item.value + } + except HTTPException: + raise + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/global/batch") +async def update_global_configs_batch( + configs: list[ConfigItem], + user: Dict[str, Any] = Depends(get_current_user), +): + """批量更新全局策略配置(仅管理员)""" + if (user.get("role") or "user") != "admin": + raise HTTPException(status_code=403, detail="仅管理员可修改全局策略配置") + + try: + from database.models import GlobalStrategyConfig + import config_manager + + updated_count = 0 + mgr = None + if hasattr(config_manager, 'GlobalStrategyConfigManager'): + mgr = config_manager.GlobalStrategyConfigManager() + + for item in configs: + # 简单验证 + config_type = item.type + val = item.value + if config_type == 'number': + try: + val = float(val) + except: + continue + elif config_type == 'boolean': + if not isinstance(val, bool): + val = str(val).lower() in ('true', '1', 'yes', 'on') + + # 更新DB + GlobalStrategyConfig.set( + item.key, + val, + config_type, + item.category, + item.description, + updated_by=user.get("username") + ) + + # 更新缓存 + if mgr: + mgr._set_to_redis(item.key, val) + mgr._cache[item.key] = val + + updated_count += 1 + + return {"message": f"成功更新 {updated_count} 个全局配置项"} + + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + diff --git a/frontend/src/components/GlobalConfig.jsx b/frontend/src/components/GlobalConfig.jsx index ea258d6..d99505e 100644 --- a/frontend/src/components/GlobalConfig.jsx +++ b/frontend/src/components/GlobalConfig.jsx @@ -35,7 +35,7 @@ const ConfigItem = ({ label, config, onUpdate, disabled }) => { 'LIMIT_ORDER_OFFSET_PCT', 'ENTRY_MAX_DRIFT_PCT_TRENDING', 'ENTRY_MAX_DRIFT_PCT_RANGING', - 'FIXED_RISK_PERCENT', // 固定风险百分比,已经是小数形式(0.02 = 2%) + // FIXED_RISK_PERCENT 已移除,直接显示小数 ]) const isPctLike = PCT_LIKE_KEYS.has(label) const isNumberAsIs = NUMBER_AS_IS_KEYS.has(label) @@ -429,6 +429,24 @@ const GlobalConfig = () => { MIN_HOLD_TIME_SEC: 1800, USE_DYNAMIC_ATR_MULTIPLIER: false, // 关闭动态调整,使用固定2.5倍 } + }, + script_v1: { + name: '高收益趋势 (脚本配置)', + desc: '原 update_global_config.py 脚本中的配置。高止盈(60%) + 紧止损(10%) + 移动止损保护。适合捕捉大单边行情。', + configs: { + TAKE_PROFIT_PERCENT: 60.0, + TAKE_PROFIT_1_PERCENT: 30.0, + STOP_LOSS_PERCENT: 10.0, + TRAILING_STOP_ACTIVATION: 30.0, + TRAILING_STOP_PROTECT: 5.0, + LEVERAGE: 4, + RISK_REWARD_RATIO: 3.0, + ATR_TAKE_PROFIT_MULTIPLIER: 4.5, + ATR_STOP_LOSS_MULTIPLIER: 1.5, + USE_FIXED_RISK_SIZING: true, + FIXED_RISK_PERCENT: 1.0, // 1% + MAX_LEVERAGE_SMALL_CAP: 4, + } } } @@ -447,6 +465,17 @@ const GlobalConfig = () => { BETA_FILTER_THRESHOLD: { value: -0.005, type: 'number', category: 'strategy', description: '大盘共振阈值(比例,如 -0.005 表示 -0.5%)。' }, RSI_EXTREME_REVERSE_ENABLED: { value: false, type: 'boolean', category: 'strategy', description: '开启后:原信号做多但 RSI 超买(≥做多上限)时改为做空;原信号做空但 RSI 超卖(≤做空下限)时改为做多。属均值回归思路,可填补超买超卖时不下单的空置;默认关闭。' }, RSI_EXTREME_REVERSE_ONLY_NEUTRAL_4H: { value: true, type: 'boolean', category: 'strategy', description: '建议开启:仅在 4H 趋势为中性时允许 RSI 反向单,避免在强趋势里逆势抄底/摸顶,降低风险。关闭则反向可与 4H 同向(仍受“禁止逆4H趋势”约束)。' }, + TAKE_PROFIT_PERCENT: { value: 0.25, type: 'number', category: 'risk', description: '止盈百分比(保证金比例,如 25.0=25%)。' }, + STOP_LOSS_PERCENT: { value: 0.08, type: 'number', category: 'risk', description: '止损百分比(保证金比例,如 8.0=8%)。' }, + TRAILING_STOP_ACTIVATION: { value: 0.20, type: 'number', category: 'risk', description: '移动止损激活阈值(盈利达到此比例后激活,如 20.0=20%)。' }, + TRAILING_STOP_PROTECT: { value: 0.10, type: 'number', category: 'risk', description: '移动止损保护阈值(回撤至此比例时触发止损,如 10.0=10%)。' }, + LEVERAGE: { value: 5, type: 'number', category: 'risk', description: '基础杠杆倍数。' }, + RISK_REWARD_RATIO: { value: 1.5, type: 'number', category: 'risk', description: '盈亏比目标(用于计算动态止盈止损)。' }, + ATR_TAKE_PROFIT_MULTIPLIER: { value: 1.5, type: 'number', category: 'risk', description: 'ATR 止盈倍数。' }, + ATR_STOP_LOSS_MULTIPLIER: { value: 2.5, type: 'number', category: 'risk', description: 'ATR 止损倍数。' }, + USE_FIXED_RISK_SIZING: { value: false, type: 'boolean', category: 'risk', description: '是否使用固定风险仓位计算(基于止损距离和账户余额)。' }, + FIXED_RISK_PERCENT: { value: 0.01, type: 'number', category: 'risk', description: '每笔交易固定风险百分比(如 0.01=1%)。' }, + MAX_LEVERAGE_SMALL_CAP: { value: 3, type: 'number', category: 'risk', description: '小市值/山寨币最大允许杠杆。' }, } const loadConfigs = async () => { @@ -950,6 +979,7 @@ const GlobalConfig = () => { conservative: { group: 'legacy', tag: '传统' }, balanced: { group: 'legacy', tag: '传统' }, aggressive: { group: 'legacy', tag: '高频实验' }, + script_v1: { group: 'legacy', tag: '脚本迁移' }, } const presetGroups = [ @@ -975,7 +1005,7 @@ const GlobalConfig = () => { key: 'legacy', title: 'C. 传统 / 实验(不建议长期)', desc: '这组更多用于对比或临时实验(频率更高/更容易过度交易),建议在稳定盈利前谨慎使用。', - presetKeys: ['conservative', 'balanced', 'aggressive'], + presetKeys: ['conservative', 'balanced', 'aggressive', 'script_v1'], }, ]