跳到正文

研究指南

股票回测与实验

长短周期参数怎么公平比较:统一热身后的回测起点

改变回看周期,可能同时改变信号和回测起跑日。短周期在长周期尚未具备计算条件时已经赚钱或亏钱,最后余额就不能单独说明参数影响。公平比较需要补足共同历史输入,再从同一个可执行起点、相同账户初态重新运行。

本文交付首个有效日期表、共同计分日程及对齐重跑结果。标准库样例使用十个虚构价格、一股目标仓位和零费用,验证时间与状态处理,不代表真实股票表现,也不证明CoTrading已有相应功能。每次研究只处理一个市场和币种。

分开历史需求、有效信号和可执行成交

指标往往需要一定历史,才能产生有意义的值。指标有效后,信号还要等到信息可用之后的可执行价格。backtesting.py官方API文档提醒滚动指标热身会影响起跑,并说明其默认市价单下一根开盘成交。这里引用的是参考实现行为,不代表其他引擎遵循同一顺序。

每个候选记录三个日期:最早完整输入窗、首个允许产生的信号、首个允许成交的位置。说明当前观测是否参与指标,何时才能知道其值。使用今天收盘信息,不能自动把今天收盘当作已可获得的成交价;也不能不看实际调度就给所有引擎机械增加一天。

指标有效不等于出现买入。完整计算后决定空仓,也是有效信号。如果等每组第一次买入再起算,会挑选不同市场机会,制造新的比较偏差。共同开始日应由信息资格和成交规则决定,不应由某个有利事件什么时候发生决定。

补足历史但不把热身计入收益

取得约定计分期间,再向前补最长回看历史与额外执行延后。保留来源版本和市场日历。五个观测的窗口需要五个真实合格观测,遇到节假日或缺报价时,不等于五个自然日。不能为了凑数量,用前值填充制造实际不存在的可交易观测。

所有候选获得相同的更早历史,即使部分候选只需要其中一小段。指标可以读取这些数据,但计分账户不应计入热身收益或交易。本文选择平仓初态,因此热身信号不能留下继承仓位。若研究确实要承接持仓,应明确共同的前置账户及选择理由,作为另外一种比较政策。

嵌套指标的实际需求可能超过参数标签。例如先计算一个需要热身的指标,再对其做移动平均,会延长有效期。要记录实际首个有效输出,不能只凭最大参数猜测公式。有的引擎还会延迟首次回调,因此最终应核对真正产生的信号与成交事件。

主例:三个与五个观测的回看

合成价格序列为[10,20,30,40,30,25,20,15,18,22]。索引从零开始;时点t的价格若高于包含自身的最近n个价格均值,信号为一,否则为零。目标持股为零或一股,信号在t加一位置成交。同一合成价格同时用作该期成交和估值,简化了盘中价格变化。

回看周期首个完整信号索引最早成交索引共同成交起点
3235
5455

最长窗口在索引四完整,因此共同最早可执行起点为五。两组账户都在这里以现金一千、零持股开始,计分观测为索引五至九,各五期。指标可以读取更早价格,但不会继承更早的账户盈利或亏损。

重新推进账户,不能只裁剪旧净值

对每个计分成交位置,先评价前一期信号,再计算目标股数与当前股数之差,按成交价格更新现金,最后按同一位置估值。这条状态链明确落实了共同初态。把早已运行的净值从索引五裁出来,仍然会保留此前形成的现金和仓位,不等于平仓重跑。

for fill_t in range(common_start, len(prices)):
    signal_t = fill_t - 1
    assert signal_t >= lookback - 1
    signal = int(prices[signal_t] >
                 sum(prices[signal_t-lookback+1:signal_t+1]) / lookback)
    quantity = signal - shares
    cash -= quantity * prices[fill_t]
    shares = signal

完整样例保存每个计分索引、目标持股和估值。真实执行还要增加费用、手数、拒单和资金约束,而且这些限制也必须在候选之间保持一致。这里的一股简化并没有证明某个真实市场允许相同交易,也没有模拟停牌或涨跌停。

解释对齐与未对齐的输出

共同起点下,三周期期末为一千,五周期为九百九十五。长周期在索引五以二十五买入,后来在索引六以二十卖出,损失五。短周期没有继承较早的交易,在样例最后估值下保持初始净值。

若三周期从自己的最早成交索引三开始,就会以四十买入,后来以二十五退出,期末变成九百八十五。这十五元损失来自实验允许它提前参与,并不是公式变化。比较九百八十五与九百九十五是在不同机会集上排名;比较一千与九百九十五,才回答事先声明的共同起点问题。

末尾未平仓必须按统一规则估值,不能丢弃。短周期可以在最后位置持有一股,但输入到此结束,不会产生后续盈利。真实报告应同时保留期末现金和股数,避免相同净值掩盖不同剩余敞口。是否期末强平以及强平费用,都应该提前写进计分政策。

反例:历史不足与继承状态

反例要求五周期在索引三成交,对应信号索引二,尚无五个完整价格,断言直接拒绝。如果让较短Python切片返回不足五个值,却仍然除以五,可能算出看似合理、实际无效的均值,随后输出不应被当成合法参数证据。

另一种机制是把短周期较早形成的仓位带进共同起点。它可能构成有意义的另一项研究,但违反本文平仓账户比较。检查初始现金、股数和待执行订单,不能只检查开始日期文字。两条曲线第一天相同,也可能承担不同风险或拥有不同资本。

缺报价还应与热身分开。如果指标在名义起点之后因为输入缺失重新变得无效,应保存事件并执行冻结的缺失政策。每遇到不利缺口就把整个计分区间往后移动,会变成依赖结果的筛选,不能被称为解决输入质量问题。

导出共同日程与状态证据

交付文件除了总收益,还应列出候选编号、首个完整输入窗、首个有效信号、首个可执行成交和共同计分会话。每个位置能够追到原始观测键,而不是只写“已对齐”。候选没有买入时也要保留空仓记录,这样检查者才能勾稽五个计分会话,而不是把没有成交的日期误认为漏数据。

另设初态表,记录现金一千、持股零、待单零,并注明热身区间不允许推进账户。正式引擎若已经输出热身成交,重跑前必须恢复初态,不能只把其利润从最后余额中减掉,因为那些交易可能改变后续股数与现金。输入日历、报价状态和指标有效状态分别保存,能区分真正缺日、有效但无信号,以及尚未满足回看三个原因。

最后再抽查起点前后各一条记录,确认窗口是否包含当前值、是否错误多等了一期,以及第一笔成交到底引用哪条信号。这种边界抽查能发现索引偏移,单看五个日期和最后余额则未必能够发现。

实际运行与更新条件

下载完整示例。使用Python 3.12或更新版本,运行python R608.py;仅使用标准库和合成价格。断言核验首信号索引二/四、首成交索引三/五、共同起点五、各五个计分观测、期末[1000,995]及短周期自行起跑的九百八十五。

正式交接时导出历史覆盖、实际有效日期、共同计分键、初始账户状态和运行引用。确认热身事件没有贡献账户损益。名义日期相同仍不够:某只股票停牌或某个来源采用不同日历,都可能让实际可执行会话不同。遇到这种情况,先解释可用性,不要只缩短数组以追求长度相同。

候选集、指标依赖、执行延后或历史可得范围改变后,重新确定共同起点。保留旧比较,不能把旧起点和新起点分数混排。热身对齐只消除一种不公平来源,不会建立统计独立性、清除其他前视问题,也不证明所选参数能够在未见期间继续有效。

常见问题

共同起点是否取第一次买入?

不应如此。由信息资格和执行规则决定,有效的空仓信号也要保留。

裁剪既有净值可以代替重跑吗?

只有账户状态政策确实一致才可能。本文要求平仓初态,裁剪不能实现这一点。

相关阅读