feat: Introduce a web frontend with new city API routes and refactor bot city query logic into a dedicated service.

This commit is contained in:
2569718930@qq.com
2026-03-11 08:46:32 +08:00
parent b1e75d13d8
commit af4bee12f5
15 changed files with 893 additions and 633 deletions
+59 -3
View File
@@ -187,6 +187,53 @@ def _trigger_type_key(alert_payload: Dict[str, Any]) -> str:
return "|".join(trigger_types)
def _evidence_brief(alert_payload: Dict[str, Any]) -> str:
evidence = alert_payload.get("evidence") or {}
if not isinstance(evidence, dict):
return "--"
trigger_summary = evidence.get("trigger_summary") or {}
rules = evidence.get("rules") or {}
market = evidence.get("market") or {}
momentum = rules.get("momentum_spike") or {}
advection = rules.get("advection") or {}
breakthrough = rules.get("forecast_breakthrough") or {}
parts: List[str] = []
trigger_types = trigger_summary.get("trigger_types")
if isinstance(trigger_types, list) and trigger_types:
parts.append(f"triggers={','.join(str(t) for t in trigger_types)}")
slope = momentum.get("slope_30m")
if slope is not None:
parts.append(f"slope_30m={slope}")
lead_delta = advection.get("lead_delta")
if lead_delta is not None:
parts.append(f"lead_delta={lead_delta}")
margin = breakthrough.get("margin")
if margin is not None:
parts.append(f"break_margin={margin}")
edge = market.get("edge_percent")
if edge is not None:
parts.append(f"edge_pct={edge}")
forecast_bucket = market.get("forecast_bucket") or {}
if isinstance(forecast_bucket, dict):
label = str(forecast_bucket.get("label") or "").strip()
yes_buy = forecast_bucket.get("yes_buy")
if label:
parts.append(f"bucket={label}")
if yes_buy is not None:
parts.append(f"yes_buy={yes_buy}")
if not parts:
return "--"
return "; ".join(parts)
def _alert_signature(alert_payload: Dict[str, Any]) -> str:
rules = alert_payload.get("rules") or {}
center_deb = rules.get("ankara_center_deb_hit") or {}
@@ -321,11 +368,13 @@ def _maybe_send_alert(
"severity": alert_payload.get("severity"),
"ts": now_ts,
"active": True,
"evidence": alert_payload.get("evidence"),
}
state.setdefault("by_signature", {})[signature] = now_ts
logger.info(
f"trade alert pushed city={city} severity={alert_payload.get('severity')} "
f"trigger_count={alert_payload.get('trigger_count')} trigger_key={trigger_key}"
f"trigger_count={alert_payload.get('trigger_count')} trigger_key={trigger_key} "
f"evidence={_evidence_brief(alert_payload)}"
)
return True
@@ -340,7 +389,13 @@ def start_trade_alert_push_loop(bot: Any, config: Dict[str, Any]) -> Optional[th
logger.warning("telegram alert push loop skipped: TELEGRAM_CHAT_ID is not set")
return None
interval_sec = max(60, _env_int("TELEGRAM_ALERT_PUSH_INTERVAL_SEC", 300))
mispricing_only = _env_bool("TELEGRAM_ALERT_MISPRICING_ONLY", True)
if mispricing_only:
interval_sec = max(
300, _env_int("TELEGRAM_ALERT_MISPRICING_INTERVAL_SEC", 7200)
)
else:
interval_sec = max(60, _env_int("TELEGRAM_ALERT_PUSH_INTERVAL_SEC", 300))
cooldown_sec = max(interval_sec, _env_int("TELEGRAM_ALERT_PUSH_COOLDOWN_SEC", 1800))
min_trigger_count = max(1, _env_int("TELEGRAM_ALERT_MIN_TRIGGER_COUNT", 2))
min_severity = os.getenv("TELEGRAM_ALERT_MIN_SEVERITY", "medium").strip().lower()
@@ -353,7 +408,8 @@ def start_trade_alert_push_loop(bot: Any, config: Dict[str, Any]) -> Optional[th
except Exception:
logger.exception(f"failed to initialize telegram push state path={state_path}")
logger.info(
f"telegram alert push loop started cities={len(cities)} interval={interval_sec}s "
f"telegram alert push loop started mode={'mispricing-only' if mispricing_only else 'full'} "
f"cities={len(cities)} interval={interval_sec}s "
f"cooldown={cooldown_sec}s min_triggers={min_trigger_count} min_severity={min_severity} "
f"state_path={state_path}"
)