增加K线

This commit is contained in:
dekun
2026-05-30 10:35:25 +08:00
parent 74bd241579
commit 53959a9008
8 changed files with 683 additions and 134 deletions
+56 -32
View File
@@ -1,18 +1,23 @@
""" K 线:优先 SQLite 本地库,不足或过期再请求币安。"""
"""多周期 K 线:优先 SQLite,不足或过期再请求币安。"""
import logging
from datetime import datetime
from .binance import binance_client
from .config import settings
from .db import get_daily_klines_from_db, get_kline_meta, save_daily_klines
from .chart_intervals import (
CHART_INTERVALS,
cache_minutes_for_interval,
limit_for_interval,
validate_interval,
)
from .db import get_kline_meta, get_klines_from_db, save_klines
from .exceptions import BinanceRateLimitedError
logger = logging.getLogger(__name__)
def _is_db_fresh(symbol: str, min_bars: int) -> bool:
meta = get_kline_meta(symbol)
def _is_db_fresh(symbol: str, interval: str, min_bars: int) -> bool:
meta = get_kline_meta(symbol, interval)
if not meta or meta.get("bar_count", 0) < min_bars:
return False
try:
@@ -20,21 +25,29 @@ def _is_db_fresh(symbol: str, min_bars: int) -> bool:
except ValueError:
return False
age = (datetime.now() - last_fetch).total_seconds()
return age < settings.chart_cache_minutes * 60
return age < cache_minutes_for_interval(interval) * 60
async def sync_daily_klines(symbol: str, limit: int | None = None) -> list[dict]:
"""从币安拉取并写入本地库。"""
async def sync_klines(
symbol: str, interval: str, limit: int | None = None
) -> list[dict]:
"""从币安拉取指定周期并写入本地库。"""
sym = symbol.upper()
n = min(limit or settings.chart_kline_limit, 1500)
candles = await binance_client.get_daily_klines(sym, n)
save_daily_klines(sym, candles)
logger.info("Saved %d daily klines for %s to DB", len(candles), sym)
iv = validate_interval(interval)
n = min(limit or limit_for_interval(iv), 1500)
candles = await binance_client.get_klines_limit(sym, iv, n)
save_klines(sym, iv, candles)
logger.info("Saved %d %s klines for %s to DB", len(candles), iv, sym)
return candles
async def get_daily_candles(
async def sync_daily_klines(symbol: str, limit: int | None = None) -> list[dict]:
return await sync_klines(symbol, "1d", limit)
async def get_candles(
symbol: str,
interval: str = "1d",
limit: int | None = None,
force_refresh: bool = False,
) -> tuple[list[dict], str]:
@@ -43,23 +56,24 @@ async def get_daily_candles(
source: db | db_stale | binance
"""
sym = symbol.upper().strip()
n = min(limit or settings.chart_kline_limit, 1500)
iv = validate_interval(interval)
n = min(limit or limit_for_interval(iv), 1500)
min_bars = min(n, 50)
if not force_refresh and _is_db_fresh(sym, min_bars):
candles = get_daily_klines_from_db(sym, n)
if not force_refresh and _is_db_fresh(sym, iv, min_bars):
candles = get_klines_from_db(sym, iv, n)
if len(candles) >= min_bars:
return candles, "db"
stored = get_daily_klines_from_db(sym, n)
stored = get_klines_from_db(sym, iv, n)
if binance_client.is_rate_limited():
if stored:
logger.warning("Rate limited, serve stale DB klines for %s", sym)
logger.warning("Rate limited, serve stale DB klines for %s %s", sym, iv)
return stored, "db_stale"
raise BinanceRateLimitedError(binance_client.rate_limit_remaining_sec(), sym)
try:
candles = await sync_daily_klines(sym, n)
candles = await sync_klines(sym, iv, n)
return candles, "binance"
except BinanceRateLimitedError:
if stored:
@@ -71,23 +85,33 @@ async def get_daily_candles(
raise
async def get_daily_candles(
symbol: str,
limit: int | None = None,
force_refresh: bool = False,
) -> tuple[list[dict], str]:
return await get_candles(symbol, "1d", limit, force_refresh)
async def prefetch_symbols(symbols: list[str]) -> None:
"""后台预拉 Top 币种 K 入库(串行,避免 418)。"""
"""后台预拉 Top 币种全周期 K 线入库(串行,避免 418)。"""
seen: set[str] = set()
for raw in symbols:
sym = raw.upper().strip()
if not sym or sym in seen or not sym.endswith("USDT"):
continue
seen.add(sym)
if _is_db_fresh(sym, min(50, settings.chart_kline_limit)):
continue
if binance_client.is_rate_limited():
logger.warning("Prefetch stopped — rate limited")
break
try:
await sync_daily_klines(sym)
except BinanceRateLimitedError:
logger.warning("Prefetch rate limited at %s", sym)
break
except Exception as e:
logger.warning("Prefetch %s failed: %s", sym, e)
for interval in CHART_INTERVALS:
n = limit_for_interval(interval)
if _is_db_fresh(sym, interval, min(50, n)):
continue
if binance_client.is_rate_limited():
logger.warning("Prefetch stopped — rate limited")
return
try:
await sync_klines(sym, interval, n)
except BinanceRateLimitedError:
logger.warning("Prefetch rate limited at %s %s", sym, interval)
return
except Exception as e:
logger.warning("Prefetch %s %s failed: %s", sym, interval, e)