feat(web): 增量自动刷新、结构区修复与默认指标/周期
自动刷新常态只拉 recent 尾部 K,每 1 分钟全量重算缠论;修复结构区缓存导入;默认指标/4h·1h·15m/近30天;同步 ECR-009 screener 相关改动。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+28
-29
@@ -1,73 +1,70 @@
|
||||
"""Background 60s tip-update + rescan scheduler."""
|
||||
"""Background tip + scan scheduler for crypto wyckoff (all enabled combos)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from crypto_wyckoff.combos import all_tfs_for_combos, list_combos
|
||||
from crypto_wyckoff.io import (
|
||||
TF_LIST,
|
||||
backfill_symbol,
|
||||
bar_count,
|
||||
fetch_symbols_from_provider,
|
||||
tip_update_symbol,
|
||||
)
|
||||
from crypto_wyckoff.pipeline import analyze_and_store
|
||||
from crypto_wyckoff.version import WYCKOFF_ENGINE_VERSION
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_lock = threading.Lock()
|
||||
_status: dict[str, Any] = {
|
||||
_thread: threading.Thread | None = None
|
||||
_stop = threading.Event()
|
||||
_status: dict = {
|
||||
"running": False,
|
||||
"last_tick_at": None,
|
||||
"last_error": None,
|
||||
"symbols_total": 0,
|
||||
"symbols_scanned": 0,
|
||||
"backfill_done": False,
|
||||
"engine_version": WYCKOFF_ENGINE_VERSION,
|
||||
"tick_interval_sec": 60,
|
||||
"backfill_done": False,
|
||||
}
|
||||
_stop = threading.Event()
|
||||
_thread: threading.Thread | None = None
|
||||
|
||||
|
||||
def get_status() -> dict[str, Any]:
|
||||
with _lock:
|
||||
return dict(_status)
|
||||
_status_lock = threading.Lock()
|
||||
|
||||
|
||||
def _set(**kwargs):
|
||||
with _lock:
|
||||
with _status_lock:
|
||||
_status.update(kwargs)
|
||||
|
||||
|
||||
def get_status() -> dict:
|
||||
with _status_lock:
|
||||
return dict(_status)
|
||||
|
||||
|
||||
def run_tick(max_symbols: int | None = None, force_rescan: bool = False) -> dict:
|
||||
"""One cycle: refresh symbols, tip-update, analyze changed (or all if force)."""
|
||||
"""One cycle: refresh symbols, tip-update, analyze each combo."""
|
||||
symbols = fetch_symbols_from_provider()
|
||||
if max_symbols:
|
||||
symbols = symbols[:max_symbols]
|
||||
combos = list_combos()
|
||||
tfs = all_tfs_for_combos(combos)
|
||||
_set(symbols_total=len(symbols), running=True, last_error=None)
|
||||
scanned = 0
|
||||
errors = 0
|
||||
changed_n = 0
|
||||
|
||||
# Lazy backfill: ensure min bars
|
||||
for i, sym in enumerate(symbols):
|
||||
try:
|
||||
if bar_count(sym, "1d") < 40:
|
||||
backfill_symbol(sym, TF_LIST)
|
||||
tip_changed = tip_update_symbol(sym, TF_LIST)
|
||||
# Prefer low-TF of first combo for "enough history" gate
|
||||
low0 = combos[0]["low"] if combos else "1d"
|
||||
if bar_count(sym, low0) < 40:
|
||||
backfill_symbol(sym, tfs)
|
||||
tip_changed = tip_update_symbol(sym, tfs)
|
||||
if tip_changed:
|
||||
changed_n += 1
|
||||
if force_rescan or tip_changed or bar_count(sym, "1d") >= 40:
|
||||
# Always rescan on first pass after backfill; tip change triggers update
|
||||
if force_rescan or tip_changed or True:
|
||||
# Tip every minute: always re-analyze to refresh forming-bar features
|
||||
row = analyze_and_store(sym)
|
||||
if force_rescan or tip_changed:
|
||||
for combo in combos:
|
||||
row = analyze_and_store(sym, combo_id=combo["id"])
|
||||
if row:
|
||||
scanned += 1
|
||||
except Exception as e:
|
||||
@@ -90,11 +87,12 @@ def run_tick(max_symbols: int | None = None, force_rescan: bool = False) -> dict
|
||||
"scanned": scanned,
|
||||
"changed_tips": changed_n,
|
||||
"errors": errors,
|
||||
"combos": [c["id"] for c in combos],
|
||||
"tfs": tfs,
|
||||
}
|
||||
|
||||
|
||||
def _loop(interval: int, max_symbols: int | None):
|
||||
# First tick: force full rescan after tip/backfill
|
||||
try:
|
||||
run_tick(max_symbols=max_symbols, force_rescan=True)
|
||||
except Exception as e:
|
||||
@@ -102,7 +100,8 @@ def _loop(interval: int, max_symbols: int | None):
|
||||
_set(last_error=str(e), running=False)
|
||||
while not _stop.wait(interval):
|
||||
try:
|
||||
run_tick(max_symbols=max_symbols, force_rescan=True)
|
||||
# Tip-driven: only force full rescan when tips change is handled inside
|
||||
run_tick(max_symbols=max_symbols, force_rescan=False)
|
||||
except Exception as e:
|
||||
logger.exception("tick failed: %s", e)
|
||||
_set(last_error=str(e), running=False)
|
||||
|
||||
Reference in New Issue
Block a user