跳到正文

研究指南

股票回测与实验

股票策略参数怎么批量回测:先生成合法搜索组合

批量回测的第一份产物应该是可执行、可勾稽的合法实验队列。比较股票策略收益之前,先明确哪些参数组合允许运行,并证明执行函数确实接收了这些值。生成十八个配置文件,并不能证明做了十八个不同实验:包装函数可能一直调用同一组默认值。

本文交付参数组合账、运行引用和生效值回执。标准库样例只使用虚构价格计算信号,用于检验批次生成与参数传递,不代表真实历史表现。A股、港股、美股按单一市场、单一币种分别处理。CoTrading相关能力仍属规划或待核实,不能从本文推断已有优化器、行情连接或券商集成。

先冻结单次策略契约

从已经通过单次检查的策略开始,记录代码版本、输入结构、交易日历、复权口径、观测截止时间、下单时点、初始现金和成本假设。把允许调节的字段与固定字段分开。如果生成组合时还在换数据或者改撮合规则,最终排名比较的就不只是参数。

本文只有三个可调字段:整数快周期、整数慢周期,以及小数形式的信号阈值。两个均值都包含当前观测。当快均值大于慢均值乘以一加阈值时,信号为一,否则为零。示例没有实现仓位、成交撮合或利润核算;正式回测需要先独立验收这些环节,不能把信号运行成功等同于完整回测成功。

合法性约束必须在查看结果前确定。快周期必须为正且严格小于慢周期;阈值不得为负;输入历史必须满足最大慢周期的回看需求。字段名也应严格检查,不认识的键直接拒绝。若阈值拼错却被宽松的选项字典吞掉,批次看似有两个阈值,策略实际可能一直使用默认值。这是输入有效性问题,不是收益筛选规则。

勾稽原始组合与排除原因

设快周期为二、三、四,慢周期为三、四、五,阈值为零和百分之一。原始笛卡尔积为三乘三乘二,共十八个提案。Python的product文档说明了枚举乘积所用的标准库迭代器;排除约束由我们显式添加,不是迭代器自动判断策略是否合法。

快周期合法慢周期两个阈值下合法任务数排除数
23、4、560
34、542
4524
合计六个合法周期对126

把被拒绝的配置和原因fast>=slow保留下来。排除账解释某个提案为什么没有进入执行器。运行失败则表示配置合法、执行没有完成,例如超时或缺失输入。不能在事后把不理想的运行改写成无效组合,也不能删除失败记录后只展示成功数量。

大网格往往有多条约束,一个提案可能同时触发几条。可以按固定顺序保留首个拒绝原因,也可以保存全部原因标记,但汇总提案数时只能算一次。若直接把各原因计数相加,可能重复计算。展开前先对参数值去重,必要时另存原始请求,说明重复输入如何被规范化处理。

给每次合法运行稳定的身份

把通过校验的配置按键排序后序列化,再计算摘要。hashlib官方文档提供SHA-256接口。摘要用于识别输入字节,不会证明策略有经济意义。必须同时保存完整配置,让读者直接查看字段,而不是只留一串无法解释的编号。

样例使用短编号方便阅读,正式研究档案应保留完整摘要并检查唯一性。每行再连接冻结代码引用、数据摘要、执行政策引用和批次编号。只有配置摘要还不够:相同参数换了历史数据,应当属于新的实验上下文;参数编号相同,不意味着两次运行可以混排。

队列行至少需要请求配置、状态、尝试序号、生效配置和输出引用。待运行、运行中、完成、失败都要有确定含义。对同一冻结输入重试,增加一次尝试记录;改变参数则形成新候选。保留失败尝试及错误,不用后来成功的文件覆盖它,否则无法解释批次实际消耗及异常原因。

只保留一条明确的参数传入路径

仓库样例的核心代码如下,完整文件还包含策略函数、摘要、结果账和断言:

from itertools import product

for fast, slow, threshold in product([2, 3, 4], [3, 4, 5], [0, 0.01]):
    config = dict(fast=fast, slow=slow, threshold=threshold)
    if fast >= slow:
        excluded.append({"config": config, "reason": "fast>=slow"})
        continue
    result = strategy(PRICES, **config)
    assert result["effective"] == config

这里的策略函数从自己实际参与均值计算的参数生成回执。若包装器在进入策略之前直接复制请求配置,回执看起来正确,也可能掩盖后续丢参数的问题。在已有引擎中,要找到配置最终转成策略构造参数或函数实参的位置,在那里记录解析后的值,包括默认值和单位变换。

所有组合使用共同观察区间。虚构收盘序列为[10, 11, 9, 12, 13, 10, 14, 15, 11, 16],只在从零计数的第五到第八个索引计算信号。各候选因此都有四个计分观测,且已有足够回看历史。当前收盘参与信号,日后接入成交模块时必须等到实际可执行的后续价格,不能回填到当时尚不可知的成交价。

把实际输出读成传递链检查

在仓库根目录运行python docs/growth/research/round-5/examples/R605.py。实际输出为proposed=18 excluded=6 completed=12。第一组请求快周期二、慢周期三、阈值零,生效值逐项相同,得到信号[0, 0, 1, 0];最后一组快周期四、慢周期五、阈值百分之一,同样核对成功,信号为[0, 1, 1, 0]。

这两条记录让检查者能够从配置字段追到参与计算的实际值。但不同参数不必产生不同信号。在短序列上,相邻阈值完全可能没有改变任何一次判断。先核对生效值,再讨论输出敏感性,不能仅凭信号数组相同就断言参数未生效。

本例的完成只代表合成信号函数正常返回。完整回测还必须确认时间对齐、订单处理以及约定产物全部通过。启动进程、生成空文件或者工作线程中断,都不应该被记为完成。若部分任务尚在等待,交付物要明确仍然未完成,不能为了总数漂亮提前填入结果。

反例:合法请求被包装器忽略

样例另设一个错误包装器,不管队列请求是什么,都运行快周期二、慢周期五、阈值零。十二个合法任务表面上全部执行,其中只有一个请求恰好等于默认值,另外十一个不一致。回执检查得到ignored_config_mismatches=11,不需要看收益变化就能发现参数传递失败。

这与快周期大于等于慢周期是两种机制。前者是请求本身不合法;后者是合法请求没有被正确执行。修复实参映射后,应更换执行引用,重跑受影响任务,同时保留被作废批次。不能把请求值补抄进回执来消除报警。如果引擎确有单位转换,应当对照经过测试的显式转换规则核验,而不是追求文本恰巧相等。

验收批次并明确重建条件

排名之前,先满足提案数等于排除数加合法数,再满足合法数等于待运行、运行中、完成和失败之和,每个候选只计一次。检查身份唯一、输出可读、生效值一致;抽看上述两条运行及一条排除记录。重试还需要按候选和尝试分别勾稽,不能把重复执行算作额外的独立搜索点。

数据、引擎、代码、费用政策或计分区间改变时,重建实验上下文;新增参数值时透明扩展队列,保留此前所有尝试。本文解决的是可执行批次,不负责消除择优偏差。合法地跑完十二组,也不能把其中最好的一组直接当作未来有效策略。先保存完整搜索,再进入参数平台分析或真正未见区间的验收。

交付可被另一执行器检查的队列

导出时每个候选单独一行,请求参数、解析参数、上下文摘要、尝试序号和最终状态分别成列。排除表也使用同样的参数列,另一位执行者无需阅读图表或复制说明文字,就应能够重建十二个合法请求。先故意提交一个拼错字段的请求,确认它在派发前被阻止,而且不会生成完成结果。工作线程若先写文件后崩溃,该文件只能挂在失败尝试下,不能因为文件存在就升级为成功。异步执行时还应区分候选数、尝试数和产物数,三者本来就不一定相等。交接记录同时说明哪个输出字段用于判断完成、谁负责处理未结束任务,避免后来接手者把未知状态解释为没有交易,或者把一次超时当成一个新的参数组合。

下载完整示例。使用Python 3.12或更新版本,在下载目录运行python R605.py;仅使用标准库与合成输入。

常见问题

信号相同就说明参数没有生效吗?

不一定。不同合法设置可能产生相同判断,先比对策略内部解析出的生效值,再检查信号对参数是否敏感。

这是否证明已有自动股票优化器?

没有。它是独立的组合生成和参数传递样例,没有运行真实行情,也没有验证CoTrading优化器能力。

相关阅读