增加K线
This commit is contained in:
+173
-14
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user