diff --git a/trading_system/config.py b/trading_system/config.py index c518688..ba2231a 100644 --- a/trading_system/config.py +++ b/trading_system/config.py @@ -253,6 +253,17 @@ def _get_trading_config(): # 是否允许 4H 趋势为 neutral 时自动交易;默认不允许(震荡最易扫损) 'AUTO_TRADE_ALLOW_4H_NEUTRAL': False, + # ===== 趋势入场过滤(防止追在半山腰)===== + # 是否启用基于趋势状态的入场过滤: + # - 扫描阶段会为每个强信号交易对写入一份“趋势状态”到缓存(Redis) + # - 开仓时根据当前价格相对于信号价格的偏移,过滤“过晚追价”的入场 + 'USE_TREND_ENTRY_FILTER': True, + # 在信号方向上允许的最大累计趋势幅度(相对于信号价),超过则认为“时机太晚”,不再入场 + # 例如:0.05 表示价格沿趋势方向已经走了 5% 以上还没上车,则跳过本轮机会 + 'MAX_TREND_MOVE_BEFORE_ENTRY': 0.05, + # 趋势状态缓存的 TTL(秒),用于控制一轮趋势的“有效期” + 'TREND_STATE_TTL_SEC': 3600, + # ===== 智能入场(方案C)===== # 根治方案:默认关闭。关闭后回归“纯限价单模式”(不追价/不市价兜底/未成交撤单跳过) 'SMART_ENTRY_ENABLED': True, # 开启智能入场,提高成交率 diff --git a/trading_system/market_scanner.py b/trading_system/market_scanner.py index 52f0edb..17a3982 100644 --- a/trading_system/market_scanner.py +++ b/trading_system/market_scanner.py @@ -450,7 +450,40 @@ class MarketScanner: # 强度上限归一到 0-10 signal_strength = max(0, min(int(signal_strength), 10)) - + + # ===== 趋势状态缓存(用于后续“入场时机过滤”)===== + try: + # 只有在方向明确且信号强度达到最小门槛时,才记录趋势状态 + min_strength = int(config.TRADING_CONFIG.get('MIN_SIGNAL_STRENGTH', 7) or 7) + use_trend_filter = bool(config.TRADING_CONFIG.get('USE_TREND_ENTRY_FILTER', False)) + if use_trend_filter and direction and signal_strength >= min_strength: + trend_state_key = f"trend_state:{symbol}" + trend_state_value = { + 'symbol': symbol, + 'direction': direction, # BUY / SELL + 'signal_strength': int(signal_strength), + 'marketRegime': market_regime, + 'trend_4h': trend_4h, + # 使用技术分析用的收盘价作为“信号价格”,同时附带 24h ticker 价格 + 'signal_price': float(current_price), + 'ticker_price': float(ticker_price) if ticker_price is not None else None, + 'ema20': float(ema20) if ema20 is not None else None, + 'ema50': float(ema50) if ema50 is not None else None, + 'ema20_4h': float(ema20_4h) if ema20_4h is not None else None, + 'price_4h': float(price_4h) if price_4h is not None else None, + 'last_kline_time': last_kline_time, + 'created_at_ts': int(ticker_ts or 0), + } + ttl_sec = int(config.TRADING_CONFIG.get('TREND_STATE_TTL_SEC', 3600) or 3600) + if self.client and getattr(self.client, "redis_cache", None): + try: + await self.client.redis_cache.set(trend_state_key, trend_state_value, ttl=ttl_sec) + logger.debug(f"{symbol} 趋势状态已缓存: dir={direction}, strength={signal_strength}, price={current_price:.6f}") + except Exception as e: + logger.debug(f"{symbol} 缓存趋势状态失败: {e}") + except Exception as e: + logger.debug(f"{symbol} 处理趋势状态缓存时出错: {e}") + return { 'symbol': symbol, # 技术分析使用的价格(K线收盘价) diff --git a/trading_system/position_manager.py b/trading_system/position_manager.py index ccb9cfc..4d661a7 100644 --- a/trading_system/position_manager.py +++ b/trading_system/position_manager.py @@ -193,20 +193,69 @@ class PositionManager: # 判断是否应该交易 if not await self.risk_manager.should_trade(symbol, change_percent): return None - + # 设置杠杆 await self.client.set_leverage(symbol, leverage) - + # 计算仓位大小(传入实际使用的杠杆) # ⚠️ 优化:先估算止损价格,用于固定风险百分比计算 logger.info(f"开始为 {symbol} 计算仓位大小...") - + # 获取当前价格用于估算止损 ticker = await self.client.get_ticker_24h(symbol) if not ticker: return None estimated_entry_price = ticker['price'] estimated_side = trade_direction if trade_direction else ('BUY' if change_percent > 0 else 'SELL') + + # ===== 趋势入场过滤(防止在趋势尾部追价)===== + try: + use_trend_filter = bool(config.TRADING_CONFIG.get("USE_TREND_ENTRY_FILTER", False)) + if use_trend_filter and getattr(self.client, "redis_cache", None): + trend_state_key = f"trend_state:{symbol}" + trend_state = await self.client.redis_cache.get(trend_state_key) + + if trend_state: + trend_dir = (trend_state.get("direction") or "").upper() + signal_price = float(trend_state.get("signal_price") or 0) or None + + if signal_price and trend_dir in ("BUY", "SELL"): + # 使用更实时的价格(如有),否则使用估算价 + realtime_price = None + try: + realtime_price = self.client.get_realtime_price(symbol) + except Exception: + realtime_price = None + current_price = float(realtime_price or estimated_entry_price) + + max_move = float(config.TRADING_CONFIG.get("MAX_TREND_MOVE_BEFORE_ENTRY", 0.05) or 0.05) + + too_late = False + move_pct = 0.0 + + if trend_dir == "BUY": + # 做多:如果当前价相比信号价已经上涨超过阈值,认为追在高位 + if current_price > signal_price: + move_pct = (current_price - signal_price) / signal_price + if move_pct > max_move: + too_late = True + elif trend_dir == "SELL": + # 做空:如果当前价相比信号价已经下跌超过阈值,认为追在低位 + if current_price < signal_price: + move_pct = (signal_price - current_price) / signal_price + if move_pct > max_move: + too_late = True + + # 只有当当前入场方向与趋势方向一致时,才应用“太晚不追”规则 + if estimated_side.upper() == trend_dir and too_late: + logger.info( + f"{symbol} [入场过滤] 趋势方向={trend_dir}, 信号价={signal_price:.6f}, " + f"当前价={current_price:.6f}, 累计趋势幅度={move_pct*100:.2f}%>允许上限={max_move*100:.2f}%," + f"认为本轮趋势入场时机已错过,跳过自动开仓" + ) + return None + except Exception as e: + logger.debug(f"{symbol} 趋势入场过滤时出错(忽略,按正常逻辑继续): {e}") # 估算止损价格(用于固定风险计算) estimated_stop_loss = None