上线那天你最自信。三个月后你最慌。中间那段叫"没人看"。
策略上线了。
你发了个朋友圈。
"量化交易,启动。"
一堆人点赞。
你飘了。
然后------
第一周,每天看八遍。
第二周,每天看两遍。
第三周------
不看了。
一个月后。
策略悄悄吃了个跌停。
你都不知道。
等到发现的时候------
已经亏了六位数。
操。
这就是99%的量化新手的死法。
不是策略不行。
是没人看着 。
策略上线只是开始。
监控,才是日常。

一、监控的三个维度
很多人以为监控就是------
每天看看赚了多少。
太浅了。
监控有三个维度。
缺一个,迟早出事。
第一维度:策略层面
你最关心的。
每日收益。
持仓情况。
交易信号。
最大回撤。
夏普比率。
这些数字------
每天必须看。
不是每周。
不是每月。
每天。
为什么?
因为策略会"漂移"。
什么意思?
你回测的时候------
策略在沪深300上表现牛逼。
年化30%。
跑了一个月------
沪深300风格变了。
策略开始频繁止损。
胜率从65%掉到45%。
你没发现。
两个月后------
账户亏了15%。
你品品。
策略还是那个策略。
代码没改一行。
但市场变了。
策略和市场不匹配了。
这就是"策略漂移"。
不监控,你永远不知道。
具体看什么?
每日收益。
别光看绝对数。
要看------跟回测的日均收益比,偏离了多少。
偏离超过2倍标准差,就该警觉了。
持仓集中度。
你的策略是分散持仓的。
突然某天------
单只股票仓位占了40%。
出bug了。
信号频率。
策略一天发2个信号,正常。
突然一天发了20个信号------
不是市场炸了,是代码炸了。
策略不会背叛你,但市场会改变它的性格。监控,就是听它有没有变声。
第二维度:系统层面
策略层面看的是"赚没赚"。
系统层面看的是"跑没跑"。
网络延迟。
订单状态。
错误日志。
进程存活。
这些东西------
你看不到钱。
但一旦出问题------
钱就没了。
真实案例。
有个哥们,策略跑得挺好。
QMT客户端挂在服务器上。
每天自动生成信号、自动下单。
三个月后的一天------
服务器网卡掉了。
QMT断线了。
策略还在跑。
信号还在发。
但订单------
一个都送不出去。
那天正好有个大涨的票。
策略发了买入信号。
订单没出去。
等网络恢复了------
票已经涨停了。
少赚了八万。
更要命的是------
策略以为订单成交了。
持仓记录里多了这个票。
但实际上------
没买到。
后面的逻辑全乱了。
这就是系统层面的问题。
不监控,就是裸奔。
具体看什么?
进程存活。
你的策略脚本是不是还在跑。
别笑。
Python脚本崩溃是家常便饭。
内存泄漏。
网络超时。
一个没捕获的异常------
进程就死了。
订单状态。
每一笔订单------
报了吗?
成了吗?
部分成交了吗?
撤了吗?
这些状态必须实时记录。
错误日志。
每一个warning。
每一个error。
都要留痕。
平时不看。
出事了------
翻日志。
没有日志------
你就是个瞎子。
代码不会无缘无故地坏。但它会悄悄地坏。错误日志就是它的遗书。
第三维度:数据层面
最容易被忽视的。
数据更新了吗?
数据质量怎么样?
有没有异常值?
量化策略的燃料------
就是数据。
数据出了问题------
策略就是个吃错药的疯子。
案例。
2024年某个周五。
某数据源的系统升级。
收盘数据延迟了4个小时。
某量化团队的策略------
6点钟开始跑。
拿到的收盘价------
是昨天的 。
策略一看------
哎?这个票今天没动?
信号没了。
但实际呢?
这个票今天跌了5%。
策略基于昨天的数据做了判断。
第二天开盘------
直接亏了。
更离谱的------
数据源偶尔会返回负数价格 。
某只股票的价格字段:-3.5。
你没做校验。
策略直接买了。
因为------
-3.5的PE。
"太便宜了"。
不用我说了吧。
具体监控什么?
数据更新时间。
每天收盘后------
数据在几点更新的?
超过18:00还没更新,就该打电话了。
数据完整性。
5000只股票。
今天只更新了4800只。
少了200只。
那200只------
你的策略要不要交易?
异常值检测。
价格 > 昨日收盘价 × 1.2?(非涨停板的情况下)
成交量 = 0?(停牌但标记为正常交易?)
这些都要自动检查。
垃圾进,垃圾出。你的策略再聪明,喂它屎,它就拉屎。
二、告警阈值设置:让系统替你盯着
人靠不住。
你会忘。
你会懒。
你会觉得"今天不看了,应该没事"。
"应该没事"这四个字------是亏钱的开始。
所以------
要让系统替你盯着。
出事了------
自动报警。
怎么写?
完整代码,能跑:
// python
pip安装依赖:
pip install pandas numpy requests
import pandas as pd
import numpy as np
import datetime
import logging
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
============================================
配置区 - 根据你的实际情况修改
============================================
邮件告警配置(以QQ邮箱为例)
EMAIL_CONFIG = {
"smtp_server": "smtp.qq.com",
"smtp_port": 465,
"sender": "your_email@qq.com", # 你的发件邮箱
"password": "your_auth_code", # 邮箱授权码(不是登录密码)
"receiver": "your_email@qq.com", # 收件邮箱
}
策略监控阈值
STRATEGY_THRESHOLDS = {
"max_daily_loss_pct": -3.0, # 单日最大亏损百分比
"max_drawdown_pct": -10.0, # 最大回撤百分比
"min_sharpe_ratio": 0.5, # 最低夏普比率(近20日滚动)
"max_position_pct": 25.0, # 单只股票最大仓位百分比
"max_daily_signals": 10, # 单日最大信号数
"signal_freq_std_multiplier": 2.0, # 信号频率偏离均值的标准差倍数
}
系统监控阈值
SYSTEM_THRESHOLDS = {
"max_latency_seconds": 5, # 最大网络延迟(秒)
"max_error_count_per_hour": 3, # 每小时最大错误次数
"max_order_pending_minutes": 10, # 订单最大挂起时间(分钟)
}
数据监控阈值
DATA_THRESHOLDS = {
"max_data_delay_hours": 2, # 数据最大延迟时间(小时)
"min_data_completeness_pct": 99.0, # 数据最低完整率百分比
"price_change_alert_pct": 15.0, # 价格异常波动百分比
}
============================================
日志配置
============================================
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
handlers=
logging.FileHandler("monitor.log", encoding="utf-8"),
logging.StreamHandler()
)
logger = logging.getLogger("QuantMonitor")
============================================
告警发送
============================================
class Alerter:
"""告警发送器"""
def init(self, config: dict):
self.config = config
def send_email(self, subject: str, body: str):
"""发送邮件告警"""
try:
msg = MIMEMultipart()
msg"From" = self.config"sender"
msg"To" = self.config"receiver"
msg"Subject" = f"【量化告警】{subject}"
msg.attach(MIMEText(body, "plain", "utf-8"))
with smtplib.SMTP_SSL(
self.config"smtp_server",
self.config"smtp_port"
) as server:
server.login(self.config"sender", self.config"password")
server.send_message(msg)
logger.info(f"告警邮件已发送: {subject}")
except Exception as e:
logger.error(f"告警邮件发送失败: {e}")
def alert(self, level: str, dimension: str, message: str):
"""
统一告警入口
level: WARNING / CRITICAL
dimension: 策略 / 系统 / 数据
"""
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
subject = f"{level} {dimension} - {timestamp}"
body = f"""告警级别: {level}
告警维度: {dimension}
告警时间: {timestamp}
告警内容: {message}
量化监控系统自动发送
"""
logger.warning(f"{level}{dimension} {message}")
self.send_email(subject, body)
============================================
策略层面监控
============================================
class StrategyMonitor:
"""策略层面监控"""
def init(self, thresholds: dict, alerter: Alerter):
self.thresholds = thresholds
self.alerter = alerter
def check_daily_pnl(self, daily_pnl_pct: float):
"""
检查每日收益
daily_pnl_pct: 当日收益百分比,如 -2.5 表示亏损2.5%
"""
if daily_pnl_pct <= self.thresholds"max_daily_loss_pct":
self.alerter.alert(
"CRITICAL", "策略",
f"单日亏损 {daily_pnl_pct:.2f}% 超过阈值 "
f"{self.thresholds'max_daily_loss_pct'}%"
)
def check_drawdown(self, current_drawdown_pct: float):
"""
检查回撤
current_drawdown_pct: 当前回撤百分比(负数),如 -8.0
"""
if current_drawdown_pct <= self.thresholds"max_drawdown_pct":
self.alerter.alert(
"CRITICAL", "策略",
f"当前回撤 {current_drawdown_pct:.2f}% 超过阈值 "
f"{self.thresholds'max_drawdown_pct'}%"
)
def check_sharpe(self, returns_series: pd.Series):
"""
检查近20日滚动夏普比率
returns_series: 日收益率序列
"""
if len(returns_series) < 20:
return
recent = returns_series.tail(20)
sharpe = (recent.mean() / recent.std()) * np.sqrt(252) if recent.std() > 0 else 0
if sharpe < self.thresholds"min_sharpe_ratio":
self.alerter.alert(
"WARNING", "策略",
f"近20日滚动夏普比率 {sharpe:.2f} 低于阈值 "
f"{self.thresholds'min_sharpe_ratio'}"
)
def check_position_concentration(
self, positions: dict, total_value: float
):
"""
检查持仓集中度
positions: {股票代码: 持仓市值}
total_value: 账户总资产
"""
for stock, value in positions.items():
pct = (value / total_value) * 100
if pct > self.thresholds"max_position_pct":
self.alerter.alert(
"WARNING", "策略",
f"{stock} 仓位占比 {pct:.1f}% 超过阈值 "
f"{self.thresholds'max_position_pct'}%"
)
def check_signal_frequency(self, today_signals: int, history_signals: list):
"""
检查信号频率是否异常
today_signals: 今日信号数
history_signals: 过去30日每日信号数列表
"""
if len(history_signals) < 10:
return
mean = np.mean(history_signals)
std = np.std(history_signals)
if today_signals > self.thresholds"max_daily_signals":
self.alerter.alert(
"WARNING", "策略",
f"今日信号数 {today_signals} 超过上限 "
f"{self.thresholds'max_daily_signals'}"
)
elif std > 0 and abs(today_signals - mean) > self.thresholds
"signal_freq_std_multiplier"
* std:
self.alerter.alert(
"WARNING", "策略",
f"今日信号数 {today_signals} 偏离历史均值 "
f"{mean:.1f} 超过 {self.thresholds'signal_freq_std_multiplier'} 倍标准差"
)
============================================
数据层面监控
============================================
class DataMonitor:
"""数据层面监控"""
def init(self, thresholds: dict, alerter: Alerter):
self.thresholds = thresholds
self.alerter = alerter
def check_data_update_time(self, last_update_time: datetime.datetime):
"""
检查数据更新时间
last_update_time: 数据最后更新时间
"""
now = datetime.datetime.now()
delay_hours = (now - last_update_time).total_seconds() / 3600
if delay_hours > self.thresholds"max_data_delay_hours":
self.alerter.alert(
"CRITICAL", "数据",
f"数据已 {delay_hours:.1f} 小时未更新,"
f"最后更新: {last_update_time.strftime('%Y-%m-%d %H:%M:%S')}"
)
def check_data_completeness(self, total_count: int, updated_count: int):
"""
检查数据完整性
total_count: 应更新的股票总数
updated_count: 实际更新的股票数
"""
completeness = (updated_count / total_count) * 100
if completeness < self.thresholds"min_data_completeness_pct":
missing = total_count - updated_count
self.alerter.alert(
"WARNING", "数据",
f"数据完整率 {completeness:.1f}%,缺失 {missing} 只股票"
)
def check_price_anomaly(
self, stock_code: str, current_price: float, prev_close: float
):
"""
检查价格异常值
"""
if prev_close <= 0:
self.alerter.alert(
"CRITICAL", "数据",
f"{stock_code} 昨收价异常: {prev_close}"
)
return
change_pct = abs((current_price - prev_close) / prev_close * 100)
if current_price < 0:
self.alerter.alert(
"CRITICAL", "数据",
f"{stock_code} 价格异常: {current_price}(负数)"
)
elif change_pct > self.thresholds"price_change_alert_pct":
self.alerter.alert(
"WARNING", "数据",
f"{stock_code} 价格波动 {change_pct:.1f}%,"
f"昨收 {prev_close} -> 今 {current_price}"
)
============================================
每日监控主流程
============================================
class DailyMonitor:
"""每日监控主入口"""
def init(self):
self.alerter = Alerter(EMAIL_CONFIG)
self.strategy_monitor = StrategyMonitor(STRATEGY_THRESHOLDS, self.alerter)
self.data_monitor = DataMonitor(DATA_THRESHOLDS, self.alerter)
def run_daily_check(self):
"""
执行每日检查。
实际使用时,将下面的模拟数据替换为你的真实数据源。
"""
logger.info("===== 开始每日监控检查 =====")
--- 1. 策略层面 ---
示例:当日收益 -1.5%,回撤 -6%,近20日收益率序列
self.strategy_monitor.check_daily_pnl(daily_pnl_pct=-1.5)
self.strategy_monitor.check_drawdown(current_drawdown_pct=-6.0)
模拟近20日收益率
np.random.seed(42)
returns = pd.Series(np.random.normal(0.001, 0.02, 20))
self.strategy_monitor.check_sharpe(returns)
模拟持仓
positions = {"600519": 250000, "000858": 80000, "601318": 50000}
total_value = 500000
self.strategy_monitor.check_position_concentration(positions, total_value)
模拟信号频率
self.strategy_monitor.check_signal_frequency(
today_signals=3,
history_signals=2, 3, 1, 2, 4, 2, 3, 1, 2, 3, 2, 1, 3, 2, 4, 2, 1, 3, 2, 2
)
--- 2. 数据层面 ---
self.data_monitor.check_data_update_time(
last_update_time=datetime.datetime.now() - datetime.timedelta(hours=1)
)
self.data_monitor.check_data_completeness(
total_count=5000, updated_count=4980
)
self.data_monitor.check_price_anomaly(
stock_code="600519", current_price=1800.0, prev_close=1780.0
)
模拟异常数据检测
self.data_monitor.check_price_anomaly(
stock_code="000001", current_price=-3.5, prev_close=10.5
)
logger.info("===== 每日监控检查完成 =====")
============================================
运行
============================================
if name == "main":
monitor = DailyMonitor()
monitor.run_daily_check()
把这段代码存成 monitor.py。
改一下邮箱配置。
跑一下试试。
// bash
pip install pandas numpy
python monitor.py
你会收到一封告警邮件------
因为模拟数据里故意放了个负数价格 。
看出区别没?
这不是玩具代码。
这是生产级 的监控骨架。
你往里填真实数据就行。
三、日常运维 Checklist
光有代码不够。
你得有个习惯 。
每天固定时间。
对着清单。
一项一项打勾。
像飞行员起飞前一样。
飞行员不检查仪表------
会死人。
你不检查策略------
会亏钱。
道理一样。
盘前(9:00前)
|--------|-------------------|----------------|
| 序号 | 检查项 | 确认 |
| 1 | 昨晚数据是否全部更新完成 | □ |
| 2 | 数据质量检查是否通过(无异常值) | □ |
| 3 | 策略进程是否存活(`ps aux | grep python`) |
| 4 | 昨日报错日志是否已检查 | □ |
| 5 | 今日是否有停牌/涨跌停影响持仓 | □ |
| 6 | 网络连通性是否正常 | □ |
| 7 | QMT客户端是否已登录 | □ |
盘中(9:30-15:00)
|--------|-----------------|--------|
| 序号 | 检查项 | 确认 |
| 8 | 开盘后30分钟内信号是否正常 | □ |
| 9 | 订单是否正常成交(无异常挂单) | □ |
| 10 | 盘中策略收益是否在预期范围内 | □ |
| 11 | 持仓集中度是否正常 | □ |
盘后(15:30后)
|--------|----------------|--------|
| 序号 | 检查项 | 确认 |
| 12 | 收盘数据是否开始更新 | □ |
| 13 | 当日交易记录是否完整录入 | □ |
| 14 | 当日收益是否与预期偏离过大 | □ |
| 15 | 是否需要调整参数(记录原因) | □ |
| 16 | 监控日志是否已归档 | □ |
没有checklist的策略,就像没有仪表盘的飞机。你觉得自己飞得挺稳,其实已经偏航了。
16项。
每天10分钟。
你嫌烦?
那你等着亏钱的时候------
就不只是10分钟了。
四、监控日志记录模板
监控不能只看。
得记下来 。
为什么?
因为你要复盘 。
三个月后------
你回头看。
哪天出了问题。
哪天收益异常。
哪天信号发多了。
全在日志里。
没有日志------
你就是条没有记忆的金鱼。
日志模板:
============ 监控日志 ============
日期:2025-01-15
记录人:自动监控 / 人工补充
【策略层面】
当日收益:+0.85%
当月累计收益:+3.2%
最大回撤(近20日):-4.1%
夏普比率(近20日滚动):1.32
今日信号数:3
持仓数量:8只
最大单只仓位:招商银行 12.3%
【系统层面】
策略进程状态:正常运行(PID 12345)
QMT客户端状态:已登录
订单总数:5
成交:4 | 撤单:1 | 挂单:0
错误日志:无
网络延迟:平均 0.3s
【数据层面】
数据更新时间:16:15
数据完整率:100%(5000/5000)
异常值检测:通过
【异常记录】
无异常。
【备注】
市场整体偏强。沪深300 +1.2%。
策略表现符合预期。
============ 日志结束 ============
每天一份。
存成文件。
按日期命名。
monitor_2025-01-15.log
monitor_2025-01-16.log
...
三个月后------
你就有了一座金矿。
回头看------
哪天策略漂移了。
哪天数据出了问题。
哪天系统出了bug。
全有据可查。
日志不是写给今天看的,是写给三个月后的自己看的。那时候你会感谢今天的勤快。
五、进阶:自动化运维
人力有时而穷。
上面那些------
是起步。
真正的老手------
全自动化。
数据更新?自动检查。
进程挂了?自动重启。
信号异常?自动暂停策略。
怎么做?
两个方向:
第一,定时任务。
Linux用cron。Windows用任务计划。
每天固定时间跑检查脚本。
不用人记。
// bash
Linux crontab 示例
每个交易日16:30自动执行盘后检查
30 16 * * 1-5 python /home/user/quant/monitor.py >> /home/user/quant/cron.log 2>&1
第二,进程守护。
策略进程挂了------
自动拉起来。
用 supervisor 或者 systemd。
// ini
/etc/supervisor/conf.d/quant_strategy.conf
program:quant_strategy
command=python /home/user/quant/strategy.py
directory=/home/user/quant
autostart=true
autorestart=true
stderr_logfile=/home/user/quant/strategy_err.log
stdout_logfile=/home/user/quant/strategy_out.log
user=user
配好了------
进程死了。
秒级重启。
你连告警都来不及收到。
这才是真正的"无人值守"。
但------
无人值守的前提------
是有人盯过 。
前几个月。
每天看。
每周看。
看到你对这个策略了如指掌。
然后------
你才敢放手。
自动化不是让你变懒,是让你把精力花在值得花的地方。但前提是你先花过那个精力。
六、几个血的教训
最后,讲几个真实案例。
都是------
没做好监控。
案例一:周末忘了关策略。
某个量化新手。
策略挂在本地电脑上。
周五收盘------
没关。
周六凌晨。
某数据源更新了测试数据。
策略拿到了。
以为是正常数据。
基于测试数据------
下了单。
好在------
周末不能交易。
订单没出去。
但------
策略的持仓记录------
乱了。
周一开盘。
策略以为自己有持仓。
实际上没有。
后面的逻辑------
全错了。
案例二:一次手动干预,毁了三个月的回测。
有个老手。
策略跑得挺好。
但某天------
手痒了。
手动改了个参数。
"这个阈值太高了,调低一点应该更好。"
调了。
跑了两周。
亏了。
调回去。
又跑了两周。
还是亏了。
为什么?
因为他调参的时候------
用的是那两周的数据。
过拟合了。
三个月的回测成果------
毁于一次手痒。
教训------
参数调整必须记录。
什么时候调的。
调了什么。
为什么调。
全写进日志。
不然------
你都不知道自己做过什么。
案例三:一个warning,被忽略了47天。
服务器上。
策略每天输出一个warning:
WARNING: Data source returned NULL for stock 002XXX
运维的人------
看了一眼。
"应该是个别股票,没关系。"
47天后。
那个warning变成了------
200只股票返回NULL。
策略在这47天里------
一直基于不完整的数据做决策。
亏了多少钱?
没人知道。
因为------
没人认真看过日志。
每一个warning都是策略在喊疼。你不听,它就忍着。忍到那天------它不喊了,你才知道它死了。
总结
监控三个维度。
策略层面------赚没赚,偏没偏。
系统层面------跑没跑,死没死。
数据层面------对不对,全不全。
告警要自动化。
checklist要每天做。
日志要每天记。
上线只是开始。
监控,才是日常。
你花三个月写策略。
但你得花每一天 看策略。
这就是量化的真相。
不是写完代码就完了。
是写完代码------
才刚刚开始。
量化交易最贵的东西不是代码,是注意力。你的注意力在哪里,你的钱就在哪里。