refactor(data): 5m改同库设计——用户二次拍板2026-08-20:放弃独立库,5m直接写NAS副本主库dbbardata表(interval='5m'与镜像行d/15m唯一键正交=结构性互不干扰)——①bs_5m_eod缺省库改/volume1/stock/sanguo_vnpy_v2/data_backup/quant_trading.db(=同步目标=NAS回测provider读的库portfolio_worker.py:180),NAS就地5m回测/回放reader零改动,未来VPS扩容导interval='5m'反向merge一次迁移②同库安全性实证:同步链export不带id列+NAS侧INSERT OR IGNORE按UNIQUE去重→键永不相交双向碰不到;纯merge无文件覆盖NAS本地行不会被删;id由NAS AUTOINCREMENT自分配无跨库冲突③ensure_schema防重复索引:PRAGMA index_list/index_info识别已有同列唯一索引(NAS侧uq_dbbardata)跳过,免数GB无谓开销+双倍写放大④merge_increment补busy_timeout=60000(副本库从此有第二个写者,无它撞写锁当场locked失败,60s与5m侧对齐)⑤写锁竞争说明:双写者WAL+busy_timeout串行化,与每日增量merge(秒级)重叠单股失败次日LOOKBACK=7自愈;+2测试(缺省库契约钉死/副本已有索引不重复建);40+153绿(data_platform 155) [nas]
This commit is contained in:
@@ -1,14 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""bs_5m_eod.py — NAS 独立 5 分钟线备份 (数据补全 2026-08-20 用户拍板).
|
||||
"""bs_5m_eod.py — NAS 独立 5 分钟线备份 (数据补全 2026-08-20 用户拍板, 同日二次拍板改同库).
|
||||
|
||||
定位(铁律): NAS 的既有数据全部是 VPS 同步镜像(nas_sync 机制, 本脚本零接触);
|
||||
5m 是 NAS 唯一自主从 baostock 下载的数据, 落独立库 BS_5M_DB —— 绝不写
|
||||
/volume1/stock/sanguo_vnpy_v2/data_backup/quant_trading.db(VPS 同步目标, 写了
|
||||
下次同步即被镜像覆盖/冲突)。VPS 不跑本脚本(无 schtask), 5m 上 VPS 等"以后有机会"。
|
||||
定位: 5m 直接写进 NAS 副本主库 dbbardata 表(与 VPS 同步镜像同一个 db/同一张表),
|
||||
靠 interval='5m' 与镜像行(d/15m)在唯一键上正交实现互不干扰 —— 用户目标: NAS 就地
|
||||
5m 回测/回放(reader 零改动), 未来 VPS 扩容直接把 interval='5m' 导出 merge 过去。
|
||||
|
||||
- 库: BS_5M_DB 缺省 /volume1/stock/sanguo_5m/dbbardata_5m.db(容器内外同路径挂载,
|
||||
首次自建 schema+唯一索引)
|
||||
同库安全性(结构性, 非约定):
|
||||
- 同步链(export_increment/merge_increment/sync_dbbardata)导出**不带 id 列**, NAS 侧
|
||||
INSERT OR IGNORE 靠 UNIQUE(symbol,exchange,interval,datetime) 去重 → 5m 行与镜像行
|
||||
键永不相交, 双向都碰不到对方; NAS 本地行不会被同步删除(纯 merge 无文件覆盖)
|
||||
- id 由 NAS AUTOINCREMENT 自分配, 不存在跨库 id 冲突
|
||||
- 写锁竞争: 双写者(WAL+busy_timeout)串行化; 与每日增量 merge(秒级)重叠时单股失败
|
||||
次日 LOOKBACK=7 自愈; 全量 merge 分片(分特级)期间失败股同机制自愈
|
||||
|
||||
- 库: BS_5M_DB 缺省 NAS 副本主库(VPS 同步目标, 也是 NAS 回测 provider 读的库,
|
||||
portfolio_worker.py 传的就是它); ensure_schema 已有唯一索引(NAS 侧名 uq_dbbardata)
|
||||
则不重复建
|
||||
- 模式: 缺省每日增量(LOOKBACK 7 天, DSM 任务计划 ~19:05); --full 一次性回灌
|
||||
2020-01-03+(baostock 分钟数据固定起点非滚动; 全区间每股恰好 1 次调用, 一趟
|
||||
~5.5k query / ~3.6 亿行, 数小时级; per-stock 短事务, 中断重跑幂等)
|
||||
@@ -35,7 +43,8 @@ from bs_eod import ( # noqa: E402 — 复用同一套健壮性封装, 口径与
|
||||
fetch_all_stocks_with_timeout, fetch_k_with_timeout, login_with_retry, relogin,
|
||||
)
|
||||
|
||||
DB = Path(os.environ.get("BS_5M_DB", "/volume1/stock/sanguo_5m/dbbardata_5m.db"))
|
||||
_DEFAULT_DB = "/volume1/stock/sanguo_vnpy_v2/data_backup/quant_trading.db" # NAS 副本主库(同步目标=回测源, 同库正交共存)
|
||||
DB = Path(os.environ.get("BS_5M_DB", _DEFAULT_DB))
|
||||
LOOKBACK = int(os.environ.get("LOOKBACK_DAYS", "7"))
|
||||
FULL_START = "2020-01-03" # baostock 分钟数据固定起点(官网"近5年"口径 2020-01-03)
|
||||
FULL_TIMEOUT = 300 # 全区间单股 ~6.4 万行, 默认 60s 超时不够
|
||||
@@ -56,8 +65,24 @@ def calc_window(today, full):
|
||||
return start, end
|
||||
|
||||
|
||||
def _has_unique_index(conn):
|
||||
"""dbbardata 是否已有覆盖 (symbol,exchange,interval,datetime) 的唯一索引。
|
||||
|
||||
NAS 副本主库已带 uq_dbbardata(merge_increment 建)——同列重复建唯一索引 =
|
||||
数 GB 无谓开销+双倍写放大, 必须识别已有索引跳过(索引名不同语义同)。
|
||||
"""
|
||||
for idx in conn.execute("PRAGMA index_list('dbbardata')").fetchall():
|
||||
if not idx[2]: # (seq, name, unique, origin, partial) 第 3 列 unique
|
||||
continue
|
||||
cols = {r[2] for r in conn.execute(
|
||||
"PRAGMA index_info('%s')" % idx[1]).fetchall()}
|
||||
if cols == {"symbol", "exchange", "interval", "datetime"}:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def ensure_schema(conn):
|
||||
"""独立库自建 schema(与生产 dbbardata 同列) + 唯一索引(REPLACE 去重前提)。"""
|
||||
"""同库写入前的 schema 保障: 建表(若空库)+唯一索引(若尚无, REPLACE 去重前提)。"""
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS dbbardata ("
|
||||
"symbol TEXT NOT NULL, exchange TEXT NOT NULL, datetime TEXT NOT NULL, "
|
||||
@@ -65,11 +90,12 @@ def ensure_schema(conn):
|
||||
"open_interest REAL NOT NULL, open_price REAL NOT NULL, "
|
||||
"high_price REAL NOT NULL, low_price REAL NOT NULL, close_price REAL NOT NULL)"
|
||||
)
|
||||
conn.execute(
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS "
|
||||
"dbbardata_symbol_exchange_interval_datetime "
|
||||
"ON dbbardata (symbol, exchange, interval, datetime)"
|
||||
)
|
||||
if not _has_unique_index(conn):
|
||||
conn.execute(
|
||||
"CREATE UNIQUE INDEX "
|
||||
"dbbardata_symbol_exchange_interval_datetime "
|
||||
"ON dbbardata (symbol, exchange, interval, datetime)"
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user