feat(quant): QlibEngine v1 — 本地行情落盘 QlibDataset → D.features 读取 → 因子回测

- qlib_adapter/provider.py:SQLite 行情按 qlib 0.9.8 二进制格式落盘(起始索引头 + 逐日 float32、instruments 3 列、小写 instrument、晚上市 offset)
- qlib_adapter/dataset.py:qlib.init 幂等({'day': uri})+ D.features 读取 close 面板
- qlib_adapter/engine.py:QlibEngine(QuantEngine)v1 —— Qlib 数据管线回测与 LocalEngine 同记账规则(无未来函数/成本/涨跌停标注),factor_test 复用共享实现;Alpha158+LightGBM 为 TODO
- 真实 20 股验证:qlib 落盘 142 文件→读取→回测(-12.81%,Local 对照 -12.97%,差异为 qlib float32 存储)
- tests/test_qlib_engine.py 5 项(格式/roundtrip/晚上市 offset/回测/因子测试)→ pytest 86 passed / ruff clean
This commit is contained in:
Simon
2026-09-06 18:20:28 +08:00
parent 880f4c50fe
commit b8f67f99ae
5 changed files with 309 additions and 19 deletions
+47
View File
@@ -0,0 +1,47 @@
"""Qlib 数据读取(dataset):qlib.init 幂等 + 从 provider_uri 读行情面板。"""
from __future__ import annotations
from datetime import date
from pathlib import Path
import pandas as pd
_init_state: dict = {"uri": None}
def ensure_qlib_init(provider_uri: Path) -> None:
"""初始化 qlib(进程内同 uri 幂等;不同 uri 会重新 init)。"""
if _init_state["uri"] == str(provider_uri):
return
import qlib
from qlib.config import REG_CN
qlib.init(provider_uri={"day": str(provider_uri)}, region=REG_CN)
_init_state["uri"] = str(provider_uri)
def load_close_panel(
provider_uri: Path,
symbols: list[str],
start: date,
end: date,
) -> pd.DataFrame:
"""经 qlib D.features 读取 $close,返回 date×symbol 面板(index datetime)。"""
ensure_qlib_init(provider_uri)
from qlib.data import D
lower = [s.lower() for s in symbols]
df = D.features(
lower,
["$close"],
start_time=start.strftime("%Y-%m-%d"),
end_time=end.strftime("%Y-%m-%d"),
freq="day",
)
if df.empty:
return pd.DataFrame()
close = df["$close"].unstack(level="instrument")
close.index = pd.to_datetime(close.index)
close.columns = [c.upper() for c in close.columns]
return close.sort_index()