Improve key monitor form with bar period, box direction, and labeled fields.

Match order-monitor layout; persist bar_period and enforce upper-direction filter for box breakouts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-29 07:24:36 +08:00
parent 169136dd4a
commit bfb1b95471
6 changed files with 187 additions and 68 deletions
+49 -10
View File
@@ -27,8 +27,38 @@ ZONE_TYPES = (TYPE_ZONE, "关键阻力位", "关键支撑位")
ALERT_MAX_PUSH = 3
ALERT_INTERVAL_SEC = 300
SL_TICK_BUFFER = 2
BAR_PERIOD = "5m"
BAR_MINUTES = 5
DEFAULT_BAR_PERIOD = "5m"
PERIOD_MINUTES_MAP = {
"1m": 1, "2m": 2, "3m": 3, "5m": 5, "15m": 15, "30m": 30,
"1h": 60, "2h": 120, "4h": 240, "d": 1440, "1d": 1440,
}
def key_monitor_periods() -> list[dict[str, str]]:
"""关键位监控可选 K 线周期(触发用)。"""
from kline_chart import MARKET_PERIODS
allowed = frozenset({"5m", "15m", "30m", "1h", "2h", "4h", "d"})
return [p for p in MARKET_PERIODS if p["key"] in allowed]
def normalize_bar_period(raw: str) -> str:
valid = {p["key"] for p in key_monitor_periods()}
k = (raw or DEFAULT_BAR_PERIOD).strip()
return k if k in valid else DEFAULT_BAR_PERIOD
def bar_period_label(key: str) -> str:
k = normalize_bar_period(key)
for p in key_monitor_periods():
if p["key"] == k:
return p["label"]
return k
def bar_period_minutes(period: str) -> int:
return PERIOD_MINUTES_MAP.get(normalize_bar_period(period), 5)
def normalize_monitor_type(raw: str) -> str:
@@ -95,14 +125,19 @@ def _parse_bar_time(raw: str) -> Optional[datetime]:
return None
def last_closed_bar(bars: list[dict], now: Optional[datetime] = None) -> Optional[dict]:
def last_closed_bar(
bars: list[dict],
period_minutes: int = 5,
now: Optional[datetime] = None,
) -> Optional[dict]:
"""取最近一根已收盘 K 线。"""
dnow = now or datetime.now(TZ)
mins = max(1, int(period_minutes or 5))
for bar in reversed(bars or []):
dt = _parse_bar_time(str(bar.get("time") or ""))
if not dt:
continue
bar_end = dt + timedelta(minutes=BAR_MINUTES)
bar_end = dt + timedelta(minutes=mins)
if dnow >= bar_end:
return bar
return None
@@ -116,24 +151,26 @@ def detect_break_side(close: float, upper: float, lower: float) -> Optional[str]
return None
def fetch_closed_5m_bar(
def fetch_closed_bar(
sym: str,
period: str,
*,
db_path: str,
trading_mode: str,
) -> Optional[dict]:
p = normalize_bar_period(period)
try:
data = fetch_market_klines(
sym,
BAR_PERIOD,
p,
db_path=db_path,
trading_mode=trading_mode,
prefer_ctp=True,
)
bars = data.get("bars") or []
return last_closed_bar(bars)
return last_closed_bar(bars, bar_period_minutes(p))
except Exception as exc:
logger.debug("key monitor kline %s: %s", sym, exc)
logger.debug("key monitor kline %s %s: %s", sym, p, exc)
return None
@@ -201,9 +238,10 @@ def format_auto_breakout_msg(
break_label, _ = break_direction_label(break_side)
dir_cn = "做多" if direction == "long" else "做空"
rr = float(row.get("risk_reward") or 2)
period_label = bar_period_label(row.get("bar_period") or DEFAULT_BAR_PERIOD)
lines = [
f"{'' if ok else ''} {name} {typ}自动单",
f"5m 收盘:{bar_time}",
f"{period_label} 收盘:{bar_time}",
f"🎯 {break_label} · {trade_mode} · {dir_cn}",
f"💹 入场:{entry:g} 止损:{sl:g} 止盈:{tp:g}(盈亏比 {rr:g}",
f"📦 手数:{lots}",
@@ -333,7 +371,8 @@ def run_key_monitor_check(
archive_monitor(conn, pid)
continue
bar = fetch_closed_5m_bar(sym, db_path=db_path, trading_mode=mode)
bar_period = normalize_bar_period(row.get("bar_period") or DEFAULT_BAR_PERIOD)
bar = fetch_closed_bar(sym, bar_period, db_path=db_path, trading_mode=mode)
if not bar:
continue
bar_time = str(bar.get("time") or "")[:19]