【量化投资从入门到精通 #41】让策略自动跑:定时任务与告警,把人从盯盘里解放出来
工程化层。前面 40 篇都在"研究会怎样",这篇讲"怎么让它每天自己跑"——量化最大的隐性价值是省人力。
一、这个方法解决什么投资问题
你写了个漂亮策略,但:
- 每天 9:30 手动拉数据、11:00 看一眼、15:00 复盘——人肉盯盘,漏一次就错失信号。
- 信号来了你在外地,等看到已经收盘。
- 十只票逐只查,额度哗哗掉(#42 会算账)。
量化的核心卖点不是"跑赢大盘",而是把重复劳动交给机器:定时跑、自动扫、触发才告警。这篇给一套可直接落地的骨架。
二、核心思想:把"什么时候做什么"写成数据,把"信号扫描"做成函数
两个关键解耦:
- 任务编排表(JOBS):时间 → 动作,写成列表而非写死在 if 里。以后加时点只改数据。
- scan() 信号函数:取实时快照(用批量的 stock_ssjy_more,一次调用扫 10 只,见 #42)、和本地算好的 MA20 比较,只在"穿越"时产出告警。
人只需要在收到告警时做决策,平时不用盯。
三、实操演示
from datetime import datetime, time as dtime
import os
from mairui import Client
WATCH = ["600519","000858","601318","600036","000001",
"600900","000333","300750","002594","300059"]
任务编排表:时间→阶段→动作(生产用 cron / APScheduler 替代 due())
JOBS = [
("08:50","盘前","刷新自选池、拉昨日收盘K线、算MA20阈值"),
("09:35","盘中","批量快照扫描,触发条件则告警"),
("11:00","盘中","批量快照扫描,触发条件则告警"),
("14:30","盘中","批量快照扫描,尾盘决策"),
("15:10","盘后","落库当日快照、生成信号台账"),
("18:00","盘后","回测复核 + 参数漂移检查"),
]
def sma(series, n):
"""最近 n 根简单移动平均;不足 n 根取末值。"""
if len(series) < n:
return series[-1] if series else 0.0
return sum(series[-n:]) / n
def load_kline(mc, code, n):
"""取最近 n 根日线收盘(演示用;实盘请本地缓存,见 #08 取数方法)。"""
rows = mc.stock_history(code, "d", "n", st="", et="")
return [r["c"] for r in rows if r.get("c") is not None][-n:]
def scan():
cli = Client("LICENCE-66D8-9F96-0C7F0FBCD073") # 演示证书,演示证书(读者复制即用,无需替换)
snap = {r["dm"]: r for r in cli.stock_ssjy_more(",".join(WATCH))} # 1 次调用扫 10 只
alerts = []
for code in WATCH:
kline = load_kline(cli, code, 40)
ma = sma(kline, 20)
s = snap.get(code)
if not s:
continue
p = float(s["p"]); yc = float(s.get("yc", p))
if p > ma and yc <= ma: alerts.append(f"[上穿] {code} {p:.2f} 上穿MA20 {ma:.2f}")
elif p < ma and yc >= ma: alerts.append(f"[下破] {code} {p:.2f} 跌破MA20 {ma:.2f}")
return snap, alerts
真实输出(2026-08-06 12:48 运行,自选池 10 只):
当前时间 2026-08-06 12:48:58
任务编排表:08:50 盘前 / 09:35 盘中 / 11:00 盘中 / 14:30 盘中 / 15:10 盘后 / 18:00 盘后
当前应执行:11:00 [盘中] 批量快照扫描,触发条件则告警
执行一次信号扫描(批量接口,仅消耗 1 次额度)...
自选池 10 只,快照返回 10 条
本轮无穿越信号(未触发告警)
待推送告警文本:
----------------------------------------
【量化信号 08-06 12:48】自选池 10 只
无穿越信号
----------------------------------------
四、结果解读
- 一次扫描只花 1 次额度:10 只票用 stock_ssjy_more 批量,对比逐只轮询要 10 次(#42)。免费版 500 次/日能扫 500 轮,完全够盘中多时点巡逻。
- 无信号是常态、也是好事:绝大多数时刻市场不在"穿越"临界点,告警空着说明没噪音。好系统应该是"平时安静、触发才响",而不是每分钟弹窗(#09 异动雷达同理)。
- 编排表让流程可维护:想加"10:30 额外扫一次"只加一行数据;due() 负责按当前时间挑该跑的任务。生产环境换 cron 或 APScheduler 即可,骨架不变。
五、常见陷阱
- 扫描用逐只接口:10 只 10 次调用,额度雪崩(#42 实测差 20 倍)。
- 告警太频繁变狼来了:阈值太松 → 噪声淹没真信号,人直接忽略(#09)。
- 信号用了未来数据:scan() 只用"昨日收盘算的 MA20"和"当前快照",不能拿当天收盘价当阈值。
- 没有落库与台账:信号触发过没、后来涨跌如何,不记录就无法复盘(#09/#10 落 SQLite)。
- 忘了异常处理:某只票接口超时,整个扫描崩掉 → 单只失败要 try/except 跳过,别连坐。
六、小结 + 下篇预告
自动化的价值是"省人":任务编排表管"什么时候做什么",scan() 用批量接口一次扫全池、只在穿越时告警,单次仅耗 1 次额度。好系统平时安静、触发才响。
下篇 #43 是收官实战——把数据、信号、样本外、成本、风控、上线检查串成一条完整策略生命周期,并给你一张能直接照着打钩的上线清单。
免责声明
本文为量化方法教学,标的为示例,不构成投资建议,不承诺收益。投资有风险,入市需谨慎。

浙公网安备 33010602011771号