增加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
+173 -14
View File
@@ -47,6 +47,23 @@ def init_db() -> None:
message TEXT
);
CREATE TABLE IF NOT EXISTS klines (
symbol TEXT NOT NULL,
interval TEXT NOT NULL,
open_time INTEGER NOT NULL,
open REAL NOT NULL,
high REAL NOT NULL,
low REAL NOT NULL,
close REAL NOT NULL,
volume REAL NOT NULL,
quote_volume REAL NOT NULL DEFAULT 0,
updated_at TEXT NOT NULL,
PRIMARY KEY (symbol, interval, open_time)
);
CREATE INDEX IF NOT EXISTS idx_klines_symbol_interval
ON klines(symbol, interval, open_time);
CREATE TABLE IF NOT EXISTS daily_klines (
symbol TEXT NOT NULL,
open_time INTEGER NOT NULL,
@@ -64,9 +81,11 @@ def init_db() -> None:
ON daily_klines(symbol, open_time);
CREATE TABLE IF NOT EXISTS kline_meta (
symbol TEXT PRIMARY KEY,
symbol TEXT NOT NULL,
interval TEXT NOT NULL DEFAULT '1d',
last_fetch_at TEXT NOT NULL,
bar_count INTEGER NOT NULL
bar_count INTEGER NOT NULL,
PRIMARY KEY (symbol, interval)
);
CREATE TABLE IF NOT EXISTS funding_history (
@@ -99,6 +118,57 @@ def init_db() -> None:
"""
)
_migrate_klines_if_needed(conn)
def _migrate_klines_if_needed(conn: sqlite3.Connection) -> None:
"""从旧版 daily_klines / 单 symbol kline_meta 迁移到多周期表。"""
cols = conn.execute("PRAGMA table_info(kline_meta)").fetchall()
col_names = {c[1] for c in cols}
if "interval" not in col_names and cols:
rows = conn.execute(
"SELECT symbol, last_fetch_at, bar_count FROM kline_meta"
).fetchall()
conn.execute("DROP TABLE kline_meta")
conn.execute(
"""
CREATE TABLE kline_meta (
symbol TEXT NOT NULL,
interval TEXT NOT NULL DEFAULT '1d',
last_fetch_at TEXT NOT NULL,
bar_count INTEGER NOT NULL,
PRIMARY KEY (symbol, interval)
)
"""
)
for r in rows:
conn.execute(
"""
INSERT INTO kline_meta (symbol, interval, last_fetch_at, bar_count)
VALUES (?, '1d', ?, ?)
""",
(r[0], r[1], r[2]),
)
has_klines = conn.execute(
"SELECT 1 FROM klines LIMIT 1"
).fetchone()
if has_klines:
return
daily_count = conn.execute("SELECT COUNT(*) FROM daily_klines").fetchone()[0]
if not daily_count:
return
conn.execute(
"""
INSERT OR IGNORE INTO klines (
symbol, interval, open_time, open, high, low, close, volume, quote_volume, updated_at
)
SELECT symbol, '1d', open_time, open, high, low, close, volume, quote_volume, updated_at
FROM daily_klines
"""
)
def save_snapshot(
@@ -159,7 +229,82 @@ def log_push(period_start: str, period_end: str, success: bool, message: str = "
)
def save_klines(symbol: str, interval: str, candles: list[dict[str, Any]]) -> None:
sym = symbol.upper()
iv = interval.lower()
now = datetime.now().isoformat()
with get_conn() as conn:
for c in candles:
conn.execute(
"""
INSERT INTO klines (
symbol, interval, open_time, open, high, low, close, volume, quote_volume, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(symbol, interval, open_time) DO UPDATE SET
open = excluded.open,
high = excluded.high,
low = excluded.low,
close = excluded.close,
volume = excluded.volume,
quote_volume = excluded.quote_volume,
updated_at = excluded.updated_at
""",
(
sym,
iv,
int(c["time"]),
float(c["open"]),
float(c["high"]),
float(c["low"]),
float(c["close"]),
float(c.get("volume", 0)),
float(c.get("quote_volume", 0)),
now,
),
)
conn.execute(
"""
INSERT INTO kline_meta (symbol, interval, last_fetch_at, bar_count)
VALUES (?, ?, ?, ?)
ON CONFLICT(symbol, interval) DO UPDATE SET
last_fetch_at = excluded.last_fetch_at,
bar_count = excluded.bar_count
""",
(sym, iv, now, len(candles)),
)
def get_klines_from_db(symbol: str, interval: str, limit: int) -> list[dict[str, Any]]:
sym = symbol.upper()
iv = interval.lower()
with get_conn() as conn:
rows = conn.execute(
"""
SELECT open_time, open, high, low, close, volume, quote_volume
FROM klines
WHERE symbol = ? AND interval = ?
ORDER BY open_time DESC
LIMIT ?
""",
(sym, iv, limit),
).fetchall()
rows = list(reversed(rows))
return [
{
"time": int(r["open_time"]),
"open": float(r["open"]),
"high": float(r["high"]),
"low": float(r["low"]),
"close": float(r["close"]),
"volume": float(r["volume"]),
"quote_volume": float(r["quote_volume"]),
}
for r in rows
]
def save_daily_klines(symbol: str, candles: list[dict[str, Any]]) -> None:
save_klines(symbol, "1d", candles)
sym = symbol.upper()
now = datetime.now().isoformat()
with get_conn() as conn:
@@ -190,19 +335,12 @@ def save_daily_klines(symbol: str, candles: list[dict[str, Any]]) -> None:
now,
),
)
conn.execute(
"""
INSERT INTO kline_meta (symbol, last_fetch_at, bar_count)
VALUES (?, ?, ?)
ON CONFLICT(symbol) DO UPDATE SET
last_fetch_at = excluded.last_fetch_at,
bar_count = excluded.bar_count
""",
(sym, now, len(candles)),
)
def get_daily_klines_from_db(symbol: str, limit: int) -> list[dict[str, Any]]:
stored = get_klines_from_db(symbol, "1d", limit)
if stored:
return stored
sym = symbol.upper()
with get_conn() as conn:
rows = conn.execute(
@@ -230,12 +368,33 @@ def get_daily_klines_from_db(symbol: str, limit: int) -> list[dict[str, Any]]:
]
def get_kline_meta(symbol: str) -> dict[str, Any] | None:
def get_kline_meta(symbol: str, interval: str = "1d") -> dict[str, Any] | None:
sym = symbol.upper()
iv = interval.lower()
with get_conn() as conn:
row = conn.execute(
"SELECT last_fetch_at, bar_count FROM kline_meta WHERE symbol = ? AND interval = ?",
(sym, iv),
).fetchone()
if not row:
if iv == "1d":
return _legacy_kline_meta(sym)
return None
return {
"last_fetch_at": row["last_fetch_at"],
"bar_count": row["bar_count"],
}
def _legacy_kline_meta(symbol: str) -> dict[str, Any] | None:
"""兼容旧库仅有 symbol 维度的 meta(迁移前)。"""
with get_conn() as conn:
cols = {c[1] for c in conn.execute("PRAGMA table_info(kline_meta)").fetchall()}
if "interval" in cols:
return None
row = conn.execute(
"SELECT last_fetch_at, bar_count FROM kline_meta WHERE symbol = ?",
(sym,),
(symbol,),
).fetchone()
if not row:
return None