docs(data): 归档数据层验证产物 + 数据层总览README

- scripts/data_platform/_archive/legacy/: 归档20个独立探针/诊断/旧降级脚本(零引用验证)
- docs/archive/data/: 归档17个数据相关旧设计/plan/report(保留fusion spec作深读)
- docs/data-platform/README.md: 数据层单一权威记录(8节:架构/布局/源/管线/铁律/API/缺口/待办)
- 删除 _mootdx_depth_result.txt
- Phase2待办: 15m灌库链+旧回填import链(有测试/wrapper依赖,VPS schtask确认后归档)
This commit is contained in:
2026-07-29 10:11:38 +08:00
parent 1cc9126abb
commit c3e53fbef3
39 changed files with 436 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
# 数据层归档脚本(_archive/
本目录存放**已完成使命的验证/诊断/一次性脚本**,不再参与日常增量管线。
保留于 git 历史便于回溯;如需重跑,移动回 `scripts/data_platform/` 顶层即可。
## legacy/ — 探针 / 诊断 / 旧降级 / 一次性验证(2026-07-29 归档)
| 脚本 | 类型 | 说明 |
|------|------|------|
| `probe_*.py`(12) | 探针 | 数据源/库/接口一次性 smoke 验证(akshare 状态/成份股/dbbardata 唯一性/退市/ETF/基本面/涨跌停/unified schema 等) |
| `dbbardata_probe.py` | 探针 | dbbardata 表结构与行数抽查 |
| `run_with_diag.py` / `diag_daily_update.ps1` | 诊断 | 带诊断输出的运行包装 |
| `test_mootdx_depth.py` / `test_baostock_daily_constituent_sample.py` | 一次性验证 | mootdx 深度 / baostock 日线成份股采样(非 tests/ 正式套件) |
| `resume_5yr_watcher.py` | 一次性 | 5 年全市场下载断点续传 watcher(已完成) |
| `fallback.py` / `realtime.py` | 旧降级 | 旧多源降级管理器(日线 akshare→腾讯 / 实时 新浪→东财→腾讯),方案 A 后由 bs_eod/xt_eod 接管 |
归档前已验证:**零 import、无活跃 wrapper 引用**。
## backfill_15m/ — 15min 一次性灌库链(Phase 2 待归档)
⚠️ 未归档。`backfill_15min_baostock``tests/data/test_backfill_15min_hardening.py` 正式 import
`refresh_15min_daily` / `download_minute` / `download_15m_xtdata` / `raw_redownload` / `audit_data_layout`
存在交叉引用或 ops wrapper 依赖,需 Phase 2 评估后统一处理。
@@ -0,0 +1,62 @@
"""Smoke + timing: get_fundamentals_df fields= short-circuit + ThreadPool.
ASCII-only (VPS GBK console safe). Run on VPS:
C:\\Python310\\python.exe -X utf8 probe_fundamentals_panel.py
"""
import os
import sys
import time
os.environ.pop("http_proxy", None)
os.environ.pop("https_proxy", None)
os.environ.pop("all_proxy", None)
sys.path.insert(0, r"C:\sanguo_vnpy_v2")
from sanguo_portfolio.providers.local_unified_provider import LocalUnifiedProvider
DB = r"C:\sanguo_vnpy_v2\data\quant_trading.db"
DATA = r"C:\sanguo_vnpy_v2\data"
p = LocalUnifiedProvider({"db_path": DB, "data_dir": DATA})
# candidate pool: pull a few hundred codes from constituent_unified (000985 = full mkt)
try:
codes = p.get_index_stocks("000985", "2024-06-03")
except Exception as exc:
print("get_index_stocks failed:", exc)
codes = []
codes = codes[:300] if codes else []
jq = [c if "." in c else c + ".XSHE" for c in codes]
print("pool size:", len(jq))
if not jq:
sys.exit(0)
date = "2024-06-03"
# warm caches once (first hit pays file open) to measure steady-ish state? No -
# measure COLD first-rebalance (the real pain): fields=None full read.
t0 = time.time()
df_none = p.get_fundamentals_df(jq, date=date)
t_none = time.time() - t0
# fresh provider to drop per-instance caches, measure fields= short-circuit cold
p2 = LocalUnifiedProvider({"db_path": DB, "data_dir": DATA})
t0 = time.time()
df_fld = p2.get_fundamentals_df(jq, date=date, fields=["market_cap", "eps"])
t_fld = time.time() - t0
print("fields=None : %6.2fs rows=%d cols=%d" % (t_none, len(df_none), len(df_none.columns)))
print("fields=[mkt,ep]: %6.2fs rows=%d cols=%d" % (t_fld, len(df_fld), len(df_fld.columns)))
if t_fld > 0:
print("speedup : %.1fx" % (t_none / t_fld))
# correctness: market_cap + eps match between the two
import pandas as pd
common = [c for c in df_fld.columns if c in df_none.columns]
for col in ("market_cap", "eps"):
a = df_none[col].reindex(df_fld.index)
b = df_fld[col]
mask = a.notna() & b.notna()
diff = (a[mask] - b[mask]).abs().max() if mask.any() else 0.0
print("match %-12s: max_diff=%.6g" % (col, diff))
@@ -0,0 +1,54 @@
"""Smoke: get_limit_status_batch on real dbbardata (window query + detection).
ASCII-only (VPS GBK console safe). Run on VPS:
C:\\Python310\\python.exe -X utf8 probe_limit_status.py
"""
import collections
import os
import sys
for k in ("http_proxy", "https_proxy", "all_proxy"):
os.environ.pop(k, None)
sys.path.insert(0, r"C:\sanguo_vnpy_v2")
from sanguo_portfolio.providers.local_unified_provider import LocalUnifiedProvider
DB = r"C:\sanguo_vnpy_v2\data\quant_trading.db"
DATA = r"C:\sanguo_vnpy_v2\data"
p = LocalUnifiedProvider({"db_path": DB, "data_dir": DATA})
DATE = "2024-06-03"
try:
codes = p.get_index_stocks("000985", DATE)
except Exception as exc:
print("get_index_stocks failed:", exc)
codes = []
jq = [c if "." in c else c + ".XSHE" for c in codes[:800]]
print("pool:", len(jq), "date:", DATE)
out = p.get_limit_status_batch(jq, DATE)
cnt = collections.Counter()
examples = {"up": [], "down": [], "paused": []}
for k, v in (out or {}).items():
if v is None:
cnt["none"] += 1
continue
if v["is_limit_up"]:
cnt["up"] += 1
if len(examples["up"]) < 5:
examples["up"].append(k)
elif v["is_limit_down"]:
cnt["down"] += 1
if len(examples["down"]) < 5:
examples["down"].append(k)
if v["is_paused"]:
cnt["paused"] += 1
if len(examples["paused"]) < 5:
examples["paused"].append(k)
if not (v["is_limit_up"] or v["is_limit_down"] or v["is_paused"]):
cnt["normal"] += 1
print("counts:", dict(cnt))
print("limit_up examples:", examples["up"])
print("limit_down examples:", examples["down"])
print("paused examples:", examples["paused"])
@@ -0,0 +1,134 @@
# -*- coding: utf-8 -*-
"""UnifiedProvider + all_weather 数据链路诊断探针(VPS 跑, 快速版)。
每步带时间戳 + flush, 超时也能看卡哪。慢步骤降样本。
定位: equity 重复日 / B_mean=0 / 数据缺失。
"""
import sys
import os
import sqlite3
import time
from collections import Counter
try:
sys.stdout.reconfigure(line_buffering=True)
except Exception:
pass
t0 = time.time()
def step(name):
print(f"\n=== {name} [+{time.time()-t0:.1f}s]", flush=True)
def line(k, v):
print(f"[{k}] {v}", flush=True)
VPS_ROOT = r"C:\sanguo_vnpy_v2"
DB = os.path.join(VPS_ROOT, "data", "quant_trading.db")
os.environ.setdefault("DEFAULT_DATA_PROVIDER", "jqdata")
from unittest.mock import MagicMock
if "jqdatasdk" not in sys.modules:
_m = MagicMock()
_m.utils.assert_auth = lambda f: f
sys.modules["jqdatasdk"] = _m
sys.path.insert(0, VPS_ROOT)
step("STEP0 环境")
line("python", sys.version.split()[0])
line("PKG", os.path.isdir(os.path.join(VPS_ROOT, "sanguo_portfolio")))
line("DB", os.path.exists(DB))
conn = sqlite3.connect(DB)
tabs = [r[0] for r in conn.execute("SELECT name FROM sqlite_master WHERE type='table'")]
line("tables", tabs)
step("STEP0.5 混合 datetime 检测(单只抽样, 不全表 COUNT)")
# 单只 600519 抽样看格式(走索引, 快)
sample = conn.execute(
"SELECT datetime FROM dbbardata WHERE symbol='600519' AND exchange='SSE' "
"AND interval='d' ORDER BY datetime DESC LIMIT 5"
).fetchall()
line("600519 最近5条 datetime", [r[0] for r in sample])
# DISTINCT 对比(单只, 索引内)
d_raw = conn.execute(
"SELECT COUNT(DISTINCT datetime) FROM dbbardata "
"WHERE symbol='600519' AND exchange='SSE' AND interval='d'"
).fetchone()[0]
d_sub = conn.execute(
"SELECT COUNT(DISTINCT substr(datetime,1,10)) FROM dbbardata "
"WHERE symbol='600519' AND exchange='SSE' AND interval='d'"
).fetchone()[0]
line("DISTINCT datetime(原始)", d_raw)
line("DISTINCT substr(datetime,1,10)(按日)", d_sub)
line("重复日数(原始-按日)", d_raw - d_sub)
step("STEP1 get_trade_days 重复日期(equity_curve 重复 bug 根因)")
from sanguo_portfolio.providers import LocalUnifiedProvider
p = LocalUnifiedProvider({})
days = p.get_trade_days(start_date="2024-01-02", end_date="2024-03-31")
strs = [str(d)[:10] for d in days]
line("trade_days total", len(days))
line("unique dates", len(set(strs)))
dup = [d for d, c in Counter(strs).items() if c > 1]
line("DUP dates count", len(dup))
line("DUP sample", dup[:5])
step("STEP2 成分股(constituent_unified 覆盖)")
for idx in ["000300", "399101", "399001", "000852"]:
try:
s = p.get_index_stocks(idx)
line(f"index_stocks {idx}", len(s))
except Exception as e:
line(f"index_stocks {idx} ERR", repr(e))
step("STEP3 fundamentals 600519(单股, 关键字段)")
fdf = p.get_fundamentals_df(["600519.XSHG"], date="2024-03-29")
cols_chk = [
"code", "market_cap", "circulating_market_cap", "pe_ratio", "pb_ratio",
"ps_ratio", "pcf_ratio", "eps", "roe", "roa", "gross_profit_margin",
"net_profit_margin", "inc_revenue_year_on_year", "roic",
]
for c in cols_chk:
if c in fdf.columns:
line(f" {c}", fdf[c].iloc[0])
else:
line(f" {c}", "MISSING_COL")
step("STEP4 _trend_mean 小样本复算(hs300 前40, B_mean=0 根因)")
import numpy as np
hs300 = p.get_index_stocks("000300")
line("hs300 size", len(hs300))
# 只取前 40 只做 fundamentals(提速), top20 by circ_mktcap
sample40 = hs300[:40]
fdf2 = p.get_fundamentals_df(sample40, date="2024-03-29")
line("fdf2 shape", fdf2.shape)
if "circulating_market_cap" in fdf2.columns:
line("circ_mktcap nonNaN", int(fdf2["circulating_market_cap"].notna().sum()))
fdf2s = fdf2.sort_values("circulating_market_cap", ascending=False, na_position="last")
blst = list(fdf2s.index)[:20]
line("blst(20)", blst)
df = p.get_price(blst, end_date="2024-03-29", frequency="1d", fields=["close"], count=10, panel=False)
line("trend get_price isNone", df is None)
if df is not None:
line("trend get_price shape", df.shape)
line("trend cols", list(df.columns))
line("time dtype", df["time"].dtype if "time" in df.columns else "NO_TIME")
print(df.head(3).to_string(), flush=True)
try:
pivot = df.pivot(index="time", columns="code", values="close")
line("pivot shape", pivot.shape)
if len(pivot) >= 2:
change = (pivot.iloc[-1] / pivot.iloc[0] - 1) * 100
arr = np.nan_to_num(change.to_numpy())
line("B_mean manual", float(np.mean(arr)))
line("change nonZero count", int((arr != 0).sum()))
else:
line("pivot rows<2", len(pivot))
except Exception as e:
line("pivot ERR", repr(e))
step("DONE")
@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
"""monkey-patch engine 关键方法加诊断, 跑回测看 ETF cancel 根因(不改源码)。"""
import sys, os, logging
os.environ.setdefault("DEFAULT_DATA_PROVIDER", "jqdata")
from unittest.mock import MagicMock
m = MagicMock(); m.utils.assert_auth = lambda f: f
sys.modules.setdefault("jqdatasdk", m)
logging.basicConfig(level=logging.WARNING, format="%(message)s")
from bullet_trade.core import engine as eng
_orig_calc = eng.BacktestEngine._calculate_order_amount
def calc(self, order, cp):
r = _orig_calc(self, order, cp)
print(f"[ENG_DIAG] {order.security} cp={cp} amount={r} tgt_val={getattr(order,'_target_value',None)} is_tgt={getattr(order,'_is_target_value',None)} order_amt={getattr(order,'amount',None)}", flush=True)
return r
eng.BacktestEngine._calculate_order_amount = calc
_orig_bp = eng.BacktestEngine._resolve_base_exec_price
def bp(self, security, current_dt, fq_mode):
r = _orig_bp(self, security, current_dt, fq_mode)
print(f"[ENG_DIAG_BP] {security} dt={current_dt} fq={fq_mode} -> {r}", flush=True)
return r
eng.BacktestEngine._resolve_base_exec_price = bp
sys.argv = ['runner', '--provider', 'unified', '--max-pool', '30', '--start', '2024-01-02', '--end', '2024-01-31', '--cash', '1000000']
from sanguo_portfolio.runner_backtest import main
main()