From dc49c2717bd5b3f4bbea396b27928f5e48e60f06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=87=E8=96=87=E5=AE=89?= Date: Thu, 22 Jan 2026 08:50:42 +0800 Subject: [PATCH] a --- backend/api/routes/trades.py | 16 ++++++ backend/config_manager.py | 10 ++-- frontend/src/components/ConfigPanel.jsx | 41 +++++++++++---- trading_system/config.py | 13 +++-- trading_system/position_manager.py | 70 ++++++++++++++++++++++++- 5 files changed, 132 insertions(+), 18 deletions(-) diff --git a/backend/api/routes/trades.py b/backend/api/routes/trades.py index b69fcaf..a7dafc8 100644 --- a/backend/api/routes/trades.py +++ b/backend/api/routes/trades.py @@ -230,6 +230,14 @@ async def get_trade_stats( sum(abs(float(t["pnl"])) for t in loss_trades) / len(loss_trades) if loss_trades else 0.0 ) win_loss_ratio = (avg_win_pnl / avg_loss_pnl_abs) if avg_loss_pnl_abs > 0 else None + + # 实际盈亏比(所有盈利单的总盈利 / 所有亏损单的总亏损,必须 > 1.5,目标 2.5-3.0) + total_win_pnl = sum(float(t["pnl"]) for t in win_trades) if win_trades else 0.0 + total_loss_pnl_abs = sum(abs(float(t["pnl"])) for t in loss_trades) if loss_trades else 0.0 + actual_profit_loss_ratio = (total_win_pnl / total_loss_pnl_abs) if total_loss_pnl_abs > 0 else None + + # 盈利因子(总盈利金额 / 总亏损金额,必须 > 1.1,目标 1.5+) + profit_factor = (total_win_pnl / total_loss_pnl_abs) if total_loss_pnl_abs > 0 else None # 平仓原因分布(用来快速定位胜率低的主要来源:止损/止盈/同步等) exit_reason_counts = Counter((t.get("exit_reason") or "unknown") for t in meaningful_trades) @@ -277,6 +285,14 @@ async def get_trade_stats( "avg_loss_pnl_abs": avg_loss_pnl_abs, "avg_win_loss_ratio": win_loss_ratio, "avg_win_loss_ratio_target": 3.0, + # 实际盈亏比(所有盈利单总盈利 / 所有亏损单总亏损,目标 > 2.0) + "actual_profit_loss_ratio": actual_profit_loss_ratio, + "actual_profit_loss_ratio_target": 2.0, + "total_win_pnl": total_win_pnl, + "total_loss_pnl_abs": total_loss_pnl_abs, + # 盈利因子(总盈利 / 总亏损,目标 > 1.2) + "profit_factor": profit_factor, + "profit_factor_target": 1.2, "exit_reason_counts": dict(exit_reason_counts), "avg_duration_minutes": avg_duration_minutes, # 总交易量(名义下单量口径):优先使用 notional_usdt(新字段),否则回退 entry_price * quantity diff --git a/backend/config_manager.py b/backend/config_manager.py index 07e55b2..294cdf4 100644 --- a/backend/config_manager.py +++ b/backend/config_manager.py @@ -539,12 +539,12 @@ class ConfigManager: # 风险控制 'STOP_LOSS_PERCENT': eff_get('STOP_LOSS_PERCENT', 0.10), # 默认10% - 'TAKE_PROFIT_PERCENT': eff_get('TAKE_PROFIT_PERCENT', 0.30), # 默认30%(盈亏比3:1) + 'TAKE_PROFIT_PERCENT': eff_get('TAKE_PROFIT_PERCENT', 0.25), # 默认25%(从30%放宽到25%,配合ATR止盈放大盈亏比) 'MIN_STOP_LOSS_PRICE_PCT': eff_get('MIN_STOP_LOSS_PRICE_PCT', 0.02), # 默认2% 'MIN_TAKE_PROFIT_PRICE_PCT': eff_get('MIN_TAKE_PROFIT_PRICE_PCT', 0.03), # 默认3% 'USE_ATR_STOP_LOSS': eff_get('USE_ATR_STOP_LOSS', True), # 是否使用ATR动态止损 'ATR_STOP_LOSS_MULTIPLIER': eff_get('ATR_STOP_LOSS_MULTIPLIER', 1.8), # ATR止损倍数(1.5-2倍) - 'ATR_TAKE_PROFIT_MULTIPLIER': eff_get('ATR_TAKE_PROFIT_MULTIPLIER', 3.0), # ATR止盈倍数(3倍ATR) + 'ATR_TAKE_PROFIT_MULTIPLIER': eff_get('ATR_TAKE_PROFIT_MULTIPLIER', 4.5), # ATR止盈倍数(从3.0提升到4.5,放大盈亏比) 'RISK_REWARD_RATIO': eff_get('RISK_REWARD_RATIO', 3.0), # 盈亏比(止损距离的倍数) 'ATR_PERIOD': eff_get('ATR_PERIOD', 14), # ATR计算周期 'USE_DYNAMIC_ATR_MULTIPLIER': eff_get('USE_DYNAMIC_ATR_MULTIPLIER', False), # 是否根据波动率动态调整ATR倍数 @@ -569,9 +569,13 @@ class ConfigManager: 'LEVERAGE': eff_get('LEVERAGE', 10), 'USE_DYNAMIC_LEVERAGE': eff_get('USE_DYNAMIC_LEVERAGE', True), 'MAX_LEVERAGE': eff_get('MAX_LEVERAGE', 15), # 降低到15,更保守,配合更大的保证金 - 'USE_TRAILING_STOP': eff_get('USE_TRAILING_STOP', True), + # 移动止损:默认关闭(避免过早截断利润,让利润奔跑) + 'USE_TRAILING_STOP': eff_get('USE_TRAILING_STOP', False), 'TRAILING_STOP_ACTIVATION': eff_get('TRAILING_STOP_ACTIVATION', 0.10), # 默认10%(给趋势更多空间) 'TRAILING_STOP_PROTECT': eff_get('TRAILING_STOP_PROTECT', 0.05), # 默认5%(保护更多利润) + + # 最小持仓时间锁(强制波段持仓纪律,避免分钟级平仓) + 'MIN_HOLD_TIME_SEC': eff_get('MIN_HOLD_TIME_SEC', 1800), # 默认30分钟(1800秒) # 自动交易过滤(用于提升胜率/控频) # 说明:这两个 key 需要出现在 TRADING_CONFIG 中,否则 trading_system 在每次 reload_from_redis 后会丢失它们, diff --git a/frontend/src/components/ConfigPanel.jsx b/frontend/src/components/ConfigPanel.jsx index 105537a..11054b3 100644 --- a/frontend/src/components/ConfigPanel.jsx +++ b/frontend/src/components/ConfigPanel.jsx @@ -79,7 +79,10 @@ const ConfigPanel = ({ currentUser }) => { // 风控 MIN_SIGNAL_STRENGTH: 8, - USE_TRAILING_STOP: false, + USE_TRAILING_STOP: false, // 禁用移动止损,让利润奔跑 + ATR_TAKE_PROFIT_MULTIPLIER: 4.5, // 放大ATR止盈倍数(从3.0提升到4.5) + TAKE_PROFIT_PERCENT: 25.0, // 放宽固定止盈(从30%到25%,配合ATR止盈放大盈亏比) + MIN_HOLD_TIME_SEC: 1800, // 最小持仓时间30分钟(强制波段持仓纪律) // 根治:关闭智能入场(回归纯限价,不追价/不市价兜底) SMART_ENTRY_ENABLED: false, @@ -109,7 +112,10 @@ const ConfigPanel = ({ currentUser }) => { ENTRY_MAX_DRIFT_PCT_RANGING: 0.15, // 0.15% // 风控:默认关闭移动止损(避免“保本价”过早触发) - USE_TRAILING_STOP: false, + USE_TRAILING_STOP: false, // 禁用移动止损,让利润奔跑 + ATR_TAKE_PROFIT_MULTIPLIER: 4.5, // 放大ATR止盈倍数(从3.0提升到4.5) + TAKE_PROFIT_PERCENT: 25.0, // 放宽固定止盈(从30%到25%,配合ATR止盈放大盈亏比) + MIN_HOLD_TIME_SEC: 1800, // 最小持仓时间30分钟(强制波段持仓纪律) }, }, strict: { @@ -127,7 +133,10 @@ const ConfigPanel = ({ currentUser }) => { LIMIT_ORDER_OFFSET_PCT: 0.1, ENTRY_CONFIRM_TIMEOUT_SEC: 180, - USE_TRAILING_STOP: false, + USE_TRAILING_STOP: false, // 禁用移动止损,让利润奔跑 + ATR_TAKE_PROFIT_MULTIPLIER: 4.5, // 放大ATR止盈倍数(从3.0提升到4.5) + TAKE_PROFIT_PERCENT: 25.0, // 放宽固定止盈(从30%到25%,配合ATR止盈放大盈亏比) + MIN_HOLD_TIME_SEC: 1800, // 最小持仓时间30分钟(强制波段持仓纪律) }, }, steady: { @@ -150,7 +159,10 @@ const ConfigPanel = ({ currentUser }) => { ENTRY_MAX_DRIFT_PCT_TRENDING: 0.4, ENTRY_MAX_DRIFT_PCT_RANGING: 0.2, - USE_TRAILING_STOP: false, + USE_TRAILING_STOP: false, // 禁用移动止损,让利润奔跑 + ATR_TAKE_PROFIT_MULTIPLIER: 4.5, // 放大ATR止盈倍数(从3.0提升到4.5) + TAKE_PROFIT_PERCENT: 25.0, // 放宽固定止盈(从30%到25%,配合ATR止盈放大盈亏比) + MIN_HOLD_TIME_SEC: 1800, // 最小持仓时间30分钟(强制波段持仓纪律) }, }, conservative: { @@ -164,9 +176,12 @@ const ConfigPanel = ({ currentUser }) => { MAX_SCAN_SYMBOLS: 150, MIN_VOLATILITY: 0.02, // 保持小数形式(波动率) STOP_LOSS_PERCENT: 10.0, // 10%(相对于保证金,更宽松) - TAKE_PROFIT_PERCENT: 20.0, // 20%(相对于保证金,给趋势更多空间) + TAKE_PROFIT_PERCENT: 25.0, // 25%(相对于保证金,从20%提升到25%,放大盈亏比) MIN_STOP_LOSS_PRICE_PCT: 2.0, // 2%最小价格变动保护 - MIN_TAKE_PROFIT_PRICE_PCT: 3.0 // 3%最小价格变动保护 + MIN_TAKE_PROFIT_PRICE_PCT: 3.0, // 3%最小价格变动保护 + USE_TRAILING_STOP: false, // 禁用移动止损,让利润奔跑 + ATR_TAKE_PROFIT_MULTIPLIER: 4.5, // 放大ATR止盈倍数(从3.0提升到4.5) + MIN_HOLD_TIME_SEC: 1800 // 最小持仓时间30分钟(强制波段持仓纪律) } }, balanced: { @@ -180,9 +195,12 @@ const ConfigPanel = ({ currentUser }) => { MAX_SCAN_SYMBOLS: 250, MIN_VOLATILITY: 0.018, // 保持小数形式(波动率) STOP_LOSS_PERCENT: 8.0, // 8%(相对于保证金,默认值) - TAKE_PROFIT_PERCENT: 20.0, // 20%(相对于保证金,盈亏比2.5:1,提高收益) + TAKE_PROFIT_PERCENT: 25.0, // 25%(相对于保证金,从20%提升到25%,放大盈亏比) MIN_STOP_LOSS_PRICE_PCT: 2.0, // 2%最小价格变动保护 - MIN_TAKE_PROFIT_PRICE_PCT: 3.0 // 3%最小价格变动保护 + MIN_TAKE_PROFIT_PRICE_PCT: 3.0, // 3%最小价格变动保护 + USE_TRAILING_STOP: false, // 禁用移动止损,让利润奔跑 + ATR_TAKE_PROFIT_MULTIPLIER: 4.5, // 放大ATR止盈倍数(从3.0提升到4.5) + MIN_HOLD_TIME_SEC: 1800 // 最小持仓时间30分钟(强制波段持仓纪律) } }, aggressive: { @@ -196,9 +214,12 @@ const ConfigPanel = ({ currentUser }) => { MAX_SCAN_SYMBOLS: 350, MIN_VOLATILITY: 0.015, // 保持小数形式(波动率) STOP_LOSS_PERCENT: 5.0, // 5%(相对于保证金,较紧) - TAKE_PROFIT_PERCENT: 15.0, // 15%(相对于保证金,盈亏比3:1,能捕捉更大趋势) + TAKE_PROFIT_PERCENT: 25.0, // 25%(相对于保证金,从15%大幅提升到25%,放大盈亏比到5:1) MIN_STOP_LOSS_PRICE_PCT: 1.5, // 1.5%最小价格变动保护 - MIN_TAKE_PROFIT_PRICE_PCT: 2.0 // 2%最小价格变动保护 + MIN_TAKE_PROFIT_PRICE_PCT: 2.0, // 2%最小价格变动保护 + USE_TRAILING_STOP: false, // 禁用移动止损,让利润奔跑 + ATR_TAKE_PROFIT_MULTIPLIER: 4.5, // 放大ATR止盈倍数(从3.0提升到4.5) + MIN_HOLD_TIME_SEC: 1800 // 最小持仓时间30分钟(强制波段持仓纪律) } } } diff --git a/trading_system/config.py b/trading_system/config.py index 309bfdf..b3ad7a8 100644 --- a/trading_system/config.py +++ b/trading_system/config.py @@ -188,12 +188,12 @@ def _get_trading_config(): 'TOP_N_SYMBOLS': 50, # 每次扫描后处理的交易对数量(增加到50以获取更多推荐) 'MAX_SCAN_SYMBOLS': 500, # 扫描的最大交易对数量(0表示扫描所有) 'STOP_LOSS_PERCENT': 0.10, # 止损百分比(相对于保证金),默认10% - 'TAKE_PROFIT_PERCENT': 0.30, # 止盈百分比(相对于保证金),默认30%(盈亏比3:1) + 'TAKE_PROFIT_PERCENT': 0.25, # 止盈百分比(相对于保证金),默认25%(从30%放宽,配合ATR止盈放大盈亏比) 'MIN_STOP_LOSS_PRICE_PCT': 0.02, # 最小止损价格变动百分比(如0.02表示2%),防止止损过紧,默认2% 'MIN_TAKE_PROFIT_PRICE_PCT': 0.03, # 最小止盈价格变动百分比(如0.03表示3%),防止止盈过紧,默认3% 'USE_ATR_STOP_LOSS': True, # 是否使用ATR动态止损(优先于固定百分比) 'ATR_STOP_LOSS_MULTIPLIER': 1.8, # ATR止损倍数(1.5-2倍ATR,默认1.8) - 'ATR_TAKE_PROFIT_MULTIPLIER': 3.0, # ATR止盈倍数(3倍ATR,对应3:1盈亏比) + 'ATR_TAKE_PROFIT_MULTIPLIER': 4.5, # ATR止盈倍数(从3.0提升到4.5,放大盈亏比,让利润奔跑) 'RISK_REWARD_RATIO': 3.0, # 盈亏比(止损距离的倍数,用于计算止盈) 'ATR_PERIOD': 14, # ATR计算周期(默认14) 'USE_DYNAMIC_ATR_MULTIPLIER': False, # 是否根据波动率动态调整ATR倍数 @@ -210,9 +210,13 @@ def _get_trading_config(): 'LEVERAGE': 10, # 基础杠杆倍数 'USE_DYNAMIC_LEVERAGE': True, # 是否启用动态杠杆(根据信号强度调整) 'MAX_LEVERAGE': 15, # 最大杠杆倍数(降低到15,更保守,配合更大的保证金) - 'USE_TRAILING_STOP': True, + # 移动止损:默认关闭(避免过早截断利润,让利润奔跑) + 'USE_TRAILING_STOP': False, 'TRAILING_STOP_ACTIVATION': 0.10, # 移动止损激活提高到10%(盈利10%后激活,给趋势更多空间) 'TRAILING_STOP_PROTECT': 0.05, # 保护利润提高到5%(保护5%利润,更合理) + + # 最小持仓时间锁(强制波段持仓纪律,避免分钟级平仓) + 'MIN_HOLD_TIME_SEC': 1800, # 默认30分钟(1800秒),强制延长持仓时间 'POSITION_SYNC_INTERVAL': 60, # 持仓状态同步间隔(秒),缩短到1分钟,确保状态及时同步 # ===== 自动交易过滤(用于提升胜率/控频)===== @@ -272,6 +276,9 @@ defaults = { # 自动交易过滤默认值 'AUTO_TRADE_ONLY_TRENDING': True, 'AUTO_TRADE_ALLOW_4H_NEUTRAL': False, + + # 最小持仓时间锁(强制波段持仓纪律,避免分钟级平仓) + 'MIN_HOLD_TIME_SEC': 1800, # 默认30分钟(1800秒) } for key, value in defaults.items(): if key not in TRADING_CONFIG: diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index b8580ba..3c0b2c8 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -1185,6 +1185,28 @@ class PositionManager: else: current_price = entry_price + # 最小持仓时间锁(强制波段持仓纪律,避免分钟级平仓) + min_hold_sec = int(config.TRADING_CONFIG.get('MIN_HOLD_TIME_SEC', 1800) or 1800) + entry_time = position_info.get('entryTime') + hold_time_sec = 0 + if entry_time: + try: + if isinstance(entry_time, datetime): + hold_time_sec = int((get_beijing_time() - entry_time).total_seconds()) + else: + # 兼容:如果是时间戳或字符串 + hold_time_sec = int(time.time() - (float(entry_time) if isinstance(entry_time, (int, float)) else 0)) + except Exception: + hold_time_sec = 0 + + # 如果持仓时间不足,禁止平仓(除非是手动平仓) + if hold_time_sec < min_hold_sec: + logger.debug( + f"{symbol} [持仓时间锁] 持仓时间 {hold_time_sec}s < 最小要求 {min_hold_sec}s," + f"禁止自动平仓(强制波段持仓纪律)" + ) + continue # 跳过这个持仓,不触发任何平仓逻辑 + # 计算当前盈亏(基于保证金) leverage = position_info.get('leverage', 10) position_value = entry_price * quantity @@ -1218,7 +1240,7 @@ class PositionManager: except Exception as e: logger.debug(f"从Redis重新加载配置失败: {e}") - use_trailing = config.TRADING_CONFIG.get('USE_TRAILING_STOP', True) + use_trailing = config.TRADING_CONFIG.get('USE_TRAILING_STOP', False) if use_trailing: trailing_activation = config.TRADING_CONFIG.get('TRAILING_STOP_ACTIVATION', 0.01) # 相对于保证金 trailing_protect = config.TRADING_CONFIG.get('TRAILING_STOP_PROTECT', 0.01) # 相对于保证金 @@ -2344,7 +2366,29 @@ class PositionManager: except Exception as e: logger.debug(f"从Redis重新加载配置失败: {e}") - use_trailing = config.TRADING_CONFIG.get('USE_TRAILING_STOP', True) + # 最小持仓时间锁(强制波段持仓纪律,避免分钟级平仓) + min_hold_sec = int(config.TRADING_CONFIG.get('MIN_HOLD_TIME_SEC', 1800) or 1800) + entry_time = position_info.get('entryTime') + hold_time_sec = 0 + if entry_time: + try: + if isinstance(entry_time, datetime): + hold_time_sec = int((get_beijing_time() - entry_time).total_seconds()) + else: + # 兼容:如果是时间戳或字符串 + hold_time_sec = int(time.time() - (float(entry_time) if isinstance(entry_time, (int, float)) else 0)) + except Exception: + hold_time_sec = 0 + + # 如果持仓时间不足,禁止平仓(除非是手动平仓) + if hold_time_sec < min_hold_sec: + logger.debug( + f"{symbol} [持仓时间锁] 持仓时间 {hold_time_sec}s < 最小要求 {min_hold_sec}s," + f"禁止自动平仓(强制波段持仓纪律)" + ) + return # 不触发任何平仓逻辑 + + use_trailing = config.TRADING_CONFIG.get('USE_TRAILING_STOP', False) if use_trailing: trailing_activation = config.TRADING_CONFIG.get('TRAILING_STOP_ACTIVATION', 0.01) # 相对于保证金 trailing_protect = config.TRADING_CONFIG.get('TRAILING_STOP_PROTECT', 0.01) # 相对于保证金 @@ -2384,6 +2428,28 @@ class PositionManager: f"(保护{trailing_protect*100:.1f}% of margin = {protect_amount:.4f} USDT)" ) + # 最小持仓时间锁(强制波段持仓纪律,避免分钟级平仓) + min_hold_sec = int(config.TRADING_CONFIG.get('MIN_HOLD_TIME_SEC', 1800) or 1800) + entry_time = position_info.get('entryTime') + hold_time_sec = 0 + if entry_time: + try: + if isinstance(entry_time, datetime): + hold_time_sec = int((get_beijing_time() - entry_time).total_seconds()) + else: + # 兼容:如果是时间戳或字符串 + hold_time_sec = int(time.time() - (float(entry_time) if isinstance(entry_time, (int, float)) else 0)) + except Exception: + hold_time_sec = 0 + + # 如果持仓时间不足,禁止平仓(除非是手动平仓) + if hold_time_sec < min_hold_sec: + logger.debug( + f"{symbol} [持仓时间锁] 持仓时间 {hold_time_sec}s < 最小要求 {min_hold_sec}s," + f"禁止自动平仓(强制波段持仓纪律)" + ) + return # 不触发任何平仓逻辑 + # 检查止损(基于保证金收益比) stop_loss = position_info.get('stopLoss') should_close = False