feat(hub): add open/close arrows on archive chart with continuous klines
Span chart window across hold period, fill 5m gaps for smooth aggregation, and mark entry/exit with lightweight-charts arrows. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,6 +7,7 @@ from pathlib import Path
|
||||
|
||||
from hub_ohlcv_lib import aggregate_ohlcv_bars
|
||||
from hub_symbol_archive_lib import (
|
||||
_fill_missing_bars,
|
||||
init_db,
|
||||
resolve_archive_chart,
|
||||
upsert_bars_5m,
|
||||
@@ -16,7 +17,15 @@ from hub_symbol_archive_lib import (
|
||||
)
|
||||
|
||||
|
||||
def _seed_5m_bars(db: Path, start_ms: int, count: int, step: int = 300_000) -> None:
|
||||
def _seed_5m_bars(
|
||||
db: Path,
|
||||
start_ms: int,
|
||||
count: int,
|
||||
step: int = 300_000,
|
||||
*,
|
||||
ex: str = "gate",
|
||||
sym: str = "ONDO",
|
||||
) -> None:
|
||||
bars = []
|
||||
price = 1.0
|
||||
for i in range(count):
|
||||
@@ -32,7 +41,7 @@ def _seed_5m_bars(db: Path, start_ms: int, count: int, step: int = 300_000) -> N
|
||||
"volume": 100 + i,
|
||||
}
|
||||
)
|
||||
upsert_bars_5m("gate", "ONDO", bars, db_path=db)
|
||||
upsert_bars_5m(ex, sym, bars, db_path=db)
|
||||
|
||||
|
||||
def test_aggregate_15m_from_5m():
|
||||
@@ -76,6 +85,53 @@ def test_resolve_archive_chart_15m():
|
||||
assert len(out["candles"]) >= 10
|
||||
|
||||
|
||||
def test_fill_missing_bars_continuity():
|
||||
period = 300_000
|
||||
start = (1_700_000_000_000 // period) * period
|
||||
bars = [
|
||||
{
|
||||
"open_time_ms": start,
|
||||
"open": 1.0,
|
||||
"high": 1.1,
|
||||
"low": 0.9,
|
||||
"close": 1.05,
|
||||
"volume": 10,
|
||||
},
|
||||
{
|
||||
"open_time_ms": start + period * 2,
|
||||
"open": 1.05,
|
||||
"high": 1.15,
|
||||
"low": 1.0,
|
||||
"close": 1.1,
|
||||
"volume": 8,
|
||||
},
|
||||
]
|
||||
filled = _fill_missing_bars(bars, period, start, start + period * 2)
|
||||
assert len(filled) >= 3
|
||||
assert any(b.get("filled") for b in filled)
|
||||
|
||||
|
||||
def test_resolve_archive_chart_hold_window():
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
db = Path(td) / "archive.db"
|
||||
init_db(db)
|
||||
open_ms = 1_700_000_000_000
|
||||
close_ms = open_ms + 6 * 3600_000
|
||||
_seed_5m_bars(db, open_ms - 20 * 300_000, 200, ex="gate", sym="BNB/USDT")
|
||||
out = resolve_archive_chart(
|
||||
"gate",
|
||||
"BNB/USDT",
|
||||
"15m",
|
||||
opened_ms=open_ms,
|
||||
closed_ms=close_ms,
|
||||
mode="hold",
|
||||
db_path=db,
|
||||
)
|
||||
assert out["ok"] is True
|
||||
assert out.get("opened_ms") == open_ms
|
||||
assert len(out["candles"]) >= 10
|
||||
|
||||
|
||||
def test_list_with_overlay_filters():
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
db = Path(td) / "archive.db"
|
||||
|
||||
Reference in New Issue
Block a user