程序员量化交易实战 05:量化系统最重要的是数据,不是策略
程序员量化交易实战 05:量化系统最重要的是数据,不是策略
古董级程序员,大厂出来后一直在创业公司,现在仍活跃在一线做 AI 相关的开发。这个专栏会把一个 A 股量化平台从 0 到 1 拆开写:数据、策略、回测、模拟盘、提醒和生产化,尽量用真实代码和真实运行结果说话。更完整的更新也会同步到微信公众号「字与码」。
前四篇已经把边界、工程骨架、核心概念和 A 股交易规则铺好了。
现在很多人会想:是不是终于可以写策略了?还不急。量化系统里最容易被低估的部分不是策略,而是数据。脏数据、缺数据、错口径数据,会让任何策略看起来像有用,也会让任何回测失去意义。
这一篇开始进入数据层。

策略之前先问数据从哪来
一个简单的均线策略看起来只需要收盘价。
但真落到系统里,问题会立刻变多:
- 股票列表从哪里来?
- 退市股票怎么处理?
- 停牌日有没有 K 线?
- 成交量单位是股、手,还是供应商自定义单位?
- 日线是前复权、后复权,还是不复权?
- 财报数据是否有发布时间滞后?
- 数据源失败时要不要 fallback?
- fallback 数据能不能进入正式回测?
这些问题如果不在数据层解决,策略层就会到处打补丁。

数据源不能写死在策略里
策略只应该消费统一后的数据,不应该知道供应商细节。
比如策略想要一段日线,它不应该自己去拼 QVeris、东方财富、Tushare 或同花顺的请求参数。策略应该调用统一接口,拿到标准化后的 MarketBar。
ZiQuant 里用 DataSourceConfig 管理数据源配置:
class DataSourceConfig(Base):
__tablename__ = "zi_quant_data_source_configs"
name = mapped_column(String(80), unique=True)
adapter = mapped_column(String(40), index=True)
enabled = mapped_column(Boolean, default=True)
priority = mapped_column(Integer, default=100)
config_json = mapped_column(JSONB, default=dict)
secret_ref = mapped_column(String(200), nullable=True)
last_status = mapped_column(JSONB, default=dict)
几个字段很关键。
adapter 表示供应商类型,比如 qveris、eastmoney、tushare。priority 表示优先级。secret_ref 不直接存密钥,而是引用环境变量或密钥系统。last_status 保存最近一次调用状态,方便排障。
这样设计以后,策略不需要关心谁提供数据。供应商可以替换,策略逻辑不动。

行情数据要记录来源
行情表不是只有价格。
ZiQuant 的 MarketBar 里有 source 和 payload:
class MarketBar(Base):
__tablename__ = "zi_quant_market_bars"
symbol = mapped_column(String(16), index=True)
trade_date = mapped_column(Date, index=True)
frequency = mapped_column(String(16), default="1d", index=True)
open = mapped_column(Float)
high = mapped_column(Float)
low = mapped_column(Float)
close = mapped_column(Float)
volume = mapped_column(Float, default=0)
amount = mapped_column(Float, default=0)
source = mapped_column(String(40), default="unknown", index=True)
payload = mapped_column(JSONB, default=dict)
如果一条数据来自东方财富,另一条来自 QVeris,还有一条来自 fallback,系统必须能区分。否则后面发现回测异常时,很难判断是策略问题,还是数据混了口径。
数据来源不是审计洁癖。它是排障入口。
财报数据有滞后性
价格数据通常按交易日更新,财报数据不是。
财报有报告期,也有披露时间。2025 年年报不等于 2025 年最后一个交易日就可用。如果回测时提前使用未来才披露的财报,就会出现未来函数。
ZiQuant 当前的 FinancialReport 先保存基础字段:
class FinancialReport(Base):
__tablename__ = "zi_quant_financial_reports"
symbol = mapped_column(String(16), index=True)
report_date = mapped_column(Date, index=True)
report_type = mapped_column(String(40), default="income")
revenue = mapped_column(Float, nullable=True)
net_profit = mapped_column(Float, nullable=True)
roe = mapped_column(Float, nullable=True)
source = mapped_column(String(40), default="unknown", index=True)
payload = mapped_column(JSONB, default=dict)
后续我们会继续补披露时间和口径字段。现在先建立原则:财报因子不能直接用“报告期”当作“可交易日可见信息”。
fallback 数据只能救急,不能糊弄
开发早期,系统可能用 fallback 数据让页面和接口先跑起来。这没问题,但 fallback 必须显式标记。
ZiQuant 的 README 里已经写清楚:真实行情和财报会标记真实来源,fallback 数据会显式标记为 simulated_fallback。生产质量检查里也会统计 fallback 比例。
这条规则非常重要。fallback 可以用于本地开发、界面联调、测试空链路,但不能假装成真实行情进入正式策略结论。
数据质量要变成接口
数据质量不应该靠人打开数据库看几眼。
ZiQuant 已经准备了几个接口:
GET /api/data/quality
GET /api/data/provenance
POST /api/data/coverage
POST /api/stocks/sync
POST /api/data/sync
POST /api/financials/sync
POST /api/financials/coverage
这些接口服务于不同问题:
quality看整体质量:覆盖率、新鲜度、fallback 比例。provenance看数据来源:各来源多少行,最近同步状态如何。coverage看某批股票有没有足够行情。sync系列负责触发股票、行情和财报同步。
后面写策略前,我们会先用这些接口确认数据能不能支撑回测。
数据源接口先定义清楚
第五篇先不实现完整供应商 SDK,但要把接口边界想清楚。
一个数据源适配器至少应该提供三类能力:
class MarketDataProvider:
async def get_stock_basic(self) -> list[dict]:
...
async def get_daily_bars(self, symbol: str, start: str, end: str) -> list[dict]:
...
async def get_financial_reports(self, symbol: str, limit: int = 8) -> list[dict]:
...
注意这里返回的还不是最终 ORM 对象,而是供应商数据经过第一层解析后的结构。真正写入数据库前,还需要标准化、去重、校验和来源标记。
数据层最小验收
数据层不是写完一个下载脚本就结束。至少要能回答下面几个问题:
- 股票主数据有多少只?
- 公共股票池有多少只?
- 每只股票有多少根日 K?
- 最近一根 K 线是哪天?
- 多少数据来自真实供应商?
- fallback 比例是多少?
- 财报覆盖多少股票?
- 同步失败时最近错误是什么?
ZiQuant 的 /ready、/api/data/quality 和 /api/data/provenance 会逐步承担这些检查。
本篇实战任务
这一篇先确认现有数据模型和质量接口入口。
运行表结构测试:
cd /home/alex/work/yswx/zi-quant-platform
uv run pytest tests/test_services.py -k "schema or data_source or data_provenance"
如果从 GitHub 拉取这一章对应代码:
git clone https://github.com/ax2/zi-quant-platform.git
cd zi-quant-platform
git checkout chapter-05
uv sync --extra dev
uv run pytest
启动服务后看健康状态:
uv run uvicorn app.main:app --host 127.0.0.1 --port 8092
curl http://127.0.0.1:8092/health
curl http://127.0.0.1:8092/ready
如果配置了 API Token,后续同步类接口要带 X-Zi-Api-Token。这一点后面写真实数据同步时会展开。
前五篇阶段 review
到这里,第一组五篇完成了一个小闭环。
第 1 篇回答为什么程序员适合做量化,定下“研究、回测、模拟盘,不真实下单”的边界。
第 2 篇搭 Python 项目骨架,让 ZiQuant 可以安装、启动、配置和测试。
第 3 篇把核心概念落到模型:股票、股票池、K 线、因子、策略、回测和模拟盘。
第 4 篇把 A 股交易规则落到代码,新增了可测试的 trading_rules 模块。
第 5 篇开始数据层,强调数据源抽象、来源记录、质量检查和 fallback 标记。
这五篇没有重复写“量化不是玄学”这类开场,而是逐步把项目从认知边界推进到工程骨架、领域模型、交易规则和数据层入口。后面第二组文章会继续沿着数据层展开:PostgreSQL 表设计、股票基础信息表、500 只 A 股股票池、真实行情接入和数据清洗。
本章更新与代码仓库
本章更新内容:
- 明确数据层优先于策略层。
- 梳理数据源抽象、行情来源、财报滞后、fallback 标记和质量接口。
- 完成前五篇阶段 review,确认第一组文章风格、边界和项目进度一致。
代码仓库:
https://github.com/ax2/zi-quant-platform
本章代码:
git clone https://github.com/ax2/zi-quant-platform.git
cd zi-quant-platform
git checkout chapter-05
uv sync --extra dev
uv run pytest
本篇小结
策略之前先做数据。
没有来源记录、质量检查、覆盖率和 fallback 标记,策略输出就没有可信基础。程序员做量化,真正的起点不是写一个买卖公式,而是让数据链路可替换、可检查、可复盘。
下一篇我们进入 PostgreSQL,开始把股票、行情、财报和股票池这些对象稳定地存下来。