Fix discontinuous hub chart candles from orphaned DB bars.

Keep only the latest contiguous K-line segment, purge isolated stale rows, and backfill when the tail is still shorter than the initial limit.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-08 11:21:01 +08:00
parent cfa28e7f4e
commit 440d1ecbc9
3 changed files with 173 additions and 1 deletions
+85
View File
@@ -345,6 +345,54 @@ def load_bars_before(
conn.close()
def trim_contiguous_tail(
bars: list[dict[str, Any]],
period_ms: int,
*,
max_gap_factor: float = 1.5,
) -> tuple[list[dict[str, Any]], int]:
"""只保留最近一段连续 K 线,丢弃左侧与主段断开的孤立数据。"""
if len(bars) <= 1:
return list(bars), 0
try:
period = max(1, int(period_ms))
except (TypeError, ValueError):
period = 60_000
max_gap = int(period * max_gap_factor)
split = 0
for i in range(len(bars) - 1, 0, -1):
gap = int(bars[i]["open_time_ms"]) - int(bars[i - 1]["open_time_ms"])
if gap > max_gap:
split = i
break
return bars[split:], split
def purge_bars_open_before(
exchange_key: str,
symbol: str,
timeframe: str,
open_time_ms: int,
db_path: Path | None = None,
) -> int:
"""删除某品种周期下早于 open_time_ms 的 K 线(清理与主段断开的孤立历史)。"""
ex_k = (exchange_key or "").strip().lower()
sym = (symbol or "").strip().upper()
tf = normalize_chart_timeframe(timeframe)
conn = _connect(db_path)
try:
cur = conn.execute(
"""
DELETE FROM ohlcv_bars
WHERE exchange_key=? AND symbol=? AND timeframe=? AND open_time_ms < ?
""",
(ex_k, sym, tf, int(open_time_ms)),
)
return int(cur.rowcount or 0)
finally:
conn.close()
def _rows_to_bars(rows) -> list[dict[str, Any]]:
return [
{
@@ -532,6 +580,43 @@ def resolve_chart_bars(
except Exception:
pass
if not is_history and db_rows and len(db_rows) > 1:
trimmed, split_at = trim_contiguous_tail(db_rows, period_display)
if split_at > 0:
purge_bars_open_before(
ex_k, sym, storage_tf, int(trimmed[0]["open_time_ms"]), db_path
)
db_rows = trimmed
if (
not is_history
and db_rows
and len(db_rows) < need
and not force_refresh
):
oldest = int(db_rows[0]["open_time_ms"])
missing = need - len(db_rows)
backfill_since = max(cutoff, oldest - period_storage * (missing + 40))
try:
remote_back = remote_fetch(
symbol=sym,
timeframe=storage_tf,
since_ms=backfill_since,
limit=min(missing + 60, 1500),
)
if remote_back.get("ok") and remote_back.get("bars"):
fetched += upsert_bars(ex_k, sym, storage_tf, remote_back["bars"], db_path)
db_rows = load_display_rows()
if len(db_rows) > 1:
trimmed, split_at = trim_contiguous_tail(db_rows, period_display)
if split_at > 0:
purge_bars_open_before(
ex_k, sym, storage_tf, int(trimmed[0]["open_time_ms"]), db_path
)
db_rows = trimmed
except Exception:
pass
price_tick = normalize_price_tick(price_tick)
if db_rows and price_tick is not None:
round_ohlcv_bars_to_tick(db_rows, price_tick)