选股公式扫描范围没设对,是条件选股跑得慢的一个直接原因:点下「执行」,进度条一格一格走完要好几分钟,回去把公式里的语句删掉几句,再跑一遍,时间差不了多少。这时候该看的不是公式写得多复杂,而是选股器一次要跑多少只股票、条件按什么顺序判断、每一只是不是都得从第一根 K 线重算。
这三件事都在调度层,和公式内部的写法优化不是同一个问题。站内 公式报错与卡顿排查 里讲过单条公式算得重不重,那属于另一层。本文只讲调度层,也就是在公式不改的前提下,怎么让一次跑批的时间降下来。
一、先分清慢在哪一层
一次条件选股的时间可以拆成两段。第一段是选股器按顺序把范围内的标的逐个取出来,第二段是针对每只标的把公式算一遍、比较结果。第二段的成本由公式决定,第一段的成本由范围和缓存决定。两段的症状能分得开:
| 现象 | 更可能在哪一层 | 可先做的判断 |
|---|---|---|
| 把范围从全部 A 股改成自选股,耗时明显下降 | 调度层(范围) | 本文第二节 |
| 范围不变,只调换两条条件的先后,耗时变化 | 调度层(顺序) | 本文第三节 |
| 范围与顺序都不动,改一个窗口参数就慢一倍 | 公式层 | 回到公式内部去找 |
| 第一次跑慢,紧接着第二次跑快 | 调度层(缓存) | 本文第四节 |
这张表的价值在于排除。先做一次「只改范围」的对照,如果耗时随范围线性变化,说明成本主要落在「跑多少只」上,公式写得再紧也只是把系数调小,改变不了量级。反过来,如果范围缩小到二十只自选股仍然要等,那才是公式层的问题。
二、扫描范围:全市场、板块、自选股
条件选股执行前要选范围,界面里给出三个口径:全部 A 股、某个板块、自选股。三个口径对应的标的数量差着量级,耗时也跟着差量级。选股公式扫描范围的改动,是这几个动作里代价小、见效直接的一个。
全部 A 股包含沪深两市所有上市标的,数量在五千只上下,还要算上当日停牌、退市整理期的记录。板块口径只跑该板块的成分,行业板块几百只,概念板块几十到几百只不等。自选股口径取决于你自己那份列表有多少只。同一支公式在三个口径下跑一遍,耗时排序基本与标的数量排序一致。
什么时候该收窄范围,取决于你要解决什么问题。做每日复盘,看的是「今天有没有符合条件的」,范围可以按板块收窄;做一次全市场的普查,想确认某个条件的分布,那就得跑全部 A 股,这时候能省的只有后面几节讲的顺序与缓存,范围不能再动。
还有一种介于两者之间的做法:把常用的观察对象做成一个自定义板块,跑批时选这个板块。自定义板块在板块管理器里维护,成员可以手工增删,也可以由另一次选股的结果导入。这样每次跑批的标的集合是稳定的,耗时也就稳定,便于做前后对照。
三、条件顺序:把便宜的判断放前面
公式里的语句是从上往下执行的,但判断的顺序能不能提前,取决于写法。把一次计算成本低的判断放在前面,可以让后面的重计算只对通过前一道的标的执行。这一步不需要改公式的逻辑,只调整语句的位置。
哪些判断便宜,按数据来源分:只用当前 K 线的开高低收与成交量,取数成本最低;要用到 N 根窗口的统计量,成本随 N 增长;要用到另一周期的数据或者财务字段,成本再上一层,因为要跨数据源取数。同一个公式里把窗口判断写在最前面,把跨周期的引用写在最后,执行顺序就与成本顺序一致了。
{ 便宜的先算:先看当日形态,再看长窗口,最后才碰跨周期 }
T1:=CLOSE>OPEN;
T2:=HHV(HIGH,120)/LLV(LOW,120);
T3:="MACD.MACD#WEEK";
XG:T1 AND T2>1.5 AND T3>0;
上面这段里,T1 只用当根 K 线,T2 要取一百二十根,T3 要跨到周线去取。写在同一个 XG 表达式里,软件不会替你重排顺序,它按你写的次序逐个求值。把 T1 换到最前面,条件不成立的标的在这一步就被排除,后面两个取数动作不必发生。
要注意的是,这种排序只在条件之间是「与」的关系时有效。条件之间是「或」,前面不成立并不能排除后面,重排没有收益。另外,任何一次重排都要重新跑一遍核对结果:结果集应当与重排前完全一致,数量与名单都对得上,才算只改了顺序没改逻辑。
四、逐股重算与结果缓存
选股器对每只标的都要算一遍公式,这一步无法跳过。能利用的是软件对已算结果的缓存:同一支公式、同一组参数、同一周期,短时间内重复执行,第二次会快一些。缓存失效的条件也很明确——改参数、换周期、换范围、切换复权方式,都会让缓存作废,重新从第一根 K 线算起。
这个行为带来一个实用结论:调参阶段不要一边改参数一边跑全市场。正确的顺序是先用自选股里的一小批标的把参数调到满意,再放开到全市场跑一次。反过来的做法是每次调参都全量重算,时间全花在等待上,而且拿不到可比的结果,也看不出选股公式扫描范围的影响。
缓存还有一个容易误判的地方:第一次跑得慢、第二次跑得快,并不说明公式有问题,也不说明第一次哪里设错了。判断公式本身的成本,要看的是「冷启动」那一次,也就是改过参数后的第一次执行。比较两个写法谁更省,前提是两次都在冷启动状态下测,否则测出来的是缓存差异,不是写法差异。
五、跨周期引用与财务字段的代价
跨周期引用和财务字段是两类跨数据源的取数。前者要读另一周期的 K 线序列,后者要读财务数据表。两者的共同点是:数据不在当前周期的 K 线里,每次取都要另外去找,找不到就返回空值,而不会弹一个提示。
空值是排查里最容易漏的一环。公式里写了 "MACD.MACD#WEEK",如果本机没有下载周线数据,或者所选的标的周线数据不全,这个值会是空的。空值参与比较时既不算成立也不算不成立,含它的条件会整条失效,于是结果集变成空。看上去像「条件太严」,实际是取数取不到。
财务字段同理。财务数据按报告期发布,公式里取的是最近一期已发布的数值;如果本机的财务数据没有更新到当期,取到的就是上一期。这类偏差不会报错,只会让结果与你在行情软件里看到的财务页对不上。核对方式是把公式里取的字段与软件财务页逐项对照,看的是同一期、同一口径。
从成本角度看,这两类取数都排在价格与成交量之后。能不用就不用,非用不可时放在判断链条的末端,让前面的便宜条件先把范围压下来。
六、跑批前后可以核对的几处
把上面几节收成一张按顺序执行的清单,每一步都有可以当场看到的判断结果。
- 先做范围对照:同一支公式、同一组参数,分别用全部 A 股与自选股跑一次,记下两次耗时。耗时随范围明显变化,问题在调度层。
- 看结果集大小:结果几百条与结果几十条,列表的排序与展示成本不同。结果过多时先收紧条件,而不是先去优化公式。
- 确认冷启动:改过参数后的第一次执行才是公式的真实成本,第二次以后带着缓存,不宜作为比较依据。
- 查跨周期有没有值:把跨周期引用单独画一条线,没值说明数据没下全,此时改公式没有意义。
- 查财务字段的期次:与软件财务页对照,看取到的是不是同一报告期。
- 重排条件后复跑:结果名单要与重排前一致,数量一致、标的对得上,才算只动顺序没动逻辑。
下一步动作很清楚:先花十分钟做一次「只改范围」的对照,把问题定位到调度层还是公式层。定位在调度层,就把选股公式扫描范围收窄、条件重排、避开反复全量重算;定位在公式层,再回头去改写法。两层混在一起动手,改完也说不清是哪一处起了作用。
