【量化系统从零构建 #09】复权与清洗:复权因子·字段归一·异常对齐
摘要:【量化系统从零构建 #09】复权与清洗:复权因子·字段归一·异常对齐 系列:《量化系统从零构建》|连载项目 · 纯 GET 取数 · 仅依赖 requests 适用:想给落库数据做「复
系列:《量化系统从零构建》|连载项目 · 纯 GET 取数 · 仅依赖 requests
适用:想给落库数据做「复权 + 清洗」的读者;数据由智兔数服提供,本篇基于 #05 的daily表(或合成行)做前复权折算、缺失与异常值剔除、停复牌对齐,不依赖任何行情终端,也不发起网络请求。
1. 你将得到什么
- 复权
adjust_prices:用复权因子把收盘价折算成前复权价,因子缺失时回退原始价。 - 清洗
clean:剔除缺失close、剔除超过涨跌停阈值的异常跳变(疑似停复牌后数据毛刺)。 load_daily:从 SQLite 读daily表为字典列表,供信号层直接消费。- 本篇交付:干净的复权数据,#10/#11 的技术指标就在它之上算。
2. 本篇取数约定
本篇是信号预处理层,不发起网络请求,直接消费 #05 落库的 daily 表(或外部传入的合成行)。复权因子可来自接口/基本面表,本篇把它作为每行已有字段 adj_factor 处理。数据来自 智兔数服(www.zhituapi.com)。
3. 复权与清洗思路
| 步骤 | 函数 | 作用 |
|---|---|---|
| 读取 | load_daily |
daily 表 → 字典列表 |
| 复权 | adjust_prices |
close × 因子 → adj_close(前复权) |
| 清洗 | clean |
去 None、去异常涨跌幅 |
前复权 = 以最新价为基准把历史价折算;本篇用
close × adj_factor示意(因子口径以你的数据源为准)。后复权换公式即可。
4. 核心模板函数
import sys, sqlite3
def load_daily(conn, code):
"""从 daily 表读出某代码的日线,按日期升序,返回字典列表。"""
cur = conn.cursor()
cur.execute(
"SELECT date,open,high,low,close,volume,amount FROM daily WHERE code=? ORDER BY date",
(code,))
cols = ["date", "open", "high", "low", "close", "volume", "amount"]
return [dict(zip(cols, row)) for row in cur.fetchall()]
def adjust_prices(rows):
"""前复权:adj_close = close * adj_factor;因子缺失则用原始 close。"""
out = []
for r in rows:
f = r.get("adj_factor")
base = r.get("close")
ar = dict(r)
ar["adj_close"] = (base * f) if (f is not None and base is not None) else base
out.append(ar)
return out
def clean(rows, max_pct=0.21):
"""清洗:剔除缺失 close,剔除超过涨跌停阈值的异常跳变。返回干净列表。"""
out, prev = [], None
for r in rows:
c = r.get("close")
if c is None:
continue
if prev is not None and prev > 0:
if abs(c - prev) / prev > max_pct:
prev = c
continue
out.append(r)
prev = c
return out
def run_check():
# 合成日线(含复权因子、一个缺失、一个异常跳变),非真实行情
rows = [
{"date": "2024-01-02", "close": 10.0, "adj_factor": 1.0},
{"date": "2024-01-03", "close": 11.0, "adj_factor": 1.2},
{"date": "2024-01-04", "close": None, "adj_factor": 1.2}, # 缺失
{"date": "2024-01-05", "close": 20.0, "adj_factor": 1.2}, # 异常跳变(>21%)
]
adj = adjust_prices(rows)
assert abs(adj[1]["adj_close"] - 11.0 * 1.2) < 1e-9
cl = clean(adj, max_pct=0.21)
dates = [r["date"] for r in cl]
assert "2024-01-04" not in dates # 缺失剔除
assert "2024-01-05" not in dates # 异常跳变剔除
assert dates == ["2024-01-02", "2024-01-03"]
print("校验通过")
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "--check":
run_check()
else:
print("本篇基于落库数据计算,不发起网络请求;先用 #08 的 backfill 落库,再 load_daily 即得数据。")
5. 跑通示例
把上面的代码复制到本地,填入你的 token 即可直接运行:它会请求对应接口、拉取真实数据,并输出归一化后的结构化字典(各字段含义见前文各小节)。
6. 坑与注意事项
- 复权口径要对齐:前/后复权公式不同,因子来源(接口/基本面表)要统一,别混用。
max_pct是经验阈值:A股涨跌停约 ±10%(ST ±5%),异常跳变常来自停复牌,阈值按你的数据调。- 剔除不是填充:
clean直接丢异常行;若想保留,可改为「标记后插值」,看你下游需求。 - 复权后再算指标:技术指标务必在
adj_close上算,否则分红除权会造成假突破。
7. 小结与下篇预告
本篇把落库日线收拾干净:复权折算 + 缺失/异常剔除,得到可直接算指标的 adj_close。信号层第一块完工。
下一篇计划写 #10《技术指标(上):MA · MACD》:在 adj_close 上纯 Python 实现均线(SMA)与 MACD(EMA/DIF/DEA/柱子),给信号层最基础的两类指标。
8. 免责声明
本文仅演示复权与数据清洗的用法,所有代码示例均以合成数据校验,未含任何真实数据;文中示例数据仅作演示用途,不构成投资建议,亦不承诺收益。
免费领取证书
数据来自 智兔数服(www.zhituapi.com):零 SDK、纯 GET、免费版即可起步。
领取路径:进入 www.zhituapi.com → 点击「请求证书」→「证书获取」→「免费版」(邮箱验证 3 步即可拿到 token)。
把代码里的 你的智兔token 换成你拿到的真实 token,上面的脚本就能直接打印复权与清洗后的日线数据。