Files
gate_scout_order/onchain_scout_gate/app/key_gate.py
T
2026-05-22 22:15:46 +08:00

100 lines
4.0 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.
"""关键位 5m 硬门控(逻辑自 crypto_monitor_gate _key_hard_checks,使用 Gate K 线)。"""
from __future__ import annotations
from statistics import mean
from .candle_rows import rows_to_ohlcv
def key_hard_checks_from_rows(
rows: list[list[str]],
*,
direction: str,
upper: float,
lower: float,
volume_ma_bars: int = 20,
volume_ratio_min: float = 1.3,
breakout_amp_min_pct: float = 0.03,
breakout_amp_max_pct: float = 0.5,
volume_rank: int | None = None,
volume_rank_total: int = 0,
volume_rank_max: int = 30,
) -> dict:
"""rows:Gate K 线行,最后一根应为最近一根已闭合 5m(调用方负责剔除未闭合)。"""
out: dict = {"ok": False}
_, ah, al, ac, av = rows_to_ohlcv(rows)
vol_lb = max(5, int(volume_ma_bars))
min_need = vol_lb + 3
if len(ac) < min_need:
out["reason"] = f"insufficient_candles have={len(ac)} need>={min_need}"
return out
breakout_close = float(ac[-2])
confirm_close = float(ac[-1])
breakout_high = float(ah[-2])
breakout_low = float(al[-2])
try:
open_b = float(rows[-2][1])
except (IndexError, TypeError, ValueError):
open_b = breakout_close
prev_vol = av[-vol_lb - 2 : -2]
avg20 = mean(prev_vol) if prev_vol else 0.0
vol_break = float(av[-2])
vol_ok = vol_break > avg20 * volume_ratio_min if avg20 > 0 else False
amp_pct = abs(breakout_close - open_b) / open_b * 100 if open_b > 0 else 0.0
amp_ok = (amp_pct > breakout_amp_min_pct) and (amp_pct < breakout_amp_max_pct)
direction = (direction or "long").strip().lower()
edge = float(upper) if direction == "long" else float(lower)
breakout_ok = (breakout_close > float(upper)) if direction == "long" else (breakout_close < float(lower))
confirm_ok_raw = (confirm_close > edge) if direction == "long" else (confirm_close < edge)
amp_ok = amp_ok and breakout_ok
confirm_ok = confirm_ok_raw and breakout_ok
rank_ok = (volume_rank is not None) and (int(volume_rank) <= volume_rank_max)
try:
seg = rows[-48:] if len(rows) >= 48 else rows
hh = max(float(x[2]) for x in seg)
ll = min(float(x[3]) for x in seg)
swing4h_pct = ((hh - ll) / ll * 100.0) if ll > 0 else 0.0
except Exception:
swing4h_pct = 0.0
out.update(
{
"ok": all([vol_ok, amp_ok, breakout_ok, confirm_ok, rank_ok]),
"vol_ok": vol_ok,
"avg20": avg20,
"vol_break": vol_break,
"amp_ok": amp_ok,
"amp_pct": amp_pct,
"breakout_ok": breakout_ok,
"breakout_close": breakout_close,
"confirm_ok": confirm_ok,
"confirm_close": confirm_close,
"edge_price": edge,
"rank": volume_rank,
"rank_total": volume_rank_total,
"rank_ok": rank_ok,
"breakout_high": breakout_high,
"breakout_low": breakout_low,
"swing4h_pct": swing4h_pct,
"direction": direction,
}
)
return out
def key_hard_lines_from_checks(checks: dict, *, volume_ratio_min: float) -> list[str]:
return [
f"量能:{'通过' if checks.get('vol_ok') else '不通过'}(突破K量 {round(float(checks.get('vol_break') or 0), 4)} / 前20均量 {round(float(checks.get('avg20') or 0), 4)},阈值{volume_ratio_min:g}x",
f"突破价位:{'通过' if checks.get('breakout_ok') else '不通过'}(突破K收盘 {round(float(checks.get('breakout_close') or 0), 8)},关键位 {checks.get('edge_price')}",
f"突破K幅度:{'通过' if checks.get('amp_ok') else '不通过'}{round(float(checks.get('amp_pct') or 0), 4)}%,要求0.03%~0.5%",
f"第二根确认:{'通过' if checks.get('confirm_ok') else '不通过'}(确认收盘 {checks.get('confirm_close')},关键位 {checks.get('edge_price')}",
f"日成交量排名:{'通过' if checks.get('rank_ok') else '不通过'}{checks.get('rank')}/{checks.get('rank_total')},要求前30",
]