fix: ECR-004 威科夫区间评分硬化与 VP 绘图减负(已审)

评分选 TR、阶段最小跨度、elements_only 门闩、Top-8 VP;无币种独立参数。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-06 18:46:08 +08:00
co-authored by Cursor
parent ac6be80278
commit d3188ca83c
19 changed files with 275 additions and 89 deletions
+23 -3
View File
@@ -1,4 +1,4 @@
"""交易区间检测:ATR 容差下近期震荡箱。"""
"""交易区间检测:ATR 容差下按评分选取近期震荡箱。"""
from __future__ import annotations
from typing import Any, Dict, Optional
@@ -23,6 +23,20 @@ def _atr(df: pd.DataFrame, period: int = 14) -> pd.Series:
return tr.rolling(period, min_periods=max(3, period // 2)).mean()
def _score_segment(
length: int,
near_hi: int,
near_lo: int,
inside: float,
width: float,
atr: float,
) -> float:
"""触边密度 + 箱内比例 − 相对宽度;弱奖励长度以免只追最长。"""
touch_density = (near_hi + near_lo) / float(max(length, 1))
width_pen = (width / atr) if atr > 0 else width
return touch_density * 50.0 + float(inside) * 30.0 - width_pen * 3.0 + min(length / 40.0, 2.0)
def detect_trading_range(
df: pd.DataFrame,
lookback: int = 120,
@@ -33,6 +47,7 @@ def detect_trading_range(
"""
在最近 lookback 根内寻找高低点波动受控的连续段作为交易区间。
尾部预留 tail_reserve 根用于事件(Spring/SOS),不参与箱体边界计算。
在硬门槛之上按评分取最优段(非仅最长窗口)。
"""
if df is None or len(df) < min_bars + 5:
return None
@@ -54,6 +69,7 @@ def detect_trading_range(
last_atr = float(core["close"].iloc[-1]) * 0.01
best = None
best_score = float("-inf")
cn = len(core)
for length in range(min(cn, lookback), min_bars - 1, -4):
seg = core.iloc[-length:]
@@ -67,14 +83,18 @@ def detect_trading_range(
near_lo = int((seg["low"] <= lo + tol).sum())
if near_hi < 2 or near_lo < 2:
continue
inside = ((seg["close"] >= lo - tol) & (seg["close"] <= hi + tol)).mean()
inside = float(((seg["close"] >= lo - tol) & (seg["close"] <= hi + tol)).mean())
if inside < 0.75:
continue
score = _score_segment(length, near_hi, near_lo, inside, width, last_atr)
if score <= best_score:
continue
start_i = cn - length
end_i = cn - 1
mid = (hi + lo) / 2.0
last_c = float(work["close"].iloc[-1])
active = (lo - tol * 1.5) <= last_c <= (hi + tol * 1.5)
best_score = score
best = {
"start_idx": int(start_i),
"end_idx": int(end_i),
@@ -85,8 +105,8 @@ def detect_trading_range(
"atr": last_atr,
"tol": tol,
"bars": int(length),
"score": float(score),
}
break
if best is None:
return None