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:
@@ -14,6 +14,7 @@ from hub_kline_store import (
|
||||
purge_timeframe_by_days,
|
||||
resolve_chart_bars,
|
||||
retention_days,
|
||||
trim_contiguous_tail,
|
||||
upsert_bars,
|
||||
)
|
||||
from hub_ohlcv_lib import (
|
||||
@@ -190,6 +191,92 @@ class TestHubKlineStore(unittest.TestCase):
|
||||
self.assertEqual(len(got), 2)
|
||||
self.assertEqual(got[-1]["open_time_ms"], base + 2 * period)
|
||||
|
||||
def test_trim_contiguous_tail_drops_orphan_prefix(self):
|
||||
period = TIMEFRAME_MS["15m"]
|
||||
base_old = 1_700_000_000_000
|
||||
base_new = base_old + period * 500
|
||||
bars = []
|
||||
for i in range(3):
|
||||
bars.append(
|
||||
{
|
||||
"open_time_ms": base_old + i * period,
|
||||
"open": 1,
|
||||
"high": 2,
|
||||
"low": 0.5,
|
||||
"close": 1.5,
|
||||
"volume": 1,
|
||||
}
|
||||
)
|
||||
for i in range(5):
|
||||
bars.append(
|
||||
{
|
||||
"open_time_ms": base_new + i * period,
|
||||
"open": 2,
|
||||
"high": 3,
|
||||
"low": 1.5,
|
||||
"close": 2.5,
|
||||
"volume": 2,
|
||||
}
|
||||
)
|
||||
trimmed, split = trim_contiguous_tail(bars, period)
|
||||
self.assertEqual(split, 3)
|
||||
self.assertEqual(len(trimmed), 5)
|
||||
self.assertEqual(trimmed[0]["open_time_ms"], base_new)
|
||||
|
||||
def test_resolve_drops_discontinuous_orphans(self):
|
||||
init_db(self.db)
|
||||
period = TIMEFRAME_MS["15m"]
|
||||
now = int(time.time() * 1000)
|
||||
old_ms = now - period * 800
|
||||
upsert_bars(
|
||||
"okx",
|
||||
"ONDO/USDT",
|
||||
"15m",
|
||||
[
|
||||
{
|
||||
"open_time_ms": old_ms,
|
||||
"open": 0.33,
|
||||
"high": 0.34,
|
||||
"low": 0.32,
|
||||
"close": 0.335,
|
||||
"volume": 100,
|
||||
}
|
||||
],
|
||||
self.db,
|
||||
)
|
||||
recent = []
|
||||
start = now - period * 20
|
||||
for i in range(20):
|
||||
recent.append(
|
||||
{
|
||||
"open_time_ms": start + i * period,
|
||||
"open": 0.35,
|
||||
"high": 0.36,
|
||||
"low": 0.34,
|
||||
"close": 0.355,
|
||||
"volume": 50,
|
||||
}
|
||||
)
|
||||
|
||||
def remote_fetch(**kwargs):
|
||||
return {"ok": True, "bars": recent, "price_tick": 0.0001}
|
||||
|
||||
out = resolve_chart_bars(
|
||||
"okx",
|
||||
"ONDO/USDT",
|
||||
"15m",
|
||||
remote_fetch,
|
||||
db_path=self.db,
|
||||
limit=50,
|
||||
)
|
||||
self.assertTrue(out.get("ok"))
|
||||
candles = out.get("candles") or []
|
||||
self.assertGreaterEqual(len(candles), 19)
|
||||
if len(candles) >= 2:
|
||||
for i in range(1, len(candles)):
|
||||
gap = candles[i]["time"] - candles[i - 1]["time"]
|
||||
self.assertLessEqual(gap, int(period / 1000 * 1.5))
|
||||
|
||||
def test_resolve_before_ms_exhausted(self):
|
||||
init_db(self.db)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user