试想你要开一家新餐厅。厨师长会直接把整本菜谱挂出去营业吗?当然不会。他一定会在后厨反复试做:按菜谱下料、调火候、请朋友试吃、记录反馈、调整配方,直到确认这道菜“拿得出手”,才敢把它写进正式菜单。量化交易也是如此——AI 模型每次产出的买卖策略都像一道新菜,而回测框架就是量化团队的那间“试菜厨房”:用历史行情当“往日菜价记录”,在投入真金白银之前,把整套交易逻辑完整彩排一遍。本文会带你走进这间试菜厨房:认识事件驱动的私房菜做派、向量化的中央厨房批量出菜,学会不偷看未来菜价(前视偏差)、把采购损耗算进成本(滑点),并用多厨并行、交叉盲测的方式筛出最稳健的菜谱。

岗位关联

对 AI 从业者而言,回测框架是把模型策略从“纸面想法”推向“实盘收益”的质检关卡,掌握它等于掌握了评估工作成果的精确秤。

目录
  1. 可视化演示
  2. 回测的价值
  3. 事件驱动回测
  4. 向量化回测
  5. 前视偏差控制
  6. 滑点与成本模拟
  7. 过拟合检测
  8. 性能优化
  9. 并行与工程落地
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示事件驱动与向量化回测差异、前视偏差控制与滑点模拟过程,可切换三种模式观察回测结果如何更真实可靠。

本节配有交互动效(进入视口后自动加载,约 3KB)。

事件驱动 vs 向量化 事件驱动逐条处理行情,模拟真实交易但较慢 向量化批量计算向量,速度快但抽象程度高 速度 vs 真实度 按复杂订单逻辑选择架构。

回测的价值

开一家新餐厅,厨师长会直接把整本菜谱挂出去营业吗?当然不会。他一定会在后厨反复试做:按菜谱下料、调火候、请朋友试吃、记录反馈、调整配方,直到确认这道菜“拿得出手”才敢写进正式菜单。量化交易里的回测框架,就是量化团队的这间“试菜厨房”——在投入真金白银之前,用历史行情作为“往日菜价记录”,把一套交易逻辑完整地彩排一遍。

量化策略本质上是一条“自动化买卖菜谱”:什么价位买入(开仓)、什么价位卖出(平仓)、每次买多少(仓位管理)。回测(Backtesting)要做的事情,就是把这个菜谱放到过去若干年的行情数据上,模拟每一笔交易,最后算出这套策略如果真实运行过会赚多少钱、波动多大。它回答的问题很简单:这道菜,到底值不值得上正式菜单?

回测框架最基础的评价指标是收益率。假设期初投入资金为 $V_{start}$,回测结束时账户资产为 $V_{end}$,则简单收益率可以用下面的公式计算:

当然,收益率只是第一道“口味评分”。真正专业的试菜还要看波动、回撤、夏普比率等指标,就像评价一道菜不能只看卖相,还要看它是否稳定、是否经得起客人反复点。接下来,我们走进两间风格不同的“试菜厨房”——事件驱动与向量化回测。

Rp=Vend−VstartVstart×100%\displaystyle R_p = \frac{V_{end} - V_{start}}{V_{start}} \times 100\%
通俗理解:用试菜结束时的账上余额减去试菜前的本金,再除以本金,得到“每一块钱赚了还是亏了多少百分比”。
  1. $V_{end}$回测结束时的账户总资产,相当于试菜结束时账上的最终余额。
    VendV_{end}
  2. 减去 $V_{start}$用结束值减期初本金,得到试菜期间的净赚或净亏金额。
    Vend−VstartV_{end} - V_{start}
  3. 除以 $V_{start}$把净赚净亏金额换算成“每投入 1 元赚了多少钱的比例”,消除本金规模的影响。
    Vend−VstartVstart\frac{V_{end} - V_{start}}{V_{start}}
  4. 乘以 100%把小数转成百分比,方便与银行理财、指数涨幅等常见数字做直观对比。
    ×100%\times 100\%

事件驱动回测

私房菜馆的做派是客人点什么,厨师就一道一道现场做:下单、切菜、起锅、上桌,每一步都在真实的后厨发生。事件驱动回测就是这种“私房菜做派”——它不会把整段历史行情一次性算完,而是把行情报价、信号触发、订单成交都看作一个个“事件”,像接待客人一样按时间顺序逐个处理。

事件驱动回测(Event-driven Backtesting)维护一个事件队列和一口“计时钟”。每来一个新事件,系统就检查当前持仓是否需要调整:如果均线金叉信号触发,就模拟按当前价格买入;如果信号消失,就模拟卖出。每一步都会记录成交价、手续费和当时的资金变化。这种写法像后厨的流水线账本,每一笔都清清楚楚。

这个框架最核心的环节是“撮合决策”——根据当前信号决定持仓状态。设 $Position_t$ 表示 t 时刻的持仓,$Signal_t$ 表示策略发出的信号,则:

事件驱动的优势在于真实——它能精细处理涨跌停无法成交、停牌、成交滑落等现实细节,像私房菜一样“每一步都有讲究”;缺点是慢,每一笔都要走完整流程。如果只是想知道“这道菜大概好不好吃”,有没有更快的办法?下一节的“中央厨房”给出了答案。

Positiont={+1if Signalt=BUY0if Signalt=HOLD−1if Signalt=SELL\displaystyle Position_t = \begin{cases} +1 & \text{if } Signal_t = BUY \\ 0 & \text{if } Signal_t = HOLD \\ -1 & \text{if } Signal_t = SELL \end{cases}
通俗理解:后厨接到不同类型的订单(买入、观望、卖出),决定此时锅里要放多少货、是买进还是卖出。
  1. $Position_t$表示 t 时刻账户的持仓状态,决定了此时手里握着多少“食材”仓位。
    PositiontPosition_t
  2. 分支条件判断系统像后厨接单一样,把信号 $Signal_t$ 分成三类:买入信号 (+1)、观望信号 (0)、卖出信号 (-1),各自对应一种持仓动作。
    if Signalt=BUY/HOLD/SELL\text{if } Signal_t = BUY / HOLD / SELL
  3. 选择结果每个时刻选中的持仓值会被代入收益计算,逐步累加成整条资金曲线,就像一道道菜最终汇成一桌宴席的利润。
    {+10−1\begin{cases} +1 \\ 0 \\ -1 \end{cases}

向量化回测

大型连锁餐厅的中央厨房,会把几百份同款菜一次性批量备好,而不是等客人来了才开火。向量化回测(Vectorized Backtesting)沿用了这个思路:把多年的历史行情看作整批“食材”,用矩阵和向量运算一次性算出每一天“如果按当前仓位持有会赚多少”。

向量化回测的关键是构造收益率序列。给定每日收盘价序列 $P_t$,相邻两天的收益率:

有了收益率向量,再配合策略给出的持仓向量做逐元素相乘、累加,就能快速得到整体资金曲线。矩阵库(如 NumPy)能在极短时间内完成几十万行的计算。这让向量化回测特别适合初期海选:快速过滤掉明显不靠谱的菜谱,再把少数候选交给事件驱动精测。不过“快速”有代价——向量化难以模拟涨跌停不成交、停牌等真实约束,精度上更像“理论口味”。聪明的团队会把两者搭配成完整的研发流水线:用中央厨房的粗筛速度,保私房菜的最终精度。

Rt+1=Pt+1−PtPt\displaystyle R_{t+1} = \frac{P_{t+1} - P_t}{P_t}
通俗理解:今天的菜价相对昨天涨了百分之几,就是今天的“收益率”。
  1. $P_{t+1}$ 与 $P_t$分别表示相邻两个交易日的收盘价,好比“今天的菜价”和“昨天的菜价”。
    Pt+1,PtP_{t+1}, \quad P_t
  2. 相减$P_{t+1} - P_t$ 算出今天比昨天涨跌了多少“绝对金额”。
    Pt+1−PtP_{t+1} - P_t
  3. 除以昨天价格把涨跌金额除以昨天的价格 $P_t$,得到“相比昨天涨了百分之几”的收益率,消除了价格绝对水平的影响。
    Pt+1−PtPt\frac{P_{t+1} - P_t}{P_t}

前视偏差控制

试菜时如果厨师长先翻开明天的菜价单,再决定今天买什么菜,朋友们的“好吃”评价就毫无意义——作弊得来的成绩单不能预测真实营业。回测中的前视偏差(Look-ahead Bias),就是一切“偷看未来”的行为。

最常见的前视偏差有两类。第一类是“未来函数”:策略代码本应只用 t 时刻及之前的数据来做 t 时刻的决策,却误用了 t+1 时刻甚至更晚的数据(比如用了未来财报数据、用全样本统计量做归一化)。第二类是“幸存者偏差”:研究者用今天还活着的股票列表回测过去十年,早已退市、暴跌的股票被悄悄剔除,等于只统计活下来的名菜,收益自然虚高。

控制前视偏差要守住一条“信息边界”:t 时刻的决策只能依赖当时信息集 $I_t$,任何未来信息都不得进入。

守住这条红线,回测结果才有“预测未来”的资格。但即便不偷看未来,还有一件每天都会发生的麻烦事——真到了菜市场,你根本买不到价目表上那个完美价格。

It={P1,P2,…,Pt},Decisiont=f(It)\displaystyle I_t = \{ P_1, P_2, \ldots, P_t \}, \qquad Decision_t = f(I_t)
通俗理解:后厨今天买什么菜,只能参考今天之前写下的价目表,不能翻开明天的记录。
  1. $I_t$t 时刻已知的全部历史信息集合,从第 1 天到第 t 天的价格等数据,是决策可用的“合法食材清单”。
    It={P1,P2,…,Pt}I_t = \{ P_1, P_2, \ldots, P_t \}
  2. $Decision_t$策略在 t 时刻做出的买卖决策,它只能是信息集 $I_t$ 的函数 $f$,即“看完合法清单才下单”。
    Decisiont=f(It)Decision_t = f(I_t)
  3. 时间对齐约束公式左右下标都是 t,若出现 $P_{t+1}$ 就说明偷看了未来,回测结果必然虚高,必须通过数据对齐严格禁止。
    t 时刻决策仅依赖 t 时刻已知信息t \text{ 时刻决策仅依赖 } t \text{ 时刻已知信息}

滑点与成本模拟

厨师长拿着昨天的价目表去买菜,到了市场发现今天的价格已经变了;如果一次要买几百斤,摊主甚至还会顺势涨价。这种“实际成交价偏离信号价”的现象,在量化交易里叫滑点(Slippage)。

滑点的来源主要包括:市场短时波动、订单量过大对价格产生的冲击、以及下单与成交之间的时间差。回测如果完全忽略滑点,资金曲线就像一个“从不被涨价、从不付运费”的完美买菜计划,长期来看必然高估收益。把成本计入账本,才是对试菜诚实的做法。

工程上,最常见的做法是在信号价格上叠加一个惩罚项。假设策略看到信号价 $P_{signal}$ 后下单,模拟的实际成交价为:

把滑点、手续费一起计入回测账本后,报告才具备参考价值。很多人走到这一步就以为万事大吉——但别忘了,还有一道更隐蔽的坎:菜谱可能在试菜时完美,一开张就翻车。

Pexec=Psignal+δ×σ+λ×Q\displaystyle P_{exec} = P_{signal} + \delta \times \sigma + \lambda \times Q
通俗理解:真实买菜价 ≈ 理想价 + 市场波动造成的偏移 + 买太多把价格推高的部分。
  1. $P_{exec}$模拟出的实际成交价,也就是回测账本里真正记账的价格,是“实际付出去的钱”。
    PexecP_{exec}
  2. $\delta \times \sigma$滑点由市场波动率 $\sigma$ 放大一个固定系数 $\delta$ 得到,波动越剧烈,成交价偏离信号价越明显。
    δ×σ\delta \times \sigma
  3. $\lambda \times Q$冲击成本项,下单量 $Q$ 越大,对市场价格的推动越显著,就像买几百斤菜会哄抬菜价一样。
    λ×Q\lambda \times Q

过拟合检测

为了讨好试菜评委,厨师长反复微调调料的克数,直到评委们连连点头。可正式开张后,新来的客人尝了一口就皱眉:“这菜太咸了。”菜谱“过拟合”了——它背熟了评委的口味,却没有学会大众的味蕾。

回测过拟合(Overfitting)指策略在历史数据(样本内)上表现极佳,但换到没见过的数据(样本外)就大幅衰减。原因是调参过程中,参数被反复打磨到贴合历史噪声,而不是抓住真实规律。检测过拟合最基础的方法是样本外测试:把历史数据切分成训练段与验证段,前者用于调参,后者用于检验。

样本外收益的计算如下,其中 $N_{OOS}$ 为样本外交易日数量:

如果样本内收益很高而样本外明显缩水,几乎可以判定过拟合。更稳健的做法还有滚动交叉验证(多换几批客人盲测)和参数敏感性分析(微调配方参数,看表现是否忽上忽下)。通过考验的菜谱终于可以批量复制了,但新的瓶颈随之而来——出菜太慢。

ROOS=1NOOS∑t=1NOOSRt\displaystyle R_{OOS} = \frac{1}{N_{OOS}} \sum_{t=1}^{N_{OOS}} R_t
通俗理解:把菜谱交给一批从未尝过的客人,看他们这桌的平均满意度如何。
  1. $R_t$样本外每一天的策略日收益,来自前视偏差控制后的事件驱动或向量化回测结果。
    RtR_t
  2. 求和 $\sum$把样本外所有交易日的收益累加起来,得到样本外总收益。
    ∑t=1NOOSRt\sum_{t=1}^{N_{OOS}} R_t
  3. 除以 $N_{OOS}$求平均得到样本外日均收益,反映“换个市场环境每天平均表现如何”。
    1NOOS\frac{1}{N_{OOS}}

性能优化

餐厅火了,后厨开始排长龙:备料占地方、灶台不够用、工序有等待。回测框架的性能优化,就是让同一套菜谱在最短时间内完成试做,为反复调参争取更多迭代次数。

一次回测的耗时可以分解为三个部分:数据准备耗时 $T_{data}$、信号计算耗时 $T_{signal}$、撮合记账耗时 $T_{exec}$。总耗时是三者之和:

常用优化手段包括:把行情数据缓存到内存或 Parquet 文件中,避免重复加载;用 NumPy/Numba 对信号计算做向量化加速;把事件驱动中的撮合热点用 Cython 重写。优化的第一步永远是剖析(Profiling)——先量化出三块耗时各占多少,再对症下药,而不是盲目优化所有代码。厨房效率提升后,就能同时试做更多菜谱了,这就引出了最后一道工序:让多个厨师在多口灶台上并行开工。

Ttotal=Tdata+Tsignal+Texec\displaystyle T_{total} = T_{data} + T_{signal} + T_{exec}
通俗理解:出菜总时间 = 备料时间 + 炒菜时间 + 装盘时间,先找出最慢的那道工序再整改。
  1. $T_{total}$一次完整回测的总执行时间,是衡量后厨出菜效率的“总工时”。
    TtotalT_{total}
  2. $T_{data}$加载、清洗、对齐行情数据的时间,类似备料砍切占用的工时;用内存缓存可显著减少。
    TdataT_{data}
  3. $T_{exec}$撮合与记账时间,类似实际炒菜上桌的工序耗时;通过向量化矩阵运算可大幅压缩这一项。
    TexecT_{exec}

并行与工程落地

成熟的餐厅研发部不会一道一道菜慢慢试,而是让多位厨师同时试做不同候选菜谱,一次性比较哪个最有潜力。量化团队的做法类似:把几百组策略参数拆成独立任务,分发到多台机器上并行回测。

并行验证的收益可以用加速比衡量。若单机串行完成所有任务需要 $T_{serial}$,n 个 worker 并行后实际耗时为 $T_{parallel}$,则:

工程落地时通常采用“任务队列”架构:调度中心把参数组合、数据路径打包成任务投入队列,空闲的 worker 领取任务、执行回测、上报结果。这个架构天然支持水平扩展,也方便做企业级 ROI 核算——把回测所需的机器成本、人力成本,与策略上线后的预期收益放在一起评估,决定一道菜是否值得上正式菜单。从私房菜的一口灶台,到中央厨房的批量出菜,再到多灶台并行研发——回测框架这套“试菜体系”,是量化团队在真金白银面前的第一道质检工序。它让我们用历史数据反复彩排,把失误留在模拟厨房里,只把最好吃的菜谱端上正式餐桌。

Speedup=TserialTparallel\displaystyle Speedup = \frac{T_{serial}}{T_{parallel}}
通俗理解:一口灶台炖一百锅汤 vs 十口灶台同时炖,加速比就看分工是否顺畅、协作是否高效。
  1. $T_{serial}$单个机器依次跑完所有策略的总时间,是“一个人慢慢试菜”的基准耗时。
    TserialT_{serial}
  2. $T_{parallel}$多机并行跑完后最慢一个 worker 的耗时,受限于最慢的那位厨师。
    TparallelT_{parallel}
  3. 加速比的含义加速比接近 worker 数说明任务拆分均匀、扩展性好;若远小于 worker 数,说明瓶颈在数据同步或等待队列。
    SpeedupSpeedup

AI 应用场景

私募基金策略上线前的回测质检

阶段:模型验证

解决的问题:传统方式靠人工盯盘或直接小资金试错,风险高、周期长。AI 量化策略在实盘前必须经过回测质检;回测框架通过事件驱动精确模拟撮合与滑点,避免把纸上富贵当成真实收益。

具体案例:某 CTA 私募研发“螺纹钢趋势跟踪”策略,用事件驱动回测引擎载入 5 年 tick 级数据,滑点模型采用“波动率 + 冲击成本”的计价方式。加入 0.15% 滑点后,策略年化收益从 22% 降到 13%,团队由此放弃了这条“菜谱”,避免了实盘中的潜在亏损。

智能投顾产品的批量策略并行验证

阶段:模型评估与上线前筛选

解决的问题:智能投顾需要管理大量资产配置策略,串行回测一组参数要几小时,无法支撑快速迭代。AI 工程团队用任务队列 + 多 worker 并行回测,把千组参数验证压缩到分钟级,同时统一做样本外测试清洗掉过拟合策略。

具体案例:某券商智能投顾团队一天需验证 3000 组“股债轮动”参数组合,采用队列分发到 50 台 worker 并行回测,全流程从 6 小时压缩到 25 分钟;统一执行样本外测试与参数敏感性评分后,只有 37 个策略进入白名单。

个人量化者的快速原型验证

阶段:模型原型验证

解决的问题:个人投资者算力有限,也不具备完整的柜台撮合条件。利用向量化回测能以极低成本快速验证策略思路,把有限精力聚焦在少数有潜力的想法上。

具体案例:一位独立开发者想验证“双均线 + 成交量过滤”的指数增强策略,用 NumPy 向量化回测在 30 秒内扫完 100 组参数网格,发现多数参数在加入千二滑点后盈利消失,转而用“动量因子 + 再平衡”的思路重新试验,避免了长时间无效调参。

Python 演示

python59 行
import numpy as np  class SlippageBacktest:    def __init__(self, prices, fast=5, slow=20, slippage=0.002):        self.prices = np.asarray(prices, dtype=float)        self.fast = fast        self.slow = slow        self.slippage = slippage        self.signals = self._make_signals()        self.returns = np.zeros(len(self.prices) - 1)        self.equity = np.ones(len(self.prices))     def _make_signals(self):        fast_ma = np.convolve(            self.prices, np.ones(self.fast) / self.fast, mode="same"        )        slow_ma = np.convolve(            self.prices, np.ones(self.slow) / self.slow, mode="same"        )        pos = np.where(fast_ma > slow_ma, 1.0, 0.0)        return np.concatenate(([0.0], np.diff(pos)))     def run(self):        position = 0.0        for t in range(1, len(self.prices)):            position += self.signals[t]            daily_ret = self.prices[t] / self.prices[t - 1] - 1.0            trade_cost = abs(self.signals[t]) * self.slippage            self.returns[t - 1] = position * daily_ret - trade_cost            self.equity[t] = self.equity[t - 1] * (1.0 + self.returns[t - 1])        return self.equity     def split_test(self, ratio=0.7):        cut = int(ratio * len(self.returns))        train_ret = self.returns[:cut]        test_ret = self.returns[cut:]        train_final = np.cumprod(1.0 + train_ret)[-1]        test_final = np.cumprod(1.0 + test_ret)[-1]        return train_final, test_final  def main():    rng = np.random.default_rng(42)    steps = 500    trend = np.linspace(0, 0.5, steps)    noise = np.cumsum(rng.normal(0, 0.01, steps))    prices = 100.0 * np.exp(trend + noise)     bt = SlippageBacktest(prices, fast=5, slow=20, slippage=0.002)    equity = bt.run()    train_end, test_end = bt.split_test(0.7)    print("样本内终值: {:.4f}".format(train_end))    print("样本外终值: {:.4f}".format(test_end))    print("全样本总收益: {:.2f}%".format((equity[-1] - 1.0) * 100))  if __name__ == "__main__":    main()
目的:实现一个含滑点模拟和样本外测试的双均线回测框架,验证策略在训练段与样本外段的表现差异。
  1. fast_ma = np.convolve(self.prices, np.ones(self.fast) / self.fast, mode="same")用滑动平均计算“快速均线”,相当于后厨快速判断食材短期走势的参考线。
  2. pos = np.where(fast_ma > slow_ma, 1.0, 0.0)快速均线高于慢速均线时持有多头仓位,否则空仓,形成最朴素的趋势交易信号。
  3. return np.concatenate(([0.0], np.diff(pos)))把仓位序列转换为“仓位变化”信号,只在切换时产生交易动作,降低无效换手。
  4. position += self.signals[t]事件驱动式地逐日累加信号,模拟真实账户中仓位的开平过程。
  5. trade_cost = abs(self.signals[t]) * self.slippage每次买卖都按滑点比例扣成本,让账本不再“理想化”。
  6. self.equity[t] = self.equity[t - 1] * (1.0 + self.returns[t - 1])用复利方式滚动更新资金曲线,模拟真实账户净值。
  7. train_final = np.cumprod(1.0 + train_ret)[-1]计算样本内外的累计增长倍数,是判断过拟合的第一道指标。

常见误区

误区“我的策略回测年化 58%,实盘肯定也能赚到,赶紧上。” 初学者看到漂亮的资金曲线就激动得睡不着。

正解回测只是“试菜”,不是“开席”。前视偏差、幸存者偏差、滑点未建模、过拟合都会让回测虚高。实盘还要面对容量限制、流动性、市场结构变化等现实约束。应当把回测当成“厨艺预演”,上桌前还要过样本外测试、压力测试、小资金实盘三道关。

为何常见:人容易把历史回测的完美曲线与未来实盘划等号,忽略了回测环境与真实市场之间的系统性差异。

误区“向量化回测看着就不严谨,只有事件驱动才算真回测,所以以后全用事件驱动吧。”

正解两者是“私房菜 vs 中央厨房”的关系。向量化快、适合大规模筛选;事件驱动真实、计算慢。工程上的最佳实践是先向量化粗筛,再用事件驱动精测 Top N。无脑全用事件驱动会拖慢研发迭代速度,反而降低策略产出效率。

为何常见:听说事件驱动“更真实”后走向另一个极端,忽略了计算代价与场景匹配,把“精度高”误当成“一定更优”。

误区“我做了 70/30 切分,样本外表现也不错,这策略肯定没过拟合。”

正解单次样本外测试只是“一批评委试吃”。参数在调优过程中若反复参考同一样本外结果,样本外也会被“过拟合”。更稳妥的是滚动交叉验证(多次换评委)、参数敏感性分析(微调配方看口味是否稳定),以及保留下一次真正没看过的数据做最终验证。

为何常见:把“做了样本外”与“样本外绝对可信”混淆,忽略了调参过程本身会污染样本外信息。

误区“我不知道滑点该设多少,那就设 5% 吧,越保守越好。回测还能赚钱就一定稳。”

正解滑点设置过高会杀死本来可盈利的策略,导致“误杀”;设置过低又会让绩效虚高。应当按品种实际波动率、交易量和冲击成本建模,并用敏感性测试观察结果是否随滑点剧烈翻转——如果一加成本就从盈利变亏损,说明策略本身不稳健。

为何常见:非黑即白的保守主义,把“设置大”误当成“更安全”,忽略了过度惩罚同样会做出错误取舍。

误区“我把现在市值前 100 的股票拿来做 2008 年回测,收益率这么漂亮,说明策略能穿越牛熊。”

正解这是典型的幸存者偏差。2008 年还活跃的股票未必活到今天,退市股票被剔除后,回测只统计了“活下来的名菜”,收益天然虚高。应该用“时点可得”的股票池,即每个历史时刻只选当时实际存在且可交易的标的。

为何常见:历史成分数据难获取,图省事直接用当前股票池,忽略了生存者偏差这类前视数据污染,还误以为数据越全越好。

面试题

Q1. 回测(Backtesting)最核心的目的是什么?

单选题 · easy

  • A.让策略在实盘自动执行交易
  • B.预测未来每天的精确涨跌
  • C.用历史数据验证策略表现← 正确答案
  • D.替代人工实时监控行情

答案:C。回测的本质是用历史数据“彩排”交易策略,就像开餐厅前先在后厨试菜,C 准确抓住了这个核心。A 是实盘执行环节,B 把回测夸大成预测工具,D 是行情监控系统,都与回测定义不符。

Q2. 以下哪一项属于回测中的“前视偏差”?

单选题 · easy

  • A.用今日收盘价计算均线后买入
  • B.把手续费计入回测成本
  • C.用日线数据代替分钟数据
  • D.用未来一周财报调整当前仓位← 正确答案

答案:D。前视偏差等于偷看未来。D 把尚未发生的信息提前用于当前决策,是典型的前视偏差,就像拿明天的菜价单买今天的菜。A 使用当日已实现数据,B 是合理的成本处理,C 是数据频率选择,均不构成前视偏差。

Q3. 向量化回测的主要优势是什么?

单选题 · easy

  • A.矩阵批量运算快速检验← 正确答案
  • B.精确模拟涨跌停与停牌
  • C.完全消除滑点对结果影响
  • D.无需历史数据即可检验

答案:A。向量化回测的优势在于批量矩阵运算、极快的验证速度,类似中央厨房批量出菜。B 是事件驱动的强项,C、D 说法本身错误——向量化无法消除滑点,也不可能脱离历史数据。

Q4. 事件驱动回测与向量化回测的最大区别是什么?

单选题 · mid

  • A.事件驱动不需要历史数据
  • B.事件驱动逐笔处理事件← 正确答案
  • C.事件驱动收益必然更高
  • D.事件驱动只用股票市场

答案:B。事件驱动回测逐笔处理行情、信号与成交事件,真实模拟撮合流程,这是它与向量化最根本的区别。A 错误(同样需要历史数据),C 的“必然更高”没有依据,D 错误——事件驱动同样适用于期货、加密货币等市场。

Q5. 成交价模型 $P_{exec}=P_{signal}+\delta\sigma+\lambda Q$ 中,$\lambda Q$ 代表哪部分成本?

单选题 · mid

  • A.大额订单对价格冲击成本← 正确答案
  • B.市场波动的随机扰动部分
  • C.券商收取的固定手续费
  • D.信号价格的平均误差项

答案:A。公式中 $\lambda Q$ 与订单量 Q 成正比,刻画大额订单对市场价格造成的冲击成本——买得越多价格被推得越高。B 对应 $\delta\sigma$(波动项),C 对应固定手续费,D 在公式中没有对应项,属于干扰选项。

Q6. 关于过拟合检测,下列说法正确的是?

单选题 · mid

  • A.样本内好就说明策略稳健
  • B.参数越多策略越不失真
  • C.样本外偏低则怀疑过拟合← 正确答案
  • D.过拟合只出现在神经网络

答案:C。过拟合的典型症状是样本内优秀、样本外拉胯。C“样本外偏低则怀疑过拟合”正是检测的第一直觉。A 把样本内好当成稳健是错误归因,B 方向相反(参数越多越易过拟合),D 把过拟合狭隘地限定在神经网络上,也不正确。

Q7. 用当前中证 500 成分股回测过去 5 年,发现年化 38% 远超基准。最可能的问题是什么?

单选题 · hard

  • A.滑点参数设置过大
  • B.数据频率设置过低
  • C.幸存者偏差导致虚高← 正确答案
  • D.手续费比例设置过高

答案:C。用“今天还活着”的成分股去回测过去,等于只统计活下来的名菜,早已退市或暴跌的股票被排除在外,幸存者偏差导致收益虚高。A、D 是偏保守方向的偏差,不会造成显著高估;B 的数据频率选择与成分股幸存问题无关。

Q8. 以下哪些做法可以有效降低回测过拟合风险?

多选题 · hard

  • A.无限添加新参数强拟合
  • B.样本内外切分验证← 正确答案
  • C.滚动交叉验证多段← 正确答案
  • D.参数敏感性分析← 正确答案

答案:BCD。对抗过拟合的核心是“换人盲测”:样本内外切分(B)、滚动交叉验证(C)和参数敏感性分析(D)都能检验策略是否只对历史噪声有效。A“无限添加新参数强拟合”恰恰是制造过拟合的操作,不能入选。

Q9. 回测框架中模拟真实交易成本通常包含哪些部分?

多选题 · mid

  • A.佣金手续费← 正确答案
  • B.滑点成本← 正确答案
  • C.税费减免
  • D.冲击成本← 正确答案

答案:ABD。真实交易成本由佣金手续费(A)、滑点(B)、冲击成本(D)组成,回测应尽量把这些“采购损耗”计入账本。C“税费减免”是降低成本的措施,不属于成本构成项。

Q10. 关于并行回测工程方案,下列说法正确的有哪些?

多选题 · extreme

  • A.策略按参数拆分独立任务← 正确答案
  • B.队列加多 worker 水平扩展← 正确答案
  • C.并行能消除偏差和滑点
  • D.加速比高说明扩展性好← 正确答案

答案:ABD。并行回测把策略参数拆成独立任务(A)、用任务队列配合多 worker 水平扩展(B),加速比接近 worker 数说明并行设计良好(D)。C 错误——并行只是提高算力,并没有消除前视偏差或滑点,质量控制在并行前后都不能省。