This commit is contained in:
薇薇安 2026-03-20 08:42:36 +08:00
parent 63e798f003
commit a38f7cf09d
3 changed files with 111 additions and 1 deletions

View File

@ -966,6 +966,13 @@ class ConfigManager:
'TREND_STATE_TTL_SEC': eff_get('TREND_STATE_TTL_SEC', 3600),
'RECO_USE_TREND_ENTRY_FILTER': eff_get('RECO_USE_TREND_ENTRY_FILTER', True),
'RECO_MAX_TREND_MOVE_BEFORE_ENTRY': eff_get('RECO_MAX_TREND_MOVE_BEFORE_ENTRY', 0.04),
# 回撤/区间入场(前低前高)
'ENTRY_PULLBACK_FILTER_ENABLED': eff_get('ENTRY_PULLBACK_FILTER_ENABLED', False),
'ENTRY_PULLBACK_INTERVAL': eff_get('ENTRY_PULLBACK_INTERVAL', None),
'ENTRY_PULLBACK_LOOKBACK_BARS': eff_get('ENTRY_PULLBACK_LOOKBACK_BARS', 24),
'ENTRY_PULLBACK_MIN_BARS': eff_get('ENTRY_PULLBACK_MIN_BARS', 5),
'ENTRY_PULLBACK_MAX_LONG_IN_RANGE': eff_get('ENTRY_PULLBACK_MAX_LONG_IN_RANGE', 0.62),
'ENTRY_PULLBACK_MIN_SHORT_IN_RANGE': eff_get('ENTRY_PULLBACK_MIN_SHORT_IN_RANGE', 0.38),
# 当前交易预设(让 trading_system 能知道是哪种模式)
'TRADING_PROFILE': profile,

View File

@ -363,6 +363,16 @@ DEFAULT_TRADING_CONFIG = {
# 推荐系统使用的最大允许趋势幅度(相对于趋势信号价),默认与自动交易相同或略微更严格
'RECO_MAX_TREND_MOVE_BEFORE_ENTRY': 0.04,
# ===== 回撤/区间入场过滤参考近N根K线前低、前高减少买在区间顶部、卖在区间底部=====
# 与 USE_TREND_ENTRY_FILTER相对「信号价」的追价幅度互补本组用 K 线区间衡量是否已跑到局部极值附近。
'ENTRY_PULLBACK_FILTER_ENABLED': False, # True 启用;建议先 True 观察日志中 [回撤过滤] 跳过比例再调阈值
'ENTRY_PULLBACK_INTERVAL': None, # None=沿用 ENTRY_INTERVAL默认1h可改为 '15m' 等更敏感周期
'ENTRY_PULLBACK_LOOKBACK_BARS': 24, # 统计区间用的 K 线根数(需 >= MIN_BARS
'ENTRY_PULLBACK_MIN_BARS': 5, # 至少多少根有效 K 才启用过滤,不足则跳过本过滤
# 现价在 [区间低,区间高] 中的相对位置 pos∈[0,1]0=贴前低1=贴前高
'ENTRY_PULLBACK_MAX_LONG_IN_RANGE': 0.62, # 做多pos 超过此值则不开(默认拒绝最上约 38% 区域)
'ENTRY_PULLBACK_MIN_SHORT_IN_RANGE': 0.38, # 做空pos 低于此值则不开(默认拒绝最下约 38% 区域)
# ===== 智能入场方案C=====
# 根治方案:默认关闭。关闭后回归“纯限价单模式”(不追价/不市价兜底/未成交撤单跳过)
'SMART_ENTRY_ENABLED': True, # 开启智能入场,提高成交率

View File

@ -8,7 +8,7 @@ import random
import time
import aiohttp
from pathlib import Path
from typing import Dict, List, Optional
from typing import Dict, List, Optional, Tuple
from datetime import datetime
try:
from .binance_client import BinanceClient, AlgoOrderPositionUnavailableError
@ -149,6 +149,46 @@ except Exception:
get_balance_from_cache = lambda: None
def _pullback_range_metrics_from_klines(
klines: Optional[List],
current_price: float,
) -> Optional[Tuple[float, float, float]]:
"""
用近若干根 K 线的最高价/最低价构成区间衡量当前价在区间内的相对位置
返回 (recent_low, recent_high, position_01)其中 position_01[0,1]
- 0 表示贴在区间下沿接近前低一侧
- 1 表示贴在区间上沿接近前高一侧
多单若 position_01 过大说明价格已跑到区间顶部容易一回撤就扫损
空单若 position_01 过小同理接近区间下沿追空风险大
"""
if not klines or current_price is None:
return None
try:
cur = float(current_price)
except (TypeError, ValueError):
return None
if cur <= 0:
return None
highs: List[float] = []
lows: List[float] = []
for k in klines:
try:
highs.append(float(k[2]))
lows.append(float(k[3]))
except (IndexError, TypeError, ValueError):
continue
if len(highs) < 2:
return None
hi = max(highs)
lo = min(lows)
if hi <= lo:
return None
pos = (cur - lo) / (hi - lo)
return (lo, hi, pos)
class PositionManager:
"""仓位管理类"""
@ -425,6 +465,59 @@ class PositionManager:
except Exception as e:
logger.debug(f"{symbol} 趋势入场过滤时出错(忽略,按正常逻辑继续): {e}")
# ===== 回撤/区间入场过滤(参考近 N 根 K 线的前低/前高,避免买在区间顶部、卖在区间底部)=====
try:
pb_enabled = bool(config.TRADING_CONFIG.get("ENTRY_PULLBACK_FILTER_ENABLED", False))
if pb_enabled:
min_bars = max(3, int(config.TRADING_CONFIG.get("ENTRY_PULLBACK_MIN_BARS", 5) or 5))
lookback = max(min_bars, int(config.TRADING_CONFIG.get("ENTRY_PULLBACK_LOOKBACK_BARS", 24) or 24))
interval_pb = config.TRADING_CONFIG.get("ENTRY_PULLBACK_INTERVAL") or config.TRADING_CONFIG.get(
"ENTRY_INTERVAL", "1h"
)
max_long_pos = float(config.TRADING_CONFIG.get("ENTRY_PULLBACK_MAX_LONG_IN_RANGE", 0.62) or 0.62)
min_short_pos = float(config.TRADING_CONFIG.get("ENTRY_PULLBACK_MIN_SHORT_IN_RANGE", 0.38) or 0.38)
max_long_pos = min(1.0, max(0.0, max_long_pos))
min_short_pos = min(1.0, max(0.0, min_short_pos))
if min_short_pos > max_long_pos:
min_short_pos, max_long_pos = max_long_pos, min_short_pos
rt = None
try:
rt = self.client.get_realtime_price(symbol)
except Exception:
rt = None
cur_pb = float(rt or estimated_entry_price)
bars_pb = klines if klines and len(klines) >= min_bars else None
if not bars_pb:
try:
fetched = await self.client.get_klines(
symbol=symbol, interval=str(interval_pb), limit=lookback
)
bars_pb = fetched if fetched and len(fetched) >= min_bars else None
except Exception as e:
logger.debug(f"{symbol} 回撤过滤拉取K线失败跳过该过滤: {e}")
bars_pb = None
metrics = _pullback_range_metrics_from_klines(bars_pb, cur_pb)
if metrics is not None:
lo_pb, hi_pb, pos_pb = metrics
es = estimated_side.upper()
if es == "BUY" and pos_pb > max_long_pos:
logger.info(
f"{symbol} [回撤过滤] 做多跳过:现价={cur_pb:.6f} 在近{len(bars_pb)}{interval_pb}区间"
f"[{lo_pb:.6f},{hi_pb:.6f}] 中位置={pos_pb:.2f} > 上限{max_long_pos:.2f}(接近区间上沿/前高,易回撤扫损)"
)
return None
if es == "SELL" and pos_pb < min_short_pos:
logger.info(
f"{symbol} [回撤过滤] 做空跳过:现价={cur_pb:.6f} 在近{len(bars_pb)}{interval_pb}区间"
f"[{lo_pb:.6f},{hi_pb:.6f}] 中位置={pos_pb:.2f} < 下限{min_short_pos:.2f}(接近区间下沿/前低,易反弹扫损)"
)
return None
except Exception as e:
logger.debug(f"{symbol} 回撤/区间入场过滤异常(忽略,继续开仓流程): {e}")
# 估算止损价格(用于固定风险计算)
estimated_stop_loss = None
if atr and atr > 0: