Files
gate_scout_order/onchain_scout_gate/app/btc_regime.py
T
2026-05-16 22:25:48 +08:00

109 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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, cts,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),
},
)