diff --git a/backend/api/routes/config.py b/backend/api/routes/config.py index 9cb78c6..e01c0fc 100644 --- a/backend/api/routes/config.py +++ b/backend/api/routes/config.py @@ -458,6 +458,30 @@ async def get_global_configs( "category": "strategy", "description": "连续亏损后的冷却时间(秒),默认1小时。2026-01-29新增。", }, + "MAX_RSI_FOR_LONG": { + "value": 70, + "type": "number", + "category": "strategy", + "description": "做多时 RSI 超过此值则不开多(避免超买区追多)。2026-01-31新增。", + }, + "MAX_CHANGE_PERCENT_FOR_LONG": { + "value": 25, + "type": "number", + "category": "strategy", + "description": "做多时 24h 涨跌幅超过此值则不开多(避免追大涨)。单位:百分比数值,如 25 表示 25%。2026-01-31新增。", + }, + "MIN_RSI_FOR_SHORT": { + "value": 30, + "type": "number", + "category": "strategy", + "description": "做空时 RSI 低于此值则不做空(避免深超卖反弹)。2026-01-31新增。", + }, + "MAX_CHANGE_PERCENT_FOR_SHORT": { + "value": 10, + "type": "number", + "category": "strategy", + "description": "做空时 24h 涨跌幅超过此值则不做空(24h 仍大涨时不做空)。单位:百分比数值。2026-01-31新增。", + }, } for k, meta in ADDITIONAL_STRATEGY_DEFAULTS.items(): if k not in result: diff --git a/backend/config_manager.py b/backend/config_manager.py index ee6f7ae..788fab2 100644 --- a/backend/config_manager.py +++ b/backend/config_manager.py @@ -876,6 +876,11 @@ class ConfigManager: # 动态过滤优化 'BETA_FILTER_ENABLED': eff_get('BETA_FILTER_ENABLED', True), # 大盘共振过滤:BTC/ETH下跌时屏蔽多单 'BETA_FILTER_THRESHOLD': eff_get('BETA_FILTER_THRESHOLD', -0.005), # -0.5%(2026-01-27优化:更敏感地过滤大盘风险,15分钟内跌幅超过0.5%即屏蔽多单) + # RSI / 24h 涨跌幅过滤(避免追高杀跌) + 'MAX_RSI_FOR_LONG': eff_get('MAX_RSI_FOR_LONG', 70), # 做多时 RSI 超过此值则不开多 + 'MAX_CHANGE_PERCENT_FOR_LONG': eff_get('MAX_CHANGE_PERCENT_FOR_LONG', 25), # 做多时 24h 涨跌幅超过此值则不开多 + 'MIN_RSI_FOR_SHORT': eff_get('MIN_RSI_FOR_SHORT', 30), # 做空时 RSI 低于此值则不做空 + 'MAX_CHANGE_PERCENT_FOR_SHORT': eff_get('MAX_CHANGE_PERCENT_FOR_SHORT', 10), # 做空时 24h 涨跌幅超过此值则不做空 # 趋势尾部入场过滤 & 15m 短周期方向过滤开关(由 profile 控制默认值) 'ENTRY_SHORT_INTERVAL': eff_get('ENTRY_SHORT_INTERVAL', '15m'), diff --git a/trading_system/config.py b/trading_system/config.py index ea9c067..b5a21d1 100644 --- a/trading_system/config.py +++ b/trading_system/config.py @@ -237,6 +237,11 @@ def _get_trading_config(): # ===== 动态过滤优化 ===== 'BETA_FILTER_ENABLED': True, # 大盘共振过滤:BTC/ETH下跌时屏蔽多单 'BETA_FILTER_THRESHOLD': -0.005, # -0.5%(2026-01-27优化:更敏感地过滤大盘风险,15分钟内跌幅超过0.5%即屏蔽多单) + # ===== RSI / 24h 涨跌幅过滤(避免追高杀跌)===== + 'MAX_RSI_FOR_LONG': 70, # 做多时 RSI 超过此值则不开多(避免超买区追多) + 'MAX_CHANGE_PERCENT_FOR_LONG': 25, # 做多时 24h 涨跌幅超过此值则不开多(避免追大涨) + 'MIN_RSI_FOR_SHORT': 30, # 做空时 RSI 低于此值则不做空(避免深超卖反弹) + 'MAX_CHANGE_PERCENT_FOR_SHORT': 10, # 做空时 24h 涨跌幅超过此值则不做空(24h 仍大涨时不做空) 'ATR_SPIKE_THRESHOLD': 2.0, # ATR异常激增阈值(当前ATR / 平均ATR) 'SIGNAL_STRENGTH_POSITION_MULTIPLIER': {7: 0.8, 8: 0.9, 9: 1.0, 10: 1.0}, # 信号强度分级:7分80%仓位,8分90%,9-10分100%(快速验证模式:支持7-8分信号) diff --git a/trading_system/strategy.py b/trading_system/strategy.py index 064c10e..4d0b448 100644 --- a/trading_system/strategy.py +++ b/trading_system/strategy.py @@ -202,7 +202,7 @@ class TradingStrategy: 'reason': entry_reason, 'rsi': symbol_info.get('rsi'), 'volume_confirmed': True, # 已通过 _check_volume_confirmation - 'filters_passed': ['only_trending', 'should_trade', 'volume_ok', 'signal_ok'], + 'filters_passed': ['only_trending', 'should_trade', 'volume_ok', 'signal_ok', 'rsi_change_ok'], # rsi_change_ok:已通过做多/做空 RSI 与 24h 涨跌幅过滤 } macd_hist = symbol_info.get('macd', {}).get('histogram') if isinstance(symbol_info.get('macd'), dict) else None if macd_hist is not None: @@ -472,6 +472,46 @@ class TradingStrategy: pass should_trade = signal_strength >= min_signal_strength and direction is not None + # ===== RSI / 24h 涨跌幅过滤:做多不追高、做空不杀跌 ===== + try: + max_rsi_long = config.TRADING_CONFIG.get('MAX_RSI_FOR_LONG', 70) + max_change_long = config.TRADING_CONFIG.get('MAX_CHANGE_PERCENT_FOR_LONG', 25) + min_rsi_short = config.TRADING_CONFIG.get('MIN_RSI_FOR_SHORT', 30) + max_change_short = config.TRADING_CONFIG.get('MAX_CHANGE_PERCENT_FOR_SHORT', 10) + change_pct = symbol_info.get('changePercent') + if change_pct is not None and not isinstance(change_pct, (int, float)): + change_pct = float(change_pct) if change_pct else None + elif change_pct is not None: + change_pct = float(change_pct) + + if should_trade and direction == 'BUY': + if rsi is not None: + try: + rsi_val = float(rsi) + if rsi_val >= max_rsi_long: + should_trade = False + reasons.append(f"❌ 做多RSI过滤:RSI={rsi_val:.1f}≥{max_rsi_long}(超买区不追多)") + except (TypeError, ValueError): + pass + if should_trade and change_pct is not None and change_pct > max_change_long: + should_trade = False + reasons.append(f"❌ 做多涨跌幅过滤:24h涨幅={change_pct:.1f}%>{max_change_long}%(避免追大涨)") + + elif should_trade and direction == 'SELL': + if rsi is not None: + try: + rsi_val = float(rsi) + if rsi_val <= min_rsi_short: + should_trade = False + reasons.append(f"❌ 做空RSI过滤:RSI={rsi_val:.1f}≤{min_rsi_short}(超卖区不追空)") + except (TypeError, ValueError): + pass + if should_trade and change_pct is not None and change_pct > max_change_short: + should_trade = False + reasons.append(f"❌ 做空涨跌幅过滤:24h涨幅={change_pct:.1f}%>{max_change_short}%(24h仍大涨不做空)") + except Exception as e: + logger.debug(f"{symbol} RSI/涨跌幅过滤失败(忽略): {e}") + # ===== 15m 短周期方向过滤:避免“上涨中做空 / 下跌中做多” ===== # 思路: # - 使用最近 N 根 15m K 线的总涨跌幅来确认短期方向