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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user