风险提示:本系列为工程参考,不构成投资建议。文中出现的策略骨架、因子、参数、阈值都是为了说明工程链路而保留的最小例子,不代表任何可直接上线的方法。
写过策略代码的人多半都遇到过这种场景:在 Jupyter 里跑出年化 35%、夏普 2.6、最大回撤 8%,曲线漂亮得不真实;接到模拟盘里第一个月就跌了 4%,再上实盘第一周直接被滑点和手续费吃掉一半收益。然后开始返工:是不是用了未来函数?是不是停牌没处理?是不是涨跌停日成交假设过乐观?是不是回测里没扣印花税?是不是复权方式搞错了?是不是把因子值用了今天才发布的财报?
这些问题不是策略写得不好,是工程链路没接好。量化交易的难点从来不是想到一个能赚钱的想法,而是把一个有效的想法在不被工程坑吃掉的前提下落到实盘。这条链路从一根 K 线进入数据库,到一笔订单从交易所成交回报回流,要走过至少八段,每一段都有自己的输入输出、失败模式与不变量。
这个系列的目标,是把这条链路上每一段的工程方法讲清楚。我们不讲”明天涨什么”,我们讲:
- 一份行情数据从交易所发出来,怎么落到一张能被回测复用、又不引入未来函数的表里;
- 一个因子从公式到能进入组合优化器,要经过哪些清洗、对齐、中性化、检验;
- 一个回测引擎要做到什么程度,才能让模拟盘和实盘的偏差小于一个可解释的范围;
- 一笔目标交易要被拆成多少子单、走哪些场所、用什么算法,才能让冲击成本不把 Alpha 吃掉;
- 一套风控要在哪几个时间点拦在哪几条链路上,才能在策略失控的瞬间把钱守住。
这是【量化交易】系列的第 1 篇,承担两件事:第一,把整条链路的骨架画清楚,让后续 28 篇每篇都能挂回这张图;第二,给出一份”研究 → 上线”的流程清单,让读者在自己写第一条策略之前就知道每一步的卡点在哪。
一、什么是量化交易:从直觉到工程
1.1 一个不太严谨但够用的定义
量化交易(Quantitative Trading)这个词在工业界被用得很宽:它既包括”用 Python 跑回测、买基金”的散户量化,也包括”FPGA 解 ITCH 包做高频做市”的机构 HFT,中间还有股票多因子、CTA 趋势跟踪、统计套利、可转债套利、加密做市、跨期跨品种套利。这些事看起来差异很大,但都满足三个共同特征:
- 决策由可表达为代码的规则或模型驱动,而不是由交易员的主观判断驱动;
- 执行由系统自动完成,至少在”何时下单、下多少、撤不撤”这些动作上不依赖人;
- 效果可统计度量,能用收益、夏普、回撤、信息比率这些指标评价。
这三条同时成立的系统,就是量化系统。少一条都不算:
- 只有规则、靠人下单,是”半自动交易”;
- 有自动执行、但参数靠拍脑袋调,是”程序化交易”,不一定是量化;
- 模型存在、但效果无法度量,是”调参玄学”。
主观投资(discretionary trading)与量化的边界并不在”用不用电脑”,而是在”决策是否可复现”。一位基金经理把所有买卖逻辑写在 Excel 里逐单录入,仍然是主观;一套基于神经网络的策略,如果同样的输入永远给出同样的输出,就是量化,不论里面藏着多少黑盒。
1.2 量化的四个层次
把量化系统拆成四个层次,对后续每一篇都有用:
| 层次 | 含义 | 输出物 | 典型角色 |
|---|---|---|---|
| 因子(Factor) | 对某种市场异象的可计算度量 | 截面数列:每个标的每个时点一个数 | 研究员 |
| 信号(Signal) | 把因子转换成”对未来收益的预测” | 预期收益向量 + 不确定度 | 策略研究员 |
| 策略(Strategy) | 信号 + 风险 + 执行约束 → 目标持仓 | 目标持仓与目标交易 | 策略工程师 |
| 系统(System) | 把策略落地的全部基础设施 | 一条能稳定运转的流水线 | 量化工程师 / SRE |
很多团队的事故都来自层次混淆。研究员认为”因子做得好就行了”,工程师认为”系统稳定就行了”,没人对”信号到策略到系统”那一段负责,于是因子在研究环境是有效的,进了实盘就失效。本系列在每一篇都会标注它在哪个层次:
- 第 9–14 篇是因子层;
- 第 12、13 篇与 ML 相关的部分跨到信号层;
- 第 15–17 篇是策略层;
- 第 23–28 篇是系统层。
1.3 量化能赚什么钱
工程文章不预测市场,但”系统设计为什么这么设”绕不开这个问题。市场里能被量化系统稳定收割的钱,大致来自下面几类:
- 风险溢价:长期承担某种风险(市场风险、波动率风险、流动性风险)应得的补偿。指数增强、Smart Beta、风险平价大都吃这类钱;
- 市场异象:投资者群体的认知偏差、行为偏差、机构约束(如指数调样、季末美化报表)造成的可预测偏离。多因子、动量、反转、分析师预期修正吃这类;
- 微观结构:订单簿不平衡、盘口报价的短期回归、做市商库存调整带来的微观可预测性。HFT 与做市吃这类;
- 结构性套利:同一现金流的多种表达之间的价差。期现套利、ETF 套利、跨期跨品种套利、可转债套利吃这类;
- 信息差:基本面研究、另类数据挖掘、新闻舆情对市场未消化信息的提前解读。事件驱动与基本面量化吃这类。
不同钱对应不同的工程要求:HFT 拼时延,统计套利拼协整稳定性,基本面量化拼数据与因子库,做市拼库存控制与风控速度。后续每个具体策略类的篇章都会先回答”它在赚什么钱”,再讲怎么实现。
1.4 量化不能解决的事
工程方法不是万能。下面这些问题,再好的系统也解决不了:
- 市场逻辑变化:政策变更、做空机制改变、注册制、股指期货受限、加密交易所被封禁,这些事件下历史规律会失效;
- 容量边界:一个 5 亿规模可行的策略,做到 50 亿可能完全不行。任何回测在容量上都有上限,不靠工程突破;
- 黑天鹅尾部:再好的风控也只能压住可统计的尾部,对真正的”未见过的事件”(2015 股灾、2020 原油负油价、2022 LME 镍逼空)都需要硬性熔断兜底;
- 过度拟合:再大的模型也会在训练集上漂亮,事前检验它会不会失败,是研究问题,不是工程问题。
工程只能保证”如果策略在历史上是有效的,工程不会把它的有效性吃掉”。能不能找到一个真正有效的策略,是另一回事。
二、量化交易的工程链路
2.1 一张图:八段链路
把整个量化系统的数据流拆开,从左到右一定经过这八段:
数据 → 特征 → 因子 → 信号 → 组合 → 执行 → 风控 → 复盘
↑
└── 反馈到数据/特征/信号
每一段都有清晰的输入输出,每一段都有自己的失败模式:
| 段 | 输入 | 输出 | 主要失败模式 |
|---|---|---|---|
| 数据 | 交易所原始行情、基本面、参考数据 | 干净、对齐、PIT 安全的表 | 时间错位、复权错、停牌缺失 |
| 特征 | 干净表 | 标的 × 时点的数值矩阵 | 未来函数、横截面泄漏 |
| 因子 | 特征矩阵 | 经过中性化、标准化的因子 | 极端值、行业暴露、风格暴露 |
| 信号 | 因子 + 模型 | 预期收益与不确定度 | 过拟合、时序泄漏、样本不平衡 |
| 组合 | 信号 + 风险模型 + 约束 | 目标持仓 | 协方差病态、约束不可行 |
| 执行 | 目标持仓 - 当前持仓 | 实际成交 | 滑点、冲击成本、未成交残单 |
| 风控 | 全链路状态 | 放行/限制/熔断 | 漏拦、误杀、规则缺口 |
| 复盘 | 实际成交、行情、信号 | 归因报表与反馈 | 归因方法错误、口径不一致 |
下面这张系统架构图把这八段画成层,并标出每段对应的本系列章节:
图里有三种箭头:
- 实线表示数据/指令的前向流动;
- 红色虚线表示风控的控制流(前置/在途/事后三层),它不是数据流,是一票否决权;
- 绿色虚线表示成交回报的回流,从交易所一路回到回测/复盘,是闭环的关键。
很多团队画系统图只画前向,会漏掉两件事:风控的控制流和成交回报的回流。只要有一处回流被切断,复盘就会和实盘对不上,时间一长策略就成了黑盒。
2.2 数据:能不能正确用昨天
数据这一段决定整条链路的下限。它的工作不是”把行情存进去”,而是回答一个问题:你能不能在 t 时刻只用 t-ε 之前真实可得的数据做决策。这个能力叫 PIT(point-in-time)安全。
一份不 PIT 安全的数据有很多种死法:
- 用了财报数据,但用的是”事实发生时的财报值”而不是”在那个时点市场实际看到的财报值”。财报会被更正、被审计调整、被重述(restatement),如果数据库覆盖更新而不是版本化,回测会看到当时不可能看到的修正版数字;
- 把分析师一致预期写在预测发布日,而实际上 Bloomberg、Wind 是按日发布、可被回填的,要拿”快照”而不是”现值”;
- 复权用了未来除权信息:如果用前复权日线在某个 t 上算昨日收益,分母里嵌入了 t 之后的除权因子,等于偷看未来;
- 上市日、退市日、停牌日没有专门的状态字段,回测里”停牌日成交”就发生了。
数据段的不变量大致有四条:
- 任意一个数据点必须有”被市场看到的时间戳”
asof,与”事件发生时间戳”event_time分开存; - 同一字段被多次发布的,必须按版本保存,不能覆盖;
- 任何一个时点 t 的查询,等价于
WHERE asof <= t,不允许出现WHERE event_time = t; - 复权因子要按时点存,回测查询时给复权因子绑定
asof,不能用”当前最新的复权”。
第 5、6、7 篇会把这四条不变量在工程上怎么做钉死。
2.3 特征与因子:把数据变成可比的数
特征是从数据里算出来的”中间变量”,因子是把特征变成”对截面有信息量的数列”。两者的边界没那么清晰,但工程上有个朴素的区分:
- 特征:可以是任意尺度、任意分布、任意单位的数值,例如”过去 20 日成交量”、“市值”、“过去 60 日收益的偏度”;
- 因子:经过横截面处理、能直接和别的因子加减比较的数列,通常做过去极值、标准化、行业中性化、市值中性化。
工程上有几个反复犯的错误:
- 直接用原始市值做因子,结果整张表被几只权重股带着走,完全不是”小市值溢价”,是”按市值排序”;
- 滚动统计窗口对停牌不处理,停牌日补 0 把波动率算崩;
- 横截面排序用 rank 但没排除 NA,rank 方法不同结果差很大;
- 因子之间高度相关却没去相关,多因子合成等于在一个方向上加了几次仓位。
第 7、9 篇专门讲因子工厂、第 8 篇讲另类数据特征,每一类问题都会给出代码级的处理方案。
2.4 信号与组合:从预测到目标
信号是把因子翻译成”预期收益”。最简单的是单因子线性映射,复杂的是 LightGBM 或 LSTM 给出条件分布。但不论模型多复杂,输出都要落到两个量上:
- 每个标的的预期超额收益
alpha_i; - 每个标的预期收益的不确定度
sigma_i,或更一般地,整张协方差矩阵Sigma。
组合优化的工作是把 alpha 和
Sigma 在一组约束下变成目标持仓
w*:
maximize w^T alpha - 0.5 * lambda * w^T Sigma w - cost(w, w_prev)
subject to sum(w) = 1(或 0,市场中性)
|w_i| <= w_max
beta_exposure(w) ∈ [-b, b]
industry_exposure(w) ∈ [-h, h]
turnover(w, w_prev) <= T
这是教科书写法。工程上需要回答更朴素的问题:
Sigma怎么估计才能不病态?历史协方差直接用,标的多了立马奇异;- 约束写错一个不等号,优化器会给出极端解,怎么发现?
- 上一期目标持仓没有完全成交,新一期是否要把残单算进
w_prev? - 优化器无解的时候降级到什么?回到上期持仓还是按比例缩放?
第 15、16、17 篇会给出具体的工程化方案,特别是
cost
函数的写法——它既要包含手续费、印花税,也要包含冲击成本的二次项,不然优化器会算出一份在”没有市场摩擦”假设下漂亮、在真实市场里瞬间被冲击吃掉的目标持仓。
2.5 执行:把目标持仓变成成交
执行是被低估得最厉害的一段。研究员写完信号交给”执行模块”的那一刻,往往觉得自己的工作做完了;但一份目标持仓和一份实际成交之间的差距,常常比信号本身的 Alpha 还大。
执行段要回答的问题:
- 目标交易在多长的窗口内完成?秒级、分钟级、小时级、整日?
- 拆成多少子单?每个子单挂哪个价位?挂多久?
- 没成交的部分是追价、撤掉、还是等下一根 K 线?
- 多场所怎么路由?暗池、做市商询价、限价簿、竞价撮合各占多少?
这些问题对应不同的执行算法:VWAP(成交量加权平均价)、TWAP(时间加权平均价)、POV(参与率)、IS(实施差额,Implementation Shortfall)、Adaptive。第 23、24 篇会讲执行算法与智能订单路由(SOR),并给出”执行成本怎么测、怎么归因”的方法。
执行段的不变量有两条最关键:
- 每一笔下单必须带一个全链路唯一的
client_order_id,与回报匹配; - 一个交易意图在系统里只能对应一份”在途订单状态”,禁止重复下单。
这两条在第 27 篇 OMS/EMS 架构里展开。
2.6 风控:三层一票否决
风控分三层,对应三个不同的时间尺度:
- 前置(Pre-trade):在订单发出去之前拦。检查单笔金额、单标的当日累计、自成交(self-trade)、涨跌停、流动性、可融券与否、限制名单。
- 在途(At-trade):订单在外、还没全部成交时拦。实时持仓、实时盈亏、风险敞口、Beta、行业暴露、币种敞口。
- 事后(Post-trade):成交回报回流后做合规复核。监管报送、账务勾稽、日终核对。
工程上最常见的风控故障:
- 风控做在策略代码里而不是独立通道里,策略 bug 一并把风控绕过;
- 实时持仓由本地状态估计,没用交易所回报兜底,断线后状态漂移;
- 涨跌停判断只看价格不看状态,连续一字板时仍然挂单消耗席位;
- 自成交检查只在同账户内做,没考虑同主体下多个账户。
第 16、19、25 篇会分别讲撮合层、组合层、做市层的风控做法。本系列的核心立场是:风控必须是独立通道,必须有”安全默认是拒绝”的能力,必须能在毫秒级熔断。
2.7 复盘:闭环的最后一段
复盘的工作不是事后写报告,是把实盘结果反哺回研究流程。一份合格的复盘要回答:
- 实盘收益与回测收益的差距来自哪里?滑点占多少?费率占多少?信号衰减占多少?容量边际占多少?
- 哪些标的的成交价显著差于预期?是流动性问题还是模型问题?
- 哪些信号在最近 N 天的 IC 显著下滑?是统计噪声还是结构性失效?
第 22 篇会给出一套”实盘 - 回测差异分解”的口径模板。这一段的工程要点是口径一致:复盘用的费率、滑点假设、撮合假设必须和回测引擎完全一致,否则你比的是两套系统而不是策略本身的有效性。
三、关键约束:时间正确性
3.1 量化系统里时间有几种
普通系统里的时间通常只有一种:墙上时钟。量化系统里至少有六种,必须分清:
| 时间名 | 含义 | 典型来源 |
|---|---|---|
| 事件时间(event_time) | 事件在现实中发生的时间 | 交易所撮合时间、财报披露时间 |
| 采集时间(capture_time) | 数据被你的系统首次采到的时间 | 行情接收器入队时间 |
| 可见时间(asof) | 市场上一般参与者能看到该数据的时间 | 撮合时间 + 网络延迟 |
| 决策时间(decision_time) | 策略基于已知数据做出决策的时间 | 信号生成时刻 |
| 下单时间(submit_time) | 订单离开本系统的时间 | 网卡发出时间 |
| 成交时间(exec_time) | 交易所撮合该订单的时间 | 成交回报中的时间字段 |
回测里只能用
asof <= decision_time,不能用
event_time <= decision_time。事件时间小于决策时间不代表那个时点你能看到,这是新手最常踩的坑。
3.2 未来函数:从公式到代码
未来函数(look-ahead bias)是指一个本应只能用 t 时刻及以前数据计算的量,事实上偷用了 t 之后的数据。常见形态:
- 未对齐的滚动窗口:
pandas.rolling(20).mean()默认右对齐,没问题;但rolling(20, center=True)就是中心对齐,会用到未来 10 个点; - 整体标准化:在整段历史上用
(x - x.mean()) / x.std()做归一化,均值方差用了全样本,等于偷看未来; - 填充错误:
fillna(method='bfill')用未来值回填; - 联表错误:把财报数据按
report_date联到行情上,但市场看到财报要等announce_date(通常晚 30–90 天); - 复权错误:用前复权日线计算昨日收益,分母嵌入了之后的除权因子。
工程上防止未来函数有三层:
- 数据层:所有事实表都带
asof,查询封装强制asof <= t; - 特征层:滚动窗口默认右对齐,禁止
center=True,禁止整体标准化(用expanding或滚动窗口替代); - 回测层:撮合假设必须延迟一根 bar,即 t 时刻信号只能用 t+1 的开盘价或 VWAP 成交。
3.3 复权与停牌:两种最常见的隐形未来
复权和停牌看起来是数据问题,本质是时间问题。
复权有三种:不复权(原始价)、前复权、后复权。前复权是最常用的,但它的复权因子依赖未来的所有除权除息事件。一个回测如果用”今天最新的前复权数据”在历史时点上做信号,整段历史的价格已经被未来事件污染了。
正确做法:保留原始价 + 一张 (asof, factor)
表,回测查询时按 asof
取当时已知的复权因子。等价的实现是后复权(以历史某点为基准向后累乘),它有一个好性质——历史值固定不变,后续除权事件只追加新数据,不修改老数据。
停牌带来的问题更隐蔽:
- 停牌日不应该出现在样本里,但很多数据源会用前一日收盘价填补,
pct_change算出 0% 收益,假装连续; - 停牌期间发生的事件(增发、并购、退市预警)在复牌日一次性反映,回测里如果不识别”复牌跳空”就会把跳空算成正常收益;
- 停牌日不能下单,但回测如果按”日线收盘价成交”会假装能成交。
第 6 篇会给出停牌处理的几条标准动作。
3.4 三套环境的时间语义
研究、回测、实盘三套环境对时间的处理是不同的,工程上必须对齐:
| 环境 | 时间推进方式 | 数据可见性 | 撮合假设 |
|---|---|---|---|
| 研究 | 事后批处理 | 全样本可见 | 不撮合 |
| 回测 | 按 bar 推进 | asof <= t |
模拟撮合(滑点/延迟模型) |
| 实盘 | 真实墙上时钟 | 真实可见 | 真实撮合 |
研究阶段允许用全样本,但只能用于”探索”,不能用于评价策略表现;回测阶段必须严格按时间推进;实盘则没有”重来一次”的机会。
第 19 篇专门讲回测引擎的时间引擎怎么写,第 20 篇讲常见的时间相关坑(前视、生存者偏差、再平衡时机偏差)。
四、买方与卖方视角
4.1 角色差异
量化系统的设计思路在买方与卖方之间差异很大:
- 买方(Buy-Side):基金、自营、对冲基金、量化私募。目标是用资金赚收益,关心 Alpha、风险、容量、Sharpe;
- 卖方(Sell-Side):券商、做市商、经纪商、交易所、流动性提供者。目标是为客户提供流动性或工具,关心做市利差、佣金、滑点、库存。
简单粗暴的对照:
| 维度 | 买方 | 卖方 |
|---|---|---|
| 目标函数 | 风险调整后收益 | 流动性提供 + 价差 + 佣金 |
| 时延要求 | 多数策略秒级到日级,少数毫秒 | 几乎全部微秒级 |
| 持仓方向 | 主动选 Alpha | 被动跟随客户/市场 |
| 风控重点 | 组合层风险 | 库存与单笔风险 |
| 系统重点 | 研究 → 回测 → 组合优化 → 执行 | 行情 → 撮合/做市 → 风控 → 清结算 |
4.2 系统设计差异
买方系统重在研究链路:数据平台、因子库、回测引擎、组合优化器、归因分析。执行通常用券商提供的算法或外部 EMS,不必自己造。
卖方系统重在低延迟链路:行情解码、撮合、做市报价、风控网关、订单路由、监管报送。研究在卖方更多体现为定价模型与做市参数调优,不是 Alpha 挖掘。
两者中间还有一类——量化做市与高频套利,介于买方与卖方之间,靠极低延迟在订单簿上做被动或半主动报价。第 25、26 篇会专门讲做市与 HFT 架构。
本系列以买方视角为主线(第 1–22 篇),同时覆盖做市/HFT 与系统工程(第 23–28 篇)。读者可以按自己角色选择重点。
五、市场覆盖:A 股、美股、期货、加密
5.1 市场结构差异速查表
下面这张表是多市场量化的”地理常识”,必须记住:
| 维度 | A 股 | 港股 | 美股 | 国内期货 | 加密(中心化) |
|---|---|---|---|---|---|
| 撮合机制 | 集合竞价 + 连续竞价 | 同左 | 连续竞价 + 收盘竞价 | 集合 + 连续 | 连续竞价(订单簿) |
| 交易制度 | T+1 现货 | T+0 | T+0 | T+0 | T+0 |
| 涨跌停 | ±10%(创业板/科创板 ±20%,ST ±5%) | 无 | 无 | 各品种不同 | 多数无 |
| 最小变动价位 | 0.01 元 | 0.001/0.005/0.01 等 | 0.01 美元(个别 0.0001) | 各品种不同 | 各币对不同 |
| 卖空 | 融券受限,标的池小 | 可做空 | 可做空 | 可做空(双向) | 可永续合约做空 |
| 交易费率 | 佣金(万 1–3)+ 印花税(卖出千 1)+ 过户费 | 印花税 + 佣金 + 交易征费 | 极低佣金 + SEC 费 | 万 0.5 左右 | 0.02%–0.1% |
| 数据 Level 2 | 可买(券商/数据商) | 可买 | NASDAQ ITCH / NYSE Pillar | 可买 | API 公开 |
| 交易时间 | 9:30–11:30 / 13:00–15:00 | 9:30–12:00 / 13:00–16:00 | 9:30–16:00 ET | 多时段含夜盘 | 7×24 |
每一栏背后都对应回测里的一行代码:
- 涨跌停 → 回测里要加”涨停日不能买、跌停日不能卖”逻辑;
- T+1 → 当日买入的股票当日不能卖,组合优化的
w_prev要分”可卖部分”和”今日新买部分”; - 印花税单边 → 卖出成本明显高于买入,导致换手率敏感的策略偏好低换手;
- 最小变动价位 → 滑点模型不能假设连续价格,必须按 tick 离散化。
5.2 A 股的几个特殊约束
A 股是国内量化的主战场,有几个其他市场少见的约束,单独列出:
- T+1:买入当日不能卖,导致日内反转策略受限;
- 涨跌停:连续涨停板期间订单簿极不平衡,挂单基本无法成交,回测必须把”一字板”识别出来;
- 集合竞价:开盘前 9:15–9:25 的集合竞价规则与连续竞价不同,9:25 撮合的成交价是开盘价,回测里”按开盘价成交”必须区分”集合竞价撮合到的”和”开盘后第一笔连续撮合”;
- 科创板/创业板涨跌停 20%:要按板块查涨跌停幅度;
- 新股次新股:上市头几日无涨跌幅限制,波动极大;
- 退市制度:ST、退市整理期标的的特殊规则。
第 2 篇会把这些约束铺开讲,并给出回测引擎里对应的开关。
5.3 加密的特殊性
加密市场的工程难点和传统市场不一样:
- 7×24 无休市:没有日切,没有”昨日收盘”的天然对账锚点,要自己定义结算窗口;
- 多场所流动性碎片化:同一币种在数十家交易所交易,价差与流动性差异巨大;
- 现货/合约/永续/期权混合:永续合约的资金费率(funding rate)决定持仓成本,CEX 和 DEX 还要分开;
- API 限速:交易所 REST/WebSocket 限速严格,下单链路要自己做速率控制;
- 交易所风险:交易所本身可能跑路、被黑、暂停提现,工程上必须把”账户余额”和”链上余额”分开核对。
第 14 篇专门讲加密策略的工程化,包括跨所搬砖、资金费率套利、做市等。
六、研究流程:从想法到上线
6.1 一张图
研究流程图把”一个想法到一笔实盘订单”中间的所有关键卡点串起来:
整个流程有 12 个步骤,6 道关键卡点(G1–G6)。每一道卡点都是一次”go / no-go” 决定,不通过就回到上一步或者放弃。
6.2 每一步的关键动作与卡点
1. 假设
- 用一句话写出可证伪的假设。例如:“过去 20 日成交量加权动量在 A 股全市场截面上在未来 5 日有正向预测能力。”
- 不允许写”我觉得动量有用”这种没法证伪的话。
2. 因子构造
- 明确每个数据源的
asof字段; - 处理停牌、复权、新股、退市;
- 行业中性化(按申万一级)、市值中性化(对 log 市值回归取残差);
- 极值处理:MAD 或分位数截断;
- 标准化:z-score 或 rank。
卡点 G1:因子 PIT 校验通过。把因子计算往前挪一天,结果应该完全一致;如果不一致说明哪里偷看了未来。
3. 单因子检验
- 计算 IC(信息系数)、Rank IC、IC_IR;
- 分组:把因子值按截面排成 10 组,看分组收益是否单调;
- 衰减:因子值在未来 1、5、10、20 日的预测能力衰减半衰期;
- 行业/风格暴露:检查因子在哪些行业/风格上集中。
卡点 G2:Rank IC 显著(t > 2)、IC_IR > 0.5、衰减半衰期 ≥ 调仓周期。不通过则回到第 2 步调整因子定义。
4. 多因子合成
- 因子之间的相关性矩阵,相关性 > 0.7 的因子做正交化或剔除;
- 加权方法:等权、IC 加权、ICIR 加权、ML 合成;
- 合成后的总因子重新做单因子检验,看 IC 是否提升。
5. 组合构建
- 设定约束:换手率上限、单标的权重上限、行业偏离上限、风格偏离上限;
- 优化目标:均值方差 / 风险预算 / 凯利近似;
- 协方差矩阵估计:样本协方差 + Ledoit-Wolf 收缩,或多因子风险模型。
6. 回测
- 撮合假设:t 信号 → t+1 开盘价或 VWAP 成交;
- 滑点模型:固定滑点 / 比例滑点 / 平方根冲击模型;
- 费率:佣金 + 印花税 + 过户费 + 平台费;
- 样本切分:样本内训练,样本外验证,留一段不许碰的最终样本。
卡点 G3:扣除真实费率与冲击成本后,样本外 Sharpe 仍然显著为正、最大回撤可控、收益分布无极端尾部依赖。
7. Walk-forward 验证
- 滚动训练:每隔 N 个月重新训练,前 K 个月做训练,后 1 个月做检验;
- 参数稳健性:在合理参数邻域内表现是否稳定,曲面是否光滑;
- 结构断点:把样本按时间切几段,每段表现是否一致。
8. 容量估计
- 把目标交易量映射到日均成交量(ADV)的占比;
- 估计冲击成本:常用平方根模型
impact ∝ σ * sqrt(Q/ADV); - 给出”在不同管理规模下的预期净 Alpha 曲线”。
9. 模拟盘
- 使用真实行情、模拟下单、影子账户,跑 1–3 个月;
- 关键指标:模拟盘日收益与回测日收益的 Pearson 相关、绝对差距、滑点偏差;
- 不一致就回到第 6 步,检查回测假设。
卡点 G4:模拟盘与回测的日收益相关性 > 0.8,日均偏差 < 一定阈值,滑点偏差能解释清楚来源。
10. 灰度上线
- 先用 1%–10% 的目标资金试运行;
- 限速:单日最大下单笔数与金额;
- 监控:滑点漂移、IC 衰减、持仓异常、交易所拒单率。
卡点 G5:灰度期内监控指标无系统性漂移,无非预期事件。
11. 全量上线
- 资金分批上调;
- 持续监控 + 日报 + 周报;
- 异常熔断阈值预先定义。
12. 复盘归因
- 实盘收益 = 信号收益 - 滑点 - 费率 + 残差;
- 残差大的话定位到某几只标的或某几日;
- 归因结果反馈到下一轮的假设。
卡点 G6:归因模型对实盘 - 回测差异的解释力 > 阈值,否则视为偶然或存在未识别风险。
6.3 每一步的常见卡点
| 步骤 | 常见卡点 | 处理 |
|---|---|---|
| 1 | 假设含混无法证伪 | 写一句话假设 |
| 2 | 因子值含未来数据 | PIT 校验脚本 |
| 3 | IC 显著但分组不单调 | 检查极值与行业暴露 |
| 4 | 多因子合成后 IC 反而下降 | 因子相关性筛选 |
| 5 | 优化器无解 / 极端解 | 约束放宽 + 协方差收缩 |
| 6 | 回测漂亮、滑点崩 | 加更激进的冲击模型 |
| 7 | 参数曲面陡 | 视为过拟合,丢弃 |
| 8 | 容量太小 | 直接放弃或按容量缩规模 |
| 9 | 模拟与回测偏差大 | 检查回测假设、对齐口径 |
| 10 | 灰度期 IC 衰减 | 暂停,研究是否结构性失效 |
| 11 | 实盘异常 | 熔断 |
| 12 | 归因解释力不足 | 视为偶然,暂停加仓 |
七、本系列的边界
7.1 我们讲什么
【量化交易】系列覆盖以下内容:
- 市场结构与微观结构(第 2、3、4 篇):交易制度、订单类型、订单簿动力学;
- 数据工程(第 5、6、7、8 篇):行情管道、生存者偏差、特征存储、另类数据;
- 因子与信号(第 9、10、11、12、13 篇):经典因子、统计套利、事件驱动、ML/DL;
- 加密策略(第 14 篇);
- 组合与风险(第 15、16、17 篇):组合构建、风险模型、仓位管理;
- 执行成本(第 18 篇);
- 回测(第 19、20、21、22 篇):引擎、坑、Walk-forward、绩效;
- 执行算法(第 23、24 篇):VWAP/TWAP/IS、SOR;
- 做市与 HFT(第 25、26 篇);
- 系统架构(第 27 篇);
- 运维与合规(第 28 篇)。
7.2 我们不讲什么
下面这些不在本系列覆盖范围:
- 具体的可上线 Alpha:不公开任何”现在还能赚钱”的因子或参数;
- 投资建议:本系列不给买卖建议,不预测市场,不评价具体标的;
- 撮合引擎实现细节:撮合是交易所/经纪商侧的工作,已由 【金融科技工程】系列 第 16 篇覆盖;
- 清算结算:见 【金融科技工程】系列 第 11、18 篇;
- 监管细则:见 【金融科技工程】系列 第 21 篇;
- 传统投研基本面分析:本系列只讲量化方法,不讲行业研究、估值模型、价值投资。
7.3 与其他系列的引用关系
【量化交易】与【金融科技工程】是互补的。简单划线:
- 金融科技工程 关心”钱怎么不丢”——账务、清算、合规;
- 量化交易工程 关心”钱怎么变多”——信号、组合、执行。
两套系统在交易所/经纪商侧汇合:撮合是金融科技的工作,发单与回报是量化交易的工作。
与【分布式系统】系列也有交叉:消息总线、时序数据库、共识算法,量化系统都用得上,但本系列不重复讲分布式基础,需要时引用 【分布式系统】系列。
八、阅读路径
整个系列共 28 篇,不同读者可以按下面四条路径选读。
8.1 入门路径
目标:建立量化的世界观,能跑通第一个回测。
- 第 1 篇(本篇)
- 第 2 篇 市场结构
- 第 5 篇 数据管道
- 第 6 篇 生存者偏差
- 第 9 篇 因子库入门
- 第 19 篇 回测引擎
- 第 22 篇 绩效指标
读完这 7 篇能写出一个”不犯严重错误的回测”。
8.2 策略路径
目标:理解多种策略类型与适用边界。
- 第 9 篇 因子库
- 第 10 篇 统计套利
- 第 11 篇 事件驱动
- 第 12 篇 ML Alpha
- 第 13 篇 DL 时序
- 第 14 篇 加密
- 第 15 篇 组合构建
- 第 16 篇 风险模型
策略路径读完,对”市面上的量化策略大概在做什么”有结构性认知。
8.3 工程路径
目标:把策略变成稳定运行的系统。
- 第 5 篇 数据管道
- 第 7 篇 特征存储
- 第 19、20、21 篇 回测三连
- 第 23、24 篇 执行算法 + SOR
- 第 27 篇 系统架构
- 第 28 篇 运维合规
工程路径是给量化工程师准备的”系统书”。
8.4 HFT 路径
目标:进入低延迟领域。
- 第 3 篇 微观结构
- 第 4 篇 订单类型
- 第 16 篇 风险模型(实时风控相关)
- 第 25 篇 做市
- 第 26 篇 HFT 架构
- 第 27 篇 系统架构
HFT 路径要求对网络、内核、硬件加速有基础。
九、三段最小代码
文章末尾给三段最小可运行代码,分别覆盖数据加载、因子计算、回测骨架。它们的目的是让读者立刻有”代码触感”,后续每一篇都会在更具体的场景上扩展。
9.1 数据加载:PIT 安全的行情读取
下面这段用 polars 读一份本地 parquet 行情,强制按
asof 过滤。环境:Python 3.11、polars 1.x。
import polars as pl
from datetime import date
def load_pit_safe(
path: str,
decision_date: date,
universe: list[str] | None = None,
) -> pl.DataFrame:
"""读取 PIT 安全的日频行情。
要求 parquet 必须包含字段:
- symbol: str
- event_date: date 该 bar 对应的交易日
- asof: datetime 数据被市场看到的时间
- open / high / low / close / volume
- adj_factor: float 截至 asof 已知的复权因子
"""
df = (
pl.scan_parquet(path)
.filter(pl.col("asof") <= pl.lit(decision_date))
.group_by(["symbol", "event_date"])
.agg([
pl.col("open").last(),
pl.col("high").last(),
pl.col("low").last(),
pl.col("close").last(),
pl.col("volume").last(),
pl.col("adj_factor").last(),
pl.col("asof").last(),
])
.sort(["symbol", "event_date"])
)
if universe is not None:
df = df.filter(pl.col("symbol").is_in(universe))
return df.collect()要点:
- 先按
asof <= decision_date过滤,再按(symbol, event_date)取 last。这样保证拿到的是”在 decision_date 之前最新可见”的版本,不是事件时间为 decision_date 的最新值; adj_factor跟着asof走,回测查询时直接用,不要拿”今天最新的复权因子”;- universe 过滤放在最后,避免在 scan 阶段触发字段全表扫。
PIT 校验脚本可以这样写:把 decision_date
往前挪一天再跑一遍,结果应该是上一版的子集。如果出现”挪前一天反而多出一些行”,说明
asof 字段被弄脏了。
9.2 因子计算:行业中性化的横截面动量
下面是一个最简单的”过去 20 日动量、行业中性化、截面 z-score” 因子。环境:numpy 1.26、pandas 2.x、scipy 1.11。
import numpy as np
import pandas as pd
def neutralize_by_group(values: pd.Series, groups: pd.Series) -> pd.Series:
"""对每个组内做去均值。"""
return values - values.groupby(groups).transform("mean")
def zscore_clip(s: pd.Series, n_std: float = 3.0) -> pd.Series:
"""先做 MAD 截尾,再 z-score。"""
med = s.median()
mad = (s - med).abs().median() * 1.4826
if mad == 0 or np.isnan(mad):
return pd.Series(np.zeros(len(s)), index=s.index)
clipped = s.clip(med - n_std * mad, med + n_std * mad)
return (clipped - clipped.mean()) / (clipped.std(ddof=0) + 1e-12)
def momentum_factor(
px: pd.DataFrame, # 行=日期, 列=symbol, 值=后复权收盘价
industry: pd.Series, # index=symbol, 值=行业代码
lookback: int = 20,
) -> pd.DataFrame:
"""过去 lookback 日动量 + 行业中性 + z-score。"""
# 关键:shift(1) 保证当日因子只用 t-1 及以前的价格
ret = px.pct_change(lookback).shift(1)
out = pd.DataFrame(index=ret.index, columns=ret.columns, dtype=float)
for dt, row in ret.iterrows():
valid = row.dropna()
if len(valid) < 50:
continue
ind = industry.reindex(valid.index)
neutralized = neutralize_by_group(valid, ind)
out.loc[dt, valid.index] = zscore_clip(neutralized).values
return out要点:
shift(1)是防止信号偷看同一根 bar 的收盘——典型的未来函数;- MAD 截尾对厚尾数据更稳健;如果改成
clip(quantile(0.01), quantile(0.99))也行; - 行业中性化用”组内去均值”做最简单实现,更严格的做法是对行业哑变量做横截面 OLS 取残差;
- 当横截面有效样本太少时(
< 50)跳过这一天,避免噪音放大。
9.3 回测骨架:向量化的最小回测
下面是一段向量化回测骨架。它做出三个关键工程决定:信号 t → 持仓 t+1、按 t+1 开盘价成交、滑点 + 比例费率扣除。环境:pandas 2.x、numpy 1.26。
import numpy as np
import pandas as pd
def vector_backtest(
factor: pd.DataFrame, # 因子 z-score
open_px: pd.DataFrame, # 开盘价
close_px: pd.DataFrame, # 收盘价
top_pct: float = 0.1, # 多头分位
bot_pct: float = 0.1, # 空头分位
fee_bps: float = 3.0, # 单边费率(基点)
slip_bps: float = 5.0, # 单边滑点(基点)
) -> dict:
"""多空对冲、等权、日频再平衡、市场中性。"""
# 1) 由因子在 t 决定持仓,t+1 开盘成交 → 持仓在 t+1 开始生效
rank = factor.rank(axis=1, pct=True)
long_mask = rank >= (1 - top_pct)
short_mask = rank <= bot_pct
n_long = long_mask.sum(axis=1).replace(0, np.nan)
n_short = short_mask.sum(axis=1).replace(0, np.nan)
weight = pd.DataFrame(0.0, index=factor.index, columns=factor.columns)
weight = weight.where(~long_mask, (1.0).__rdiv__(n_long), axis=0) \
if False else weight # 写法兼容,下面用更直接的方式
weight[long_mask] = (long_mask.div(n_long, axis=0))[long_mask]
weight[short_mask] = -(short_mask.div(n_short, axis=0))[short_mask]
# 2) 信号在 t 形成,权重在 t+1 起效
weight = weight.shift(1)
# 3) t+1 收益 ≈ (close_{t+1} - open_{t+1}) / open_{t+1} 用于持有期内
# 简化为 t+1 close 相对 t close 的收益,再扣除入场滑点
ret_close = close_px.pct_change()
gross = (weight * ret_close).sum(axis=1)
# 4) 换手成本:|w_t - w_{t-1}| * (fee + slip) 单边
turnover = (weight - weight.shift(1)).abs().sum(axis=1)
cost = turnover * (fee_bps + slip_bps) / 1e4
pnl = gross - cost
# 5) 指标
ann = pnl.mean() * 252
vol = pnl.std() * np.sqrt(252)
sharpe = ann / vol if vol > 0 else np.nan
max_dd = (pnl.cumsum() - pnl.cumsum().cummax()).min()
return {
"pnl": pnl,
"turnover": turnover,
"ann_return": ann,
"ann_vol": vol,
"sharpe": sharpe,
"max_drawdown": float(max_dd),
}要点:
weight.shift(1)是不可省略的一步——信号 t 形成、t+1 开始持有,回测里漏掉这一步等于用未来数据;- 换手成本用
|w_t - w_{t-1}|直接近似,单边费率与滑点合并到 bps; - 这是”向量化回测”,速度快但表达力弱:它假设无限流动性、无冲击成本、按收盘成交。第 19 篇会给出”事件驱动 + 滑点模型 + 容量约束”的完整版本。
向量化回测的最大用途是因子早期筛选——从几千个候选因子里快速挑出几十个可能有效的,再交给事件驱动回测做精测。第 19、20 篇会讲两套引擎怎么配合。
9.4 一段 IC 计算:单因子检验的最小样例
光算因子还不够,必须配套一段 IC 计算来评价它。下面这段计算每日横截面 Spearman 相关,并给出 Rank IC 的均值、标准差、IC_IR、t 统计量与衰减曲线。
import numpy as np
import pandas as pd
from scipy.stats import spearmanr
def rank_ic(
factor: pd.DataFrame, # 行=日期, 列=symbol
fwd_ret: pd.DataFrame, # 同结构,前瞻收益(已对齐到因子日期)
) -> pd.Series:
"""每日横截面 Rank IC。"""
out = {}
for dt in factor.index:
f = factor.loc[dt].dropna()
r = fwd_ret.loc[dt].reindex(f.index).dropna()
f = f.reindex(r.index)
if len(f) < 50:
continue
rho, _ = spearmanr(f.values, r.values)
out[dt] = rho
return pd.Series(out).sort_index()
def ic_summary(ic: pd.Series) -> dict:
n = ic.dropna().shape[0]
mean = ic.mean()
std = ic.std(ddof=1)
return {
"n_days": n,
"ic_mean": mean,
"ic_std": std,
"ic_ir": mean / std if std > 0 else np.nan,
"t_stat": mean / (std / np.sqrt(n)) if std > 0 else np.nan,
"win_rate": (ic > 0).mean(),
}
def ic_decay(
factor: pd.DataFrame,
px: pd.DataFrame,
horizons: list[int] = [1, 5, 10, 20, 60],
) -> pd.DataFrame:
"""衰减曲线:不同前瞻天数下的 Rank IC 均值。"""
rows = []
for h in horizons:
fwd = px.pct_change(h).shift(-h)
ic = rank_ic(factor, fwd)
s = ic_summary(ic)
rows.append({"horizon": h, **s})
return pd.DataFrame(rows)判定准则(仅做参考,具体阈值与策略类型有关):
ic_mean量级:日频股票多因子典型在 0.02–0.05 之间,超过 0.10 多半是数据问题;ic_ir≥ 0.5 通常被视为可考虑入库;t_stat≥ 2 视为统计显著;- 衰减曲线半衰期短于调仓周期,因子等于在错过;半衰期长说明信号缓慢,调仓频率可降低。
9.5 关于 numba 与 vectorbt
回测对性能敏感时,用 numba 加速核心循环、用 vectorbt 做大规模参数扫描是常见选择。两者的边界:
- vectorbt:适合参数网格搜索、多策略并行、可视化分析,底层是 numba+numpy;
- numba:适合自己写事件驱动撮合循环,单次回测的微秒级优化;
- 纯 pandas/polars:适合早期向量化筛选,写得快但性能上限低。
第 19 篇会把三种方案在同一个策略上做基准对比,给出选择建议。
十、回测引擎选型:四类方案的工程取舍
回测引擎是所有量化系统的核心基础设施之一。市面上主流方案大致分四类,工程取舍不同。
10.1 向量化回测(Vectorized)
代表:自己写的 pandas/numpy/polars 流水线、vectorbt。
- 优点:极快,一次回测可以在秒级完成;适合参数扫描、因子筛选;
- 缺点:表达力弱——很难表达”订单挂在某价位 30 秒后未成交则撤”这类逻辑;难以处理冲击成本、跨日订单状态;
- 适用:因子早期筛选、单一调仓频率的截面策略、资产配置类策略。
10.2 事件驱动回测(Event-Driven)
代表:zipline、backtrader、自研事件循环。
- 优点:能精确表达订单生命周期、撮合假设、多场所、跨周期信号;
- 缺点:慢,单次回测可能要几分钟到几小时;写起来比向量化复杂;
- 适用:上线前精测、HFT/做市策略、跨期跨品种、复杂订单类型。
10.3 模拟撮合(Tick-level Simulation)
代表:自研 tick 级订单簿重建 + 撮合模拟器。
- 优点:能在 tick 级别复盘,精确到撮合优先级与队列位置;
- 缺点:极慢且贵,需要 tick 数据,存储与计算成本高;
- 适用:HFT、做市、订单路由策略。
10.4 影子账户(Paper Trading)
代表:把信号接到真实行情、模拟下单、影子持仓核算。
- 优点:完全消除回测假设偏差,最贴近实盘;
- 缺点:需要时间——一个月就是一个月,无法加速;
- 适用:上线前的最终验证。
四类方案在一个完整研究流水线里通常会组合使用:向量化做粗筛 → 事件驱动做精测 → 影子账户做最终验证。第 19 篇会给出三套环境如何对齐口径。
十一、生态与工具栈
下面这张表列出本系列默认假设的工具栈。读者用别的工具栈也行,关键是把每一类需求找到对应工具。
| 类别 | 默认工具 | 备选 |
|---|---|---|
| 数据处理(DataFrame) | polars 1.x、pandas 2.x | duckdb |
| 数值计算 | numpy 1.26 | jax |
| 加速 | numba | cython、rust pyo3 |
| 时序数据库 | clickhouse、questdb | influxdb、timescaledb |
| 列存 | parquet(zstd 压缩) | orc |
| 消息总线 | redis stream、kafka | nats、pulsar |
| 回测向量化 | vectorbt | bt、backtesting.py |
| 回测事件驱动 | zipline-reloaded、backtrader | 自研 |
| 组合优化 | cvxpy + ECOS/Clarabel | scipy.optimize、pyportfolioopt |
| ML 模型 | lightgbm、xgboost、catboost | sklearn |
| 深度学习 | pytorch | tensorflow |
| 行情接入 | ctp(期货)、xtquant、ib_insync | 直连交易所 SDK |
| 可视化 | plotly、altair | matplotlib、seaborn |
| 监控 | prometheus + grafana | datadog |
后续每一篇都会标注它使用的工具版本。当某段代码依赖特定版本时(例如 polars 0.x 与 1.x 之间 API 变了),会显式声明。
十二、常见误区与反模式
工程链路上反复看到的几个反模式,列出来作为反例:
12.1 “回测漂亮就上线”
漂亮的回测有 N 种来源:未来函数、生存者偏差、过拟合、容量假设、滑点假设过乐观、费率忘记扣、训练样本与检验样本未隔离、参数在几百次试错后挑出来的。任何一种都能让回测看起来比实盘强 5–10 倍。
工程上的对策:每一份回测必须配套交付以下检查:
- PIT 校验脚本(往前挪一天结果是子集);
- 生存者偏差校验(对比”含已退市”和”不含已退市”的差异);
- 容量校验(按 ADV 折算后的净 Alpha);
- 样本外校验(最后一段不许碰的数据);
- 参数稳健性(参数邻域内 Sharpe 标准差)。
12.2 “把研究脚本直连实盘”
最危险的反模式。研究环境通常没有风控、没有限速、没有幂等、没有状态恢复。直连实盘的后果是策略 bug 直接变成资金事故。
工程上的对策:研究环境永远不能拿到真实账户的下单凭证。下单链路必须经过 OMS/EMS,OMS/EMS 必须开启所有风控开关。第 27 篇会给出环境隔离的具体方案。
12.3 “风控写在策略代码里”
风控如果和策略写在一起,策略 bug 会一并把风控绕过。Knight Capital 2012 年的 4.4 亿美元事故就是典型——老代码模块被误激活,没有独立风控通道兜底。
工程上的对策:风控必须是独立通道,物理上独立的进程、独立的部署、独立的代码仓库(或至少独立的代码路径)。策略再怎么 bug 也不能污染风控状态。
12.4 “本地估算持仓”
很多新写的系统会把”持仓”算成本地状态:上次目标 + 上次成交 = 当前持仓。问题是网络抖动、回报丢失、撤单回报延迟都会让本地估算和交易所真实持仓漂移。漂移一旦发生,风控就会基于错误数据放行错误订单。
工程上的对策:本地状态只作为辅助,真实持仓以交易所回报为准,每分钟(或每次下单前)做一次对账,发现漂移就停止下单并告警。
12.5 “复盘换口径”
实盘表现不好,复盘时把回测的滑点假设调高一点、费率算少一点,让数据”对得上”。这是自欺。
工程上的对策:回测口径和复盘口径字段级一致,由同一份配置文件生成。任何口径修改必须同步两端,并对历史所有跑过的回测重跑一遍。第 22 篇会给出口径管理的工程做法。
12.6 “用平均数掩盖尾部”
夏普比率假设收益正态分布,对厚尾不敏感。一个策略 250 个交易日里 248 天小赚、2 天大亏 50%,年终统计看 Sharpe 仍然不错,但实盘上你扛不住那 2 天。
工程上的对策:评价策略至少看四个指标:Sharpe、最大回撤、Calmar(年化收益/最大回撤)、收益分布的偏度峰度。任何一个不达标都不上线。第 22 篇会展开指标体系。
十三、一份不变量清单
把整篇文章的工程要点压缩到一份清单,写在墙上每天看一遍,能避免 80% 的事故:
- 数据:所有事实表带
asof,查询永远WHERE asof <= t; - 复权:保留原始价 + 按
asof的复权因子表,禁止用”今日最新”复权数据回测; - 停牌:不参与因子计算、不参与撮合、不参与持仓估值;
- 因子:所有滚动窗口右对齐,所有标准化用滚动或扩展窗口,禁止整体标准化;
- PIT 校验:把
decision_date往前挪一天,结果必须是子集; - 撮合:信号 t 形成、t+1 开盘价或 VWAP 成交,禁止当 bar 收盘成交;
- 费率:佣金、印花税、过户费、滑点全扣,单边和双边分清楚;
- 订单:每笔下单带全链路唯一
client_order_id,与回报匹配; - 风控:独立通道、安全默认拒绝、毫秒级熔断;
- 状态:实时持仓以交易所回报为准,本地估计只能作为辅助;
- 复盘:复盘口径与回测口径完全一致,否则不可比;
- 环境隔离:研究/回测/实盘的代码、数据、时间引擎严格分离,禁止”研究脚本直连实盘账户”。
每一条都对应后续某一篇的工程方法。本系列的所有内容,本质上都是在帮你把这份清单从一行字落到一行代码。
十四、结语
量化交易的工程难度,不在任何一段单独的代码,而在八段链路必须同时正确。一根 K 线数据进来时不被时间错位污染、一个因子算出来时不偷看未来、一份目标持仓在执行时不被滑点吃掉、一笔成交回报回来时能精确匹配上原订单、一次崩盘时风控能在毫秒内把链路熔断——这些事任何一件单看都不难,难的是 365 天每天都对。
这个系列的目标,是让读者读完之后,对每一段链路都有”我知道哪里会出事、我知道怎么守住”的信心。后续 27 篇会把上面的每一条不变量从一句话展开到几千字 + 代码 + 实测。第 2 篇先从市场结构开始:A 股、美股、期货、加密各自的撮合机制、订单类型、交易制度,这是所有后续策略代码的底层假设。
参考文献
书籍
- Marcos López de Prado. Advances in Financial Machine Learning. Wiley, 2018. 第 1–4 章关于数据结构、特征工程、生存者偏差的论述是本系列数据章节的主要参考。
- Marcos López de Prado. Machine Learning for Asset Managers. Cambridge University Press, 2020. 协方差矩阵收缩、组合优化的稳定性方法。
- Robert Kissell. The Science of Algorithmic Trading and Portfolio Management. Academic Press, 2013. 执行算法与冲击成本模型的工业级参考。
- Rishi Narang. Inside the Black Box: A Simple Guide to Quantitative and High-Frequency Trading. Wiley, 2nd ed., 2013. 量化系统分层与买卖方分工的入门读物。
- Maureen O’Hara. Market Microstructure Theory. Blackwell, 1995. 市场微观结构经典教材。
- Larry Harris. Trading and Exchanges: Market Microstructure for Practitioners. Oxford University Press, 2003. 工业界视角的微观结构。
- Ernest Chan. Algorithmic Trading: Winning Strategies and Their Rationale. Wiley, 2013. 统计套利、均值回归类策略的工程化处理。
论文
- Eugene F. Fama, Kenneth R. French. “Common risk factors in the returns on stocks and bonds.” Journal of Financial Economics, 33(1), 1993. 多因子模型的原始论文。
- Narasimhan Jegadeesh, Sheridan Titman. “Returns to Buying Winners and Selling Losers: Implications for Stock Market Efficiency.” Journal of Finance, 48(1), 1993. 动量异象的开山之作。
- Olivier Ledoit, Michael Wolf. “Honey, I Shrunk the Sample Covariance Matrix.” Journal of Portfolio Management, 30(4), 2004. 协方差矩阵收缩。
- Robert Almgren, Neil Chriss. “Optimal Execution of Portfolio Transactions.” Journal of Risk, 3, 2001. 最优执行的经典论文,IS 算法的理论基础。
规范与文档
- FIX Protocol Specification, https://www.fixtrading.org。订单协议工业标准。
- NASDAQ TotalView-ITCH 5.0 Specification。Level 3 行情协议参考。
- 上海证券交易所交易规则(最新版)。A 股 T+1、涨跌停、集合竞价规则的权威文本。
- 中国金融期货交易所交易规则(最新版)。股指期货交易制度。
工具与库
- polars,https://pola.rs。本系列默认数据处理工具之一。
- vectorbt,https://vectorbt.dev。参数扫描与向量化回测。
- numba,https://numba.pydata.org。事件驱动回测的性能加速。
- zipline-reloaded,https://github.com/stefan-jansen/zipline-reloaded。事件驱动回测引擎参考实现。
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
量化交易
从因子研究到生产执行的量化交易全栈工程。覆盖市场微结构、数据管线、因子构造、组合优化、回测方法论、执行算法、做市策略、高频架构到生产运维。面向策略研究员与工程师。
【量化交易】机器学习选股:标签构造、防过拟合、SHAP 归因
把机器学习选股从「跑一个 LightGBM 看 AUC」还原为标签构造、特征中性化、训练协议、模型解释、上线监控五个独立工程问题。重点讨论 Triple-Barrier 标签、Purged K-Fold 与 Embargo、SHAP 归因,给出可直接套用的 Python 代码骨架。
【量化交易】回测引擎设计:事件驱动与向量化
把回测引擎当成一套工程系统讲清楚:事件驱动架构、撮合保真度、滑点嵌入、多频率多账户、并行加速、回放对账。给出可运行的最小事件驱动回测器与 vectorbt 向量化对照实现。
【量化交易】回测陷阱:前视偏差、过拟合、数据窥视
回测引擎只能保证「语法对」,但真正杀死策略的是「逻辑错、数据脏、推断不严」三件事。本文系统拆解前视偏差(lookahead bias)、过拟合(overfitting)、数据窥视(data snooping)三大陷阱,介绍 Bonferroni、BH-FDR、Family-Wise Error 的多重检验修正,给出 Deflated Sharpe 与概率 Sharpe(PSR)的可运行 Python 实现,配一份 30 条上线前自检清单。