Fetch native exchange OHLCV per timeframe instead of local aggregation.
Store and serve 15m/2h/4h directly from the exchange so market charts match venue candles. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+18
-51
@@ -1,4 +1,4 @@
|
||||
"""中控 K 线 SQLite:分周期保留、本地聚合、分页读取。"""
|
||||
"""中控 K 线 SQLite:分周期保留、交易所直拉、分页读取。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -12,9 +12,7 @@ from hub_ohlcv_lib import (
|
||||
HUB_KLINE_1M_MAX_BARS,
|
||||
HUB_KLINE_5M_1H_RETENTION_DAYS,
|
||||
TIMEFRAME_MS,
|
||||
aggregate_ohlcv_bars,
|
||||
aggregate_ratio,
|
||||
aggregation_source_for_display,
|
||||
YEAR_ROLLING_STORED,
|
||||
chart_chunk_limit,
|
||||
chart_initial_limit,
|
||||
chart_memory_cap,
|
||||
@@ -26,7 +24,6 @@ from hub_ohlcv_lib import (
|
||||
retention_policy_meta,
|
||||
round_ohlcv_bars_to_tick,
|
||||
seed_bar_target,
|
||||
sync_timeframe_for_display,
|
||||
)
|
||||
|
||||
_DEFAULT_RETENTION_DAYS = 15
|
||||
@@ -200,10 +197,10 @@ def purge_1m_bar_cap(db_path: Path | None = None, *, max_bars: int | None = None
|
||||
|
||||
|
||||
def purge_retention(db_path: Path | None = None) -> int:
|
||||
"""按周期策略清理:5m/1h 一年;1m 保留最近 N 根;1d/1w 不删。"""
|
||||
"""按周期策略清理:5m/15m/1h/2h/4h 一年;1m 保留最近 N 根;1d/1w 不删。"""
|
||||
n = 0
|
||||
n += purge_timeframe_by_days("5m", HUB_KLINE_5M_1H_RETENTION_DAYS, db_path)
|
||||
n += purge_timeframe_by_days("1h", HUB_KLINE_5M_1H_RETENTION_DAYS, db_path)
|
||||
for tf in sorted(YEAR_ROLLING_STORED):
|
||||
n += purge_timeframe_by_days(tf, HUB_KLINE_5M_1H_RETENTION_DAYS, db_path)
|
||||
n += purge_1m_bar_cap(db_path)
|
||||
return n
|
||||
|
||||
@@ -400,19 +397,6 @@ def _trim_display_bars(
|
||||
return bars
|
||||
|
||||
|
||||
def _aggregate_display_bars(
|
||||
src_bars: list[dict[str, Any]],
|
||||
display_tf: str,
|
||||
*,
|
||||
need: int,
|
||||
before_ms: int | None,
|
||||
) -> list[dict[str, Any]]:
|
||||
if not src_bars:
|
||||
return []
|
||||
agg = aggregate_ohlcv_bars(src_bars, display_tf)
|
||||
return _trim_display_bars(agg, need=need, before_ms=before_ms)
|
||||
|
||||
|
||||
def resolve_chart_bars(
|
||||
exchange_key: str,
|
||||
symbol: str,
|
||||
@@ -427,7 +411,7 @@ def resolve_chart_bars(
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
分页读库:首屏 / 左拖 before_ms / 尾部 tail_refresh。
|
||||
15m←5m,2h/4h←1h 现场聚合;其余直读入库周期。
|
||||
各展示周期均直读交易所同步入库的同名 K 线。
|
||||
"""
|
||||
init_db(db_path)
|
||||
purged = purge_retention(db_path)
|
||||
@@ -438,8 +422,7 @@ def resolve_chart_bars(
|
||||
if not sym or not ex_k:
|
||||
return {"ok": False, "msg": "缺少 exchange 或 symbol"}
|
||||
|
||||
agg_src = aggregation_source_for_display(display_tf)
|
||||
storage_tf = agg_src or sync_timeframe_for_display(display_tf)
|
||||
storage_tf = display_tf
|
||||
is_history = before_ms is not None and int(before_ms) > 0
|
||||
need = int(
|
||||
limit
|
||||
@@ -450,24 +433,14 @@ def resolve_chart_bars(
|
||||
now_ms = int(time.time() * 1000)
|
||||
period_display = TIMEFRAME_MS[display_tf]
|
||||
period_storage = TIMEFRAME_MS[storage_tf]
|
||||
ratio = aggregate_ratio(display_tf, storage_tf) if agg_src else 1
|
||||
if tail_refresh and not is_history:
|
||||
need = min(need, max(30, ratio * 6 if agg_src else 20))
|
||||
src_need = need * ratio + ratio * 4
|
||||
need = min(need, 30)
|
||||
cutoff = history_cutoff_ms_for_storage(storage_tf, now_ms)
|
||||
source_kind = "aggregate" if agg_src else "db"
|
||||
|
||||
def load_display_rows() -> list[dict[str, Any]]:
|
||||
if agg_src:
|
||||
if is_history:
|
||||
src = load_bars_before(ex_k, sym, storage_tf, int(before_ms), src_need, db_path)
|
||||
else:
|
||||
src = load_bars_latest(ex_k, sym, storage_tf, src_need, db_path)
|
||||
return _aggregate_display_bars(
|
||||
src, display_tf, need=need, before_ms=before_ms if is_history else None
|
||||
)
|
||||
if is_history:
|
||||
return load_bars_before(ex_k, sym, storage_tf, int(before_ms), need, db_path)
|
||||
rows = load_bars_before(ex_k, sym, storage_tf, int(before_ms), need, db_path)
|
||||
return _trim_display_bars(rows, need=need, before_ms=int(before_ms))
|
||||
return load_bars_latest(ex_k, sym, storage_tf, need, db_path)
|
||||
|
||||
db_rows: list[dict[str, Any]] = []
|
||||
@@ -498,20 +471,16 @@ def resolve_chart_bars(
|
||||
if is_history:
|
||||
bms = int(before_ms)
|
||||
anchor = bms - period_display
|
||||
since = max(cutoff, anchor - period_storage * src_need)
|
||||
fetch_limit = min(src_need + 20, 1500)
|
||||
since = max(cutoff, anchor - period_storage * need)
|
||||
fetch_limit = min(need + 20, 1500)
|
||||
elif tail_only:
|
||||
if agg_src:
|
||||
src_tail = load_bars_latest(ex_k, sym, storage_tf, 5, db_path)
|
||||
anchor_ms = int(src_tail[-1]["open_time_ms"]) if src_tail else now_ms
|
||||
else:
|
||||
anchor_ms = int(newest_db) if newest_db is not None else now_ms
|
||||
since = max(cutoff, anchor_ms - period_storage * max(5, ratio * 3))
|
||||
fetch_limit = min(max(20, ratio * 8), 300)
|
||||
anchor_ms = int(newest_db) if newest_db is not None else now_ms
|
||||
since = max(cutoff, anchor_ms - period_storage * 5)
|
||||
fetch_limit = min(need + 20, 300)
|
||||
else:
|
||||
since = max(cutoff, now_ms - period_storage * min(src_need, seed_bar_target(storage_tf)))
|
||||
since = max(cutoff, now_ms - period_storage * min(need, seed_bar_target(storage_tf)))
|
||||
fetch_limit = min(
|
||||
seed_bar_target(storage_tf) if force_refresh else src_need + 20,
|
||||
seed_bar_target(storage_tf) if force_refresh else need + 20,
|
||||
1500,
|
||||
)
|
||||
|
||||
@@ -527,8 +496,6 @@ def resolve_chart_bars(
|
||||
if price_tick is not None:
|
||||
save_symbol_price_tick(ex_k, sym, price_tick, db_path)
|
||||
db_rows = load_display_rows()
|
||||
if fetched:
|
||||
source_kind = "remote" if source_kind == "db" else source_kind
|
||||
else:
|
||||
remote_err = remote.get("msg") or remote.get("error") or "实例拉取 K 线失败"
|
||||
if not db_rows:
|
||||
@@ -589,7 +556,7 @@ def resolve_chart_bars(
|
||||
"oldest_ms": oldest_ms,
|
||||
"newest_ms": newest_ms,
|
||||
"exhausted": exhausted,
|
||||
"source": "remote" if fetched else source_kind,
|
||||
"source": "remote" if fetched else "db",
|
||||
"retention_policy": retention_policy_meta(),
|
||||
"candles": candles,
|
||||
"from_cache": from_cache,
|
||||
|
||||
Reference in New Issue
Block a user