首次上传
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from statistics import mean
|
||||
|
||||
|
||||
@dataclass
|
||||
class BtcDailyGateResult:
|
||||
"""BTC 日线辅助门控(非产品主线):下跌不扫山寨,其它仍扫 —— 仅 downtrend 时关闭本轮 alt K 线请求。"""
|
||||
|
||||
allow_alt_scan: bool
|
||||
regime: str # sideways | downtrend | neutral_or_up | unknown
|
||||
reason: str
|
||||
metrics: dict = field(default_factory=dict)
|
||||
|
||||
|
||||
def _rows_to_hlc(rows: list[list[str]]) -> tuple[list[float], list[float], list[float]]:
|
||||
"""与行情 K 线行对齐:h, l, c(ts,o,h,l,c,...)。"""
|
||||
h, l_, c = [], [], []
|
||||
for item in rows:
|
||||
if len(item) < 6:
|
||||
continue
|
||||
h.append(float(item[2]))
|
||||
l_.append(float(item[3]))
|
||||
c.append(float(item[4]))
|
||||
return h, l_, c
|
||||
|
||||
|
||||
def evaluate_btc_daily_gate(
|
||||
btc_1d_rows: list[list[str]],
|
||||
*,
|
||||
sideways_lookback_days: int = 14,
|
||||
sideways_max_range_pct: float = 10.0,
|
||||
min_bars: int = 30,
|
||||
) -> BtcDailyGateResult:
|
||||
"""
|
||||
原则:下跌不扫,其它都扫。
|
||||
|
||||
- 下跌(唯一不扫):非横盘,且收盘低于近 20 日收盘均线,且该均线相对前一段走低。
|
||||
- 其余(横盘、上涨、宽幅震荡、数据不足 unknown 等):一律允许扫山寨。
|
||||
"""
|
||||
ah, al, ac = _rows_to_hlc(btc_1d_rows)
|
||||
|
||||
if len(ac) < min_bars:
|
||||
return BtcDailyGateResult(
|
||||
allow_alt_scan=True,
|
||||
regime="unknown",
|
||||
reason=f"insufficient_1d_bars have={len(ac)} need>={min_bars}, gate skipped",
|
||||
metrics={"have": len(ac), "min_bars": min_bars},
|
||||
)
|
||||
|
||||
lb = max(5, min(sideways_lookback_days, len(ah) - 1))
|
||||
window_h = ah[-lb:]
|
||||
window_l = al[-lb:]
|
||||
range_high = max(window_h)
|
||||
range_low = min(window_l)
|
||||
mid = (range_high + range_low) / 2 if range_high > range_low else 0.0
|
||||
range_pct = ((range_high - range_low) / mid) * 100 if mid > 0 else 999.0
|
||||
|
||||
sma_curr = mean(ac[-20:])
|
||||
sma_prev = mean(ac[-26:-6]) if len(ac) >= 26 else sma_curr
|
||||
|
||||
last_close = ac[-1]
|
||||
is_sideways = range_pct <= sideways_max_range_pct
|
||||
|
||||
if is_sideways:
|
||||
return BtcDailyGateResult(
|
||||
allow_alt_scan=True,
|
||||
regime="sideways",
|
||||
reason="btc_daily_sideways",
|
||||
metrics={
|
||||
"range_lookback_days": lb,
|
||||
"range_pct": round(range_pct, 4),
|
||||
"sideways_max_range_pct": sideways_max_range_pct,
|
||||
"last_close": last_close,
|
||||
"sma20": round(sma_curr, 6),
|
||||
"sma20_prev_block": round(sma_prev, 6),
|
||||
},
|
||||
)
|
||||
|
||||
is_downtrend = last_close < sma_curr and sma_curr < sma_prev
|
||||
if is_downtrend:
|
||||
return BtcDailyGateResult(
|
||||
allow_alt_scan=False,
|
||||
regime="downtrend",
|
||||
reason="btc_daily_downtrend_below_falling_sma20",
|
||||
metrics={
|
||||
"range_lookback_days": lb,
|
||||
"range_pct": round(range_pct, 4),
|
||||
"sideways_max_range_pct": sideways_max_range_pct,
|
||||
"last_close": last_close,
|
||||
"sma20": round(sma_curr, 6),
|
||||
"sma20_prev_block": round(sma_prev, 6),
|
||||
},
|
||||
)
|
||||
|
||||
return BtcDailyGateResult(
|
||||
allow_alt_scan=True,
|
||||
regime="neutral_or_up",
|
||||
reason="btc_not_sideways_not_downtrend_gate_open",
|
||||
metrics={
|
||||
"range_lookback_days": lb,
|
||||
"range_pct": round(range_pct, 4),
|
||||
"last_close": last_close,
|
||||
"sma20": round(sma_curr, 6),
|
||||
"sma20_prev_block": round(sma_prev, 6),
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user