feat(web): 增量自动刷新、结构区修复与默认指标/周期
自动刷新常态只拉 recent 尾部 K,每 1 分钟全量重算缠论;修复结构区缓存导入;默认指标/4h·1h·15m/近30天;同步 ECR-009 screener 相关改动。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+48
-24
@@ -1,4 +1,4 @@
|
||||
"""Scan pipeline: load local frames → engines → store."""
|
||||
"""Scan pipeline: load local frames → engines → store (per TF combo)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -6,25 +6,27 @@ import json
|
||||
import logging
|
||||
from datetime import date, datetime, timezone
|
||||
|
||||
from crypto_wyckoff.combos import ROLE_HIGH, ROLE_LOW, ROLE_MID, get_combo, lookback_for
|
||||
from crypto_wyckoff.cycle import CycleEngine
|
||||
from crypto_wyckoff.decision import DecisionEngine
|
||||
from crypto_wyckoff.domain_models import WyckoffScanRow
|
||||
from crypto_wyckoff.event import EventEngine
|
||||
from crypto_wyckoff.features import FeatureEngine
|
||||
from crypto_wyckoff.io import LOOKBACK, TF_LIST, load_frame
|
||||
from crypto_wyckoff.io import load_frame
|
||||
from crypto_wyckoff.phase import PhaseEngine
|
||||
from crypto_wyckoff.plan import PlanEngine
|
||||
from crypto_wyckoff.signal import SignalEngine
|
||||
from crypto_wyckoff.store import upsert_row
|
||||
from crypto_wyckoff.symbols_cn import display_name_cn
|
||||
from crypto_wyckoff.version import WYCKOFF_ENGINE_VERSION
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def analyze_symbol(
|
||||
daily_frame,
|
||||
weekly_frame,
|
||||
monthly_frame,
|
||||
low_frame,
|
||||
mid_frame,
|
||||
high_frame,
|
||||
*,
|
||||
feature_eng: FeatureEngine,
|
||||
cycle_eng: CycleEngine,
|
||||
@@ -34,18 +36,22 @@ def analyze_symbol(
|
||||
decision_eng: DecisionEngine,
|
||||
plan_eng: PlanEngine,
|
||||
) -> dict:
|
||||
f_d = feature_eng.run(daily_frame, "1d")
|
||||
f_w = feature_eng.run(weekly_frame, "1w")
|
||||
f_m = feature_eng.run(monthly_frame, "1M")
|
||||
"""Run engines with D/W/M *role* aliases so existing rules match.
|
||||
|
||||
c_m = cycle_eng.run(f_m, "1M")
|
||||
c_w = cycle_eng.run(f_w, "1w")
|
||||
Frames may be any TF combo (e.g. 1h/4h/8h); rules still see 1d/1w/1M roles.
|
||||
"""
|
||||
f_d = feature_eng.run(low_frame, ROLE_LOW)
|
||||
f_w = feature_eng.run(mid_frame, ROLE_MID)
|
||||
f_m = feature_eng.run(high_frame, ROLE_HIGH)
|
||||
|
||||
p_w = phase_eng.run(c_w, f_w, "1w")
|
||||
p_d = phase_eng.run(c_w, f_d, "1d")
|
||||
c_m = cycle_eng.run(f_m, ROLE_HIGH)
|
||||
c_w = cycle_eng.run(f_w, ROLE_MID)
|
||||
|
||||
e_w = event_eng.run(c_w, p_w, f_w, "1w")
|
||||
e_d = event_eng.run(c_w, p_d, f_d, "1d")
|
||||
p_w = phase_eng.run(c_w, f_w, ROLE_MID)
|
||||
p_d = phase_eng.run(c_w, f_d, ROLE_LOW)
|
||||
|
||||
e_w = event_eng.run(c_w, p_w, f_w, ROLE_MID)
|
||||
e_d = event_eng.run(c_w, p_d, f_d, ROLE_LOW)
|
||||
|
||||
s_d = signal_eng.run(e_d, p_d)
|
||||
decision = decision_eng.run(c_m, c_w, p_w, e_w, e_d, s_d)
|
||||
@@ -59,7 +65,14 @@ def analyze_symbol(
|
||||
}
|
||||
|
||||
|
||||
def _to_row(trade_date: date, symbol: str, result: dict) -> WyckoffScanRow:
|
||||
def _to_row(
|
||||
trade_date: date,
|
||||
symbol: str,
|
||||
result: dict,
|
||||
*,
|
||||
combo_id: str,
|
||||
combo_label: str,
|
||||
) -> WyckoffScanRow:
|
||||
d = result["decision"]
|
||||
p = result["plan"]
|
||||
c_m, c_w, p_w = result["c_m"], result["c_w"], result["p_w"]
|
||||
@@ -67,6 +80,8 @@ def _to_row(trade_date: date, symbol: str, result: dict) -> WyckoffScanRow:
|
||||
f_d, f_w, f_m = result["f_d"], result["f_w"], result["f_m"]
|
||||
|
||||
snapshot = {
|
||||
"combo_id": combo_id,
|
||||
"combo_label": combo_label,
|
||||
"daily": {k: f_d.payload.get(k) for k in (
|
||||
"ma20", "ma60", "ma120", "atr", "adx", "volume_ratio",
|
||||
"range_high", "range_low", "swing_high", "swing_low", "close",
|
||||
@@ -82,7 +97,7 @@ def _to_row(trade_date: date, symbol: str, result: dict) -> WyckoffScanRow:
|
||||
return WyckoffScanRow(
|
||||
trade_date=trade_date,
|
||||
ts_code=symbol,
|
||||
name=symbol,
|
||||
name=display_name_cn(symbol),
|
||||
industry="crypto",
|
||||
engine_version=WYCKOFF_ENGINE_VERSION,
|
||||
m_cycle=c_m.payload.get("cycle", "Unknown"),
|
||||
@@ -120,6 +135,7 @@ def _to_row(trade_date: date, symbol: str, result: dict) -> WyckoffScanRow:
|
||||
feature_snapshot_json=json.dumps(snapshot, ensure_ascii=False),
|
||||
markers_json=json.dumps(markers, ensure_ascii=False),
|
||||
scanned_at=datetime.now(timezone.utc),
|
||||
combo_id=combo_id,
|
||||
)
|
||||
|
||||
|
||||
@@ -141,17 +157,25 @@ def _engines():
|
||||
return _ENGINES
|
||||
|
||||
|
||||
def analyze_and_store(symbol: str, trade_date: date | None = None) -> WyckoffScanRow | None:
|
||||
def analyze_and_store(
|
||||
symbol: str,
|
||||
trade_date: date | None = None,
|
||||
*,
|
||||
combo_id: str | None = None,
|
||||
) -> WyckoffScanRow | None:
|
||||
eng = _engines()
|
||||
daily = load_frame(symbol, "1d", LOOKBACK["1d"])
|
||||
weekly = load_frame(symbol, "1w", LOOKBACK["1w"])
|
||||
monthly = load_frame(symbol, "1M", LOOKBACK["1M"])
|
||||
if daily is None or len(daily) < 40:
|
||||
combo = get_combo(combo_id)
|
||||
low_tf, mid_tf, high_tf = combo["low"], combo["mid"], combo["high"]
|
||||
|
||||
low = load_frame(symbol, low_tf, lookback_for(low_tf))
|
||||
mid = load_frame(symbol, mid_tf, lookback_for(mid_tf))
|
||||
high = load_frame(symbol, high_tf, lookback_for(high_tf))
|
||||
if low is None or len(low) < 40:
|
||||
return None
|
||||
result = analyze_symbol(daily, weekly, monthly, **eng)
|
||||
result = analyze_symbol(low, mid, high, **eng)
|
||||
td = trade_date or (
|
||||
daily.trade_dates[-1] if daily.trade_dates else datetime.now(timezone.utc).date()
|
||||
low.trade_dates[-1] if low.trade_dates else datetime.now(timezone.utc).date()
|
||||
)
|
||||
row = _to_row(td, symbol, result)
|
||||
row = _to_row(td, symbol, result, combo_id=combo["id"], combo_label=combo["label"])
|
||||
upsert_row(row)
|
||||
return row
|
||||
|
||||
Reference in New Issue
Block a user