选股扫描如何秒出结果?tick-stock-panel 选股缓存与矩阵预热机制完整指南
【免费下载链接】tick-stock-panelTSP自托管、零运维的 A 股「选股 + 监控 + 回测」量化工作台 | LLM能力驱使策略定制+个股分析+复盘 | 自由接入第三方数据源与个性化扩展数据 | 个人开源项目地址: https://gitcode.com/GitHub_Trending/ti/tick-stock-panel
tick-stock-panel是一个自托管、零运维的 A 股「选股 + 监控 + 回测」量化工作台。很多新手都有一个疑问:面对 5000 多只股票的全市场扫描,为什么选股结果能"秒出"?答案藏在两套机制里——三层选股结果缓存与矩阵预热。本文带你从零看懂这套"扫描秒出结果"背后的完整设计。
为什么全市场选股扫描会很慢?
先理解"慢"的根源。朴素做法下,每次扫描都要:
- 从磁盘重新加载全部股票的日线数据;
- 对每只股票重新计算均线、MACD、BOLL 等指标;
- 执行条件过滤、排序。
数据量越大、策略越多,磁盘 I/O 和 CPU 开销就越恐怖。tick-stock-panel 的解法不是"更快地算",而是"少算 + 提前算":
- 少算:能复用的结果绝不重算 → 三层缓存;
- 提前算:最贵的全市场矩阵在后台悄悄算好 → 矩阵预热。
三层选股结果缓存
选股逻辑集中在 backend/app/services/screener.py,它按"从快到慢"的顺序尝试三级缓存,命中即返回。
第 1 层:当日快照直接命中
扫描前先查数据仓库里 enriched(已含指标)数据的最新快照:如果缓存的日期正好等于目标日期,直接复用整个 DataFrame,连指标都不用碰。这是最常见的命中路径——盘中反复扫、收盘后重复扫,几乎零成本。
第 2 层:历史区间 TTL 缓存
当你回看某个历史日期(做 as-of 扫描)时,服务会用(资产类型, 日期, 回溯天数)作为键,把整段算好的结果放进进程级 TTL 缓存。缓存设有过期时间和数量上限(最多保留 10 份),避免内存无限膨胀。同一历史日期反复扫描时,指标重算只发生一次。
第 3 层:扩展数据"文件签名"缓存
如果你启用了自定义扩展数据(比如外接的因子列),API 层 backend/app/api/screener.py 会按需从磁盘加载扩展列的取值映射。这里用了一个很精巧的 trick:以底层 parquet 文件的(路径, 修改时间)作为签名做记忆化——文件没变就复用上次的映射;数据一刷新(mtime 变化)缓存自动失效重算。既不用手动清缓存,也绝不会用到过期数据。
💡 三层缓存的设计哲学:用最小的失效判断成本,换掉最贵的重复计算。每一层的失效条件都是"数据变了",而不是"时间到了"。
矩阵预热:在用户点击之前算好最贵的部分
缓存解决的是"重复"问题,但如果缓存全空(首次启动、刚刷新完数据),第一次扫描仍然很慢。矩阵预热机制就是为了解决这一瞬间。
什么是选股矩阵?
把所有股票、所有交易日的价格数据组织成一个全市场 × 全时间的共享矩阵(基于 mmap 磁盘缓存),指标、信号的计算都在矩阵上做。选股引擎的批量扫描(run all)和回测引擎共用同一个矩阵构建入口build_shared_matrix,所以"预热一份,两边都受益"。矩阵层的确定性计算结果还有独立的字节预算缓存MatrixComputeCache(默认上限 512MB),见 backend/app/backtest/matrix.py。
预热什么时候触发?
入口在 backend/app/main.py:
- 数据刷新完成后自动触发:数据仓库的
_on_refresh_done回调会调度预热任务——刚同步完 K 线,系统就立刻把矩阵算好; - 应用启动时:如果检测到数据已就绪,也会补一次预热;
- 单飞保护:backend/app/services/matrix_prewarm_owner.py 中的
MatrixCachePrewarmOwner保证同一时间只有一个后台守护线程在预热,重复调度直接跳过;应用关闭时可在 5 秒内干净取消。
预热算的是什么?
核心函数prewarm_matrix_cache位于 backend/app/backtest/strategy.py,它做的事情很克制:
- 时间范围:默认覆盖最近5 年,并且额外向前多取不少于 120 个自然日的"暖机数据"(保证长周期指标从第一天就是精确值);
- 数据列:只预热最基础的 OHLCV 价格列,不做任何指标扩展——贵的部分先备着,便宜的按需算;
- 执行后端:
matrix_native,即直接在矩阵上向量化执行。
三个可调参数
全部集中在 backend/app/config.py:
| 参数 | 默认值 | 作用 |
|---|---|---|
backtest_matrix_cache_prewarm | True | 预热总开关 |
backtest_matrix_cache_prewarm_years | 5 | 预热覆盖的年数 |
backtest_matrix_cache_max_mb | 512 | 矩阵磁盘缓存的字节预算上限 |
如何确认预热正常工作?
打开服务端日志,搜索matrix cache prewarm即可看到完整生命周期:
matrix cache prewarm done: ...—— 预热完成,日志里附带缓存路径、字节数与耗时;matrix cache prewarm skipped: no stock enriched data—— 本地还没有股票数据,跳过(属正常);matrix cache prewarm already running or shutting down, skip—— 已有预热任务在跑,单飞保护生效。
如果日志里能看到done且bytes数量合理,说明矩阵已就绪,此时任何选股扫描都是"热启动"。
实战建议与常见问题
- 刚同步完数据后第一次扫描稍慢是正常的——后台正在构建矩阵,之后就一直是热路径;
- 磁盘紧张?调低
backtest_matrix_cache_max_mb,矩阵缓存会按预算自动淘汰旧条目; - 频繁换策略参数?完全不用慌:矩阵是价格层资产,换策略只是在其上重算信号层,这正是"秒出"的核心原因;
- 想让预热范围更保守?把
backtest_matrix_cache_prewarm_years从 5 调小即可,预热耗时和占用会同比下降。
小结
tick-stock-panel 让 5000 只股票的扫描"秒出",靠的不是玄学,而是教科书式的性能设计:
- 三层缓存让重复请求几乎零成本——快照命中、TTL 历史缓存、文件签名失效判断层层兜底;
- 矩阵预热把最贵的全市场矩阵计算从"用户等待路径"挪到了"后台空闲路径",数据一刷新就开始准备;
- 单飞守护线程 + 优雅取消保证预热机制本身不成为新的负担。
理解这套"选股结果缓存 + 矩阵预热"的组合拳,你就能判断任何扫描慢的原因出在哪一层,也能按自己的机器配置做出最合适的取舍。
【免费下载链接】tick-stock-panelTSP自托管、零运维的 A 股「选股 + 监控 + 回测」量化工作台 | LLM能力驱使策略定制+个股分析+复盘 | 自由接入第三方数据源与个性化扩展数据 | 个人开源项目地址: https://gitcode.com/GitHub_Trending/ti/tick-stock-panel
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考