数据认知
先看一个「假暴跌」
Section titled “先看一个「假暴跌」”某只股票昨天的收盘价是 34.70 元,今天一开盘,行情软件上显示昨收价变成了 17.35 元——价格「腰斩」了。如果你只看价格序列,会以为这家公司一天跌了 50%,发生了什么重大利空。
实际上什么都没发生。这家公司今天「10 送 10」:每持有 1 股,公司白送你 1 股,你的股数翻倍,每股价格减半,你账户里的总市值一分没变。这就是除权。行情软件上的价格「跳空」,是股本变化造成的记账口径变化,不是真实的下跌。
这个例子说明一件事:原始行情数据不会自动告诉你真相。做量化研究,第一步不是写策略,而是搞清楚手里的数据到底是什么口径、有什么坑。本章就把日频研究最常用的数据——K 线数据——从字段到陷阱过一遍。
OHLCV:一根 K 线里的五个字段
Section titled “OHLCV:一根 K 线里的五个字段”日线数据里,每一天用五个数字概括,合称 OHLCV。用 A 股的说法:
- 开盘价(Open):当天第一笔成交的价格。A 股由 9:25 集合竞价产生;
- 最高价(High):当天所有成交里的最高价格;
- 最低价(Low):当天所有成交里的最低价格;
- 收盘价(Close):当天最后一笔成交的价格,也是计算涨跌幅、收益率最常用的基准;
- 成交量(Volume):当天成交的总股数(A 股行情软件常以「手」为单位,1 手 = 100 股)。
四个价格之间有一个天然的约束:最高价 ≥ 开盘价、收盘价 ≥ 最低价。任何一根不满足这个约束的 K 线都是脏数据,后面会讲怎么批量检查。
四个价格描述了「这一天价格走过的轨迹」,成交量描述了「有多少人参与了这段轨迹」。同样的涨幅,放量和缩量背后的含义完全不同——这是第二编很多策略会用到的事实。
复权:把「假跳空」修平
Section titled “复权:把「假跳空」修平”为什么需要复权
Section titled “为什么需要复权”除权除息(送股、转增、分红、配股)会让股价出现与涨跌无关的跳空。如果不处理,直接用原始价格算收益率,10 送 10 这天会算出 −50% 的「收益」,整个回测都是错的。
处理方法叫复权:选定一个口径,把历史价格按复权因子换算到同一口径下,让价格序列在除权日前后保持连续。常用的是前复权——以最新价格为基准不动,把除权日之前的历史价格向下换算。
公式:复权因子
Section titled “公式:复权因子”
人话解释: 是第 天行情软件上的原始(不复权)收盘价, 是同一天复权后的收盘价, 就是两者之间的换算比例。除权日之后没有发生股本变化,复权价等于原始价,;除权日之前,原始价是复权价的两倍,。拿到某一天的价格想换算口径,乘或除以当天的复权因子就行:。
手算一遍:一只股票除权日前一天收盘价 20.00 元,实施 10 送 10——每 1 股变成 2 股。总市值不变,所以除权日的参考价为:
于是复权因子 。做前复权:除权日及之后 ,价格不动;除权日之前 ,历史价格都除以 2。比如除权前三天的收盘价是 19.60、19.80、20.00,前复权后变成:
序列从 ……9.80、9.90、10.00 平滑地接到除权日的 10.00 元附近,假跳空消失。验算一下持有人的账:除权前持有 1 股值 20 元,除权后持有 2 股 × 10 元 = 20 元,市值不变,真实收益率是 0%——而复权后的价格序列正好也显示 0% 附近的涨跌,对上了。若用不复权价格算,会得到 ,这就是错的那 50 个百分点。
图:不复权 vs 前复权
Section titled “图:不复权 vs 前复权”下面用合成数据演示同样一段行情的两个口径:2025-05-26 那天实施 10 送 10(第 61 个交易日)。不复权价格当天从 34.70 直接跳到 17.31,像暴跌;前复权价格则平滑地跨过这一天,看不出任何异常。
研究时用哪个口径? 算收益率、算指标,一律用复权价;涉及「当时的真实挂单价格」(比如模拟涨跌停能不能成交)时要记得复权价不是当时的真实价格。最稳妥的做法是两套都存着,按用途取用。
停牌与涨跌停:价格存在 ≠ 你能成交
Section titled “停牌与涨跌停:价格存在 ≠ 你能成交”回测里最常见的隐藏假设是「信号出现的价格,我一定能成交」。A 股的两条规则经常让这个假设失效:
- 涨跌停限制:主板个股单日 ±10%,创业板、科创板 ±20%,北交所 ±30%,ST 股 ±5%(主板)。一旦封死涨停,你想买买不进;封死跌停,你想卖卖不出。回测如果假设「跌停价卖出成功」,会把亏损严重低估。
- 停牌:公司可以因重大事项暂停交易,短则一天,长则数月。停牌期间没有成交、没有新价格,你的仓位被锁死,任何卖出信号都无法执行。数据上表现为日期序列里凭空缺一段,或者价格连续多天不变。
另外别忘了 T+1:当天买入的股票,最快下一个交易日才能卖出。回测里「今天买、今天卖」的日内操作在 A 股个股上是不合法的(底仓除外)。
对回测的直接要求:模拟成交前先检查「这一天这个方向能不能成交」——停牌日不能成交,封死涨停不能买入,封死跌停不能卖出,买入当天不能卖出。做不到这几条,回测收益会系统性地偏乐观。
幸存者偏差:回测池里全是「活下来的」
Section titled “幸存者偏差:回测池里全是「活下来的」”人话:你今天能下载到的股票名单,是活到今天的股票。退市、破产、被合并的公司已经从名单里消失了。如果你拿着「当前沪深 300 成分股」回测十年前开始的策略,等于只考察了这十年里的赢家——那些当年也在指数里、后来跌没了的股票被悄悄剔除了。这样测出来的收益,是「幸存者」的收益,不是策略真实的收益。
具体例子:有人说「长期持有白马股稳赚不赔,你看过去十年这些龙头翻了多少倍」。问题在于「这些龙头」是用今天的视角挑出来的。十年前同样被称为白马的公司里,有的已经退市、有的跌去九成。用今天的名单回测过去,起点就自带正确答案,结论没有参考价值。
对策:回测的股票池必须使用「当时的历史成分」,退市股票要留在样本里(允许它的净值跌到底),数据源要能还原任意时点的全市场名单。
未来函数:偷看了明天的答案
Section titled “未来函数:偷看了明天的答案”人话:策略在第 天做决策时,只能用第 天收盘前(或开盘前)已经知道的信息。如果计算里混入了 天之后才知道的数据,这个策略就含有未来函数——回测里它神勇无比,实盘里必然失效,因为实盘那一刻那些数据还不存在。
具体例子:一个信号写成「当收盘价站上 20 日均线时,当天以收盘价买入」。问题在细节里:收盘价本身要等 15:00 收盘才知道,而「以收盘价买入」意味着你 15:00 就成交了——你用了成交那一刻才确定的价格来决定要不要成交。正确的写法是「收盘价站上 20 日均线,则次日开盘价买入」。
常见变种:用当天的最高价判断是否「曾经突破」并假设在突破价成交(突破价和最高价是同一天的);用全样本的均值方差做标准化(均值方差包含了未来数据);财报数据按报告期而不是实际披露日对齐(年报的数字 1 月 1 日还不存在,4 月底才披露完)。
对策:每写一个信号,问一句「实盘这一刻,这个数字我能拿到吗?」拿不到,就整体后移一天。
标的池清洗:从 5000+ 到可交易池的过滤漏斗
Section titled “标的池清洗:从 5000+ 到可交易池的过滤漏斗”很多初学者拿到全市场 5000 多只 A 股的历史日线,直接整锅端进回测引擎。结果策略挑中了一堆「神奇股票」:要么是一天成交几万元的小仙股,要么是刚上市连拉十个一字涨停的次新股,要么是濒临退市的 ST 股。回测收益极其惊人,实盘一单都买不进。
做全市场选股策略之前,必须先给股票池装一个过滤漏斗。每一天收盘后,只有穿过漏斗的股票,才有资格进入候选池:
- *剔除 ST、ST 股票:ST 股票面临退市风险、每日涨跌停为 ±5%(主板)、机构投资者普遍有入库限制。让量化模型去赚 ST 股重组博弈的钱,本质上是在承担制度毁灭风险。
- 剔除上市不满 N 天(如 60 或 180 天)的次新股:新股上市初期换手率极高、估值剧烈波动,且缺乏足够长的历史数据计算 60 日均线、半年波动率等指标。包含次新股会把大量噪音频繁注入模型。
- 剔除当日停牌与一字涨跌停股票:如前文所述,一字涨停买不进、一字跌停卖不出。若不剔除,引擎会默认以涨停价顺利建仓,制造「买入即封板」的假象。
- 剔除流动性尾部股票(如过去 20 日日均成交额后 10%):日均成交几百万的股票,几十万资金入场就会把盘口打穿(冲击成本巨大)。回测里按收盘价撮合成交,实盘你就是这只股票最大的对手盘。
输入:全市场所有股票池 all_stocks,交易日 t输出:当日可交易候选股票池 tradable_pool
对每只股票 stock ∈ all_stocks: 若 stock 在 t 日带有 ST 或 *ST 标识:跳过 若 stock 的上市天数 < 60 天:跳过 若 stock 在 t 日全天停牌:跳过 若 stock 在 t 日一字涨停(开盘价 = 最高价 = 最低价 = 收盘价 且 涨幅触及上限):跳过 若 stock 过去 20 日的日均成交额处于全市场后 10%:跳过
将 stock 加入 tradable_pool这个漏斗每天动态执行。有了它,策略考察的才是「真实可交易、有流动性保障」的标的。
伪代码:K 线合法性校验
Section titled “伪代码:K 线合法性校验”拿到一份日线数据,先别急着算,跑一遍校验流程,把脏数据挡在门外:
对数据中的每一根 K 线(每一天): 检查 最高价 >= 最低价,否则标记为错误行 检查 开盘价 在 最低价 与 最高价 之间 检查 收盘价 在 最低价 与 最高价 之间 检查 四个价格都大于 0 检查 成交量 >= 0 与前一交易日比较: 计算 涨跌幅 = 收盘价 / 前收盘价 - 1 检查 涨跌幅 是否超出该板块的涨跌停限制 (主板 ±10%,创业板/科创板 ±20%,超出且无除权除息则存疑) 检查 日期是否比前一根 K 线更晚(防止重复或乱序) 若日期出现大段空缺: 确认是否为停牌;是停牌则标记,不当成缺失数据删除
任何一项检查失败: 记录该行的日期和失败原因 该行不进入后续的收益计算,先查数据源修正这套检查花不了几分钟,却能拦住大部分「数据本身错了,策略背锅」的事故。
- 用不复权价格直接算收益率。除权日凭空多出 −50% 的「亏损」,或者分红被算成下跌。本章开篇的例子就是这条。收益率计算一律用复权价。
- 用了未来数据而不自知。收盘价的信号按收盘价成交、用全期统计量做标准化、财报按报告期对齐——回测收益好得不像真的,通常就不是真的。
- 假设涨跌停、停牌时也能成交。回测里跌停板轻松逃顶,实盘里挂单三天卖不掉。成交假设必须过「能不能成交」这道检查。
- 拿今天的成分股回测十年前。幸存者偏差让结论天然偏乐观,而且偏多少你都不知道。
- 不洗股票池,回测全是流动性陷阱。没做过滤漏斗,策略在回测里频繁买入次新一字板和退市整理期股票,看似年化翻倍,实盘第一天就无法下单。
数据这关过了,下一章给数据配上统计学的语言:收益率的分布、均值与方差到底意味着什么、以及为什么两个例子什么也证明不了。