股票回测越跑越慢:区分行情加载、信号计算和撮合耗时
股票回测慢,先测清阶段边界,再决定是否换机器。把输入加载、指标、信号、组合目标、执行记账和结果指标分开计时,针对真正占时部分改一次算法,并要求完整输出保持等价。少跑股票只是缩小实验,不能称原任务优化成功。
本文在固定三股合成流水线的两种观测规模上,比较重复求和与前缀和。它是外部性能定位方法,不是CoTrading基准,没有调用真实行情、缓存服务或券商。合成利润不具历史投资含义,不能用来宣传策略或产品收益。
冻结测量契约
记录机器、解释器、输入身份、股票数、每股观测、回看、执行政策及输出要求。两实现工作量相同,失败和中断测量也保留,不能只选最快成功的一次。环境变化或后台负载明显时,应注明,而不是把全部波动归因代码。
说明哪些工作纳入。样例load重新解析内存JSON,生成字符串不在计时内,不包括网络、磁盘或认证。真实排查若关心数据源延迟,要另设获取阶段,不能把本文解析耗时当成供应商速度,也不能假设任何数据源允许缓存。
缓存状态与执行顺序同样保存。本例单进程每实现三次,先朴素后优化,每次重新解析,无外部缓存。但内存分配和CPU状态仍可能变化,不能声称相同冷启动。需要更严格性能比较时,可另设计交错次序和更多重复,并保留预先政策。
在有意义的责任边界计时
Python性能计数器文档介绍适合短时段的经过时间计数,只能用两次读数之差,绝对参考点无业务含义。脚本用纳秒版本,再把差转换成毫秒展示,不把时钟读数当作真实运行开始日期。
started = perf_counter_ns()
indicators = calculate_indicators(prices)
timings['indicators'] = perf_counter_ns() - started
尽量避免每个算术操作都包计时器,那会改变工作量。本例粗粒度足以识别主要合成工作,又保持执行清楚。计时边界要覆盖实际阶段,不应把慢操作挪到边界外,就宣称阶段已经提速;纳入和排除内容都在说明中记录。
阶段名与真实职责对应。本文组合目标只是把零一信号复制成目标股数,执行阶段再算股数差、现金与净值,不隐藏优化器。复杂引擎可能需要分离配置、风险、撮合和持久化,应按实际职责增加边界,而不是沿用不适合的简单表。
两种规模实际运行
固定股票A、B、C使用同一确定价格公式、不同偏移。小规模每股二千观测,目标规模八千,都计算三期和二十期均值,热身相同。股票数一直三,不能通过减少成员换时间,也不把两种日期长度说成同一个工作量。
均值有效后,快高于慢就信号一,下一观测成交,目标零或一股。每股独立初始现金一千,总资本三千,费用零。这个结构服务工作量等价,不是真实共享资金组合优化,不能据此推断大股票池资金可行性。
每规模每实现保留三条阶段记录,输出中位数。六段分别为加载、指标、信号、目标、账户成交、指标汇总。各段中位数可能来自不同重复,直接相加不一定是一条实际端到端运行;需要总体时间时应单独测,不把统计汇总冒充观察值。
先定位瓶颈,再修改
查看本机实际输出。在记录的本地运行中,重复指标求和占较大部分,因此值得换算法测试。但真实引擎可能网络、序列化或撮合更慢,不能推广成所有回测都应先优化均线。测量解决当前工作负载,不替代了解真实接口。
比较两规模变化,不只从小样例外推。这里保持股票数和策略不动,只改每股观测。若要研究股票池扩大,应另开明确规模实验;不要一边加日期、一边少股票,最后仍把耗时差全部归因算法。工作负载描述应足以重建。
Python 3.12剖析文档区分调用剖析与基准,并说明插桩开销。剖析可帮助深入某个慢阶段,但其计时不能直接等同普通运行。本文只用显式阶段计时,没有实际调用剖析器,更没有验证新版本专属分析接口。
优化滚动求和
基线每个位置切片求和,替代版先构造前缀和,用两个前缀项相减取得窗口和。整数价格、相同窗口和相同除数,让本例均值可以精确一致。算法改动保持输入、输出长度、首个有效点和信号时点不变。
结束于t、长度n的和为prefix[t+1]−prefix[t−n+1]。特别核对首有效位置和最后窗口,差一位可能更快,却悄悄改全部信号。不能删掉热身行再靠图形对齐,看起来两数组长度一样就结束验收。
任意浮点输入可能存在累积抵消和舍入差,真实优化要用合适容差或数值方法。本例整数精确相等只适用于该输入,不是对所有价格精度的保证。容差应该按指标、价格与收益单位分别规定,不能为了变绿统一放大。
先验收完整输出,再谈速度
脚本比较指标数组、信号、成交、每个净值和最终汇总,并断言固定股票观测数及确定回归锚点。只看期末金额会漏掉相互抵消的交易错误,过程中风险也可能已经变化,因此不能作为唯一正确性依据。
二千观测时,两实现期末四千七百三十二、成交事件二千七百九十四;八千观测时,期末一万零四十七、事件一万一千二百六十六。它们是合成回归锚点,不是市场成绩。各次金额保持,毫秒值允许波动,代码没有硬性提速倍数断言。
另一机制是少算工作伪装更快。漏一股、缩短序列或省略输出会违反覆盖和完整比较,即使摘要相似也不接受。窗口索引错误则由全指标对照发现,不等到收益方向变差才认定有问题。速度与正确性是分别检查的条件。
解释缓存与并发影响
真实改动引入缓存时,记录键、输入版本、命中状态和失效规则。暖缓存与冷数据源回答不同运营问题,不能混称同条件。不为局部测量删除共享缓存或改正在运行服务,研究只在自己可控的独立上下文完成。
并行调度或随机流分配改变时,还要查可复现性及合并次序。事件顺序可能影响资金资格和数值聚合,更快不代表同义。存在差异就进行执行复核,不用“这是性能改动”作为免验理由;无法保持等价时应明确变成新的行为实验。
保留原始重复值、中位数、环境和输出比较证据。几次很短计时不能承诺固定倍数,也不能把负载噪声伪装成优化收益。一次优化版较慢仍可通过正确性,错误快版则必须失败;是否值得采用需要看相应操作目标的时间证据。
交付可重复计时包
说明输入构造是否纳入、每阶段起止、两种规模与执行次序,保存完整脚本和输出。接手者应能重跑得到同样合成内容,再看到自己机器的时间,而不是被要求复现作者某个毫秒常量。绝对时间不同并不自动说明脚本失效。
若实际瓶颈转移,例如指标变快后账户阶段变主导,重新测量再决定下一改动,不能一次性改全部阶段然后失去定位。每次只改变已说明的实现,保留前后输出证据,能够判断耗时下降来自算法还是工作被移出测量范围。
原始计时也保留精度,不要先四舍五入为零再排序阶段。显示零点零零毫秒不代表没有工作,可能只是小于当前展示精度,不能据此直接删除该环节。
运行与验收
下载完整示例。使用Python 3.12或更新版本,运行python R624.py。标准库合成程序打印两规模阶段中位数,核验全部输出与上述锚点;记录是本机观察,不是可移植基准,也未测真实行情加载。
工作量和必需输出符合政策,时间证据回答当前问题,才接受改动。数据形状、股票池、引擎、机器、缓存或输出改变后重新测,保存旧上下文,不把无关数字放在同一提速口号下。本文不提供任何产品速度或市场效果保证。
常见问题
耗时降低就能接受优化吗?
不能。先确认同样工作量与信号、成交、净值、指标。
这里测了实时行情加载吗?
没有。加载仅重新解析内存合成JSON,排除网络和磁盘取得。