第二章:日志的艺术:Loguru库实战、日志分级与轮转、在策略中埋点定位Bug
调试量化策略,说白了就是跟看不见的对手下棋。你明明觉得逻辑没问题,回测曲线却突然断崖式下跌。这时候,日志就是你唯一的“监控摄像头”。
我个人习惯,写策略之前先搭日志系统。别急着写核心逻辑,先把“眼睛”装好。今天我们就聊聊怎么用好 Loguru 这个库,以及怎么在策略里埋点抓 Bug。
2.1 为什么是 Loguru?
Python 自带的 logging 模块,说实话,配置起来有点繁琐。你要搞 Handler、Formatter、Filter……一套下来代码比策略逻辑还长。Loguru 不一样,它把这一切简化了。
我最早接触 Loguru 是在一个高频策略项目里。当时回测跑了一整夜,第二天发现结果不对。用 logging 查日志,发现轮转配置写错了,日志文件被覆盖了。换成 Loguru 之后,一行代码搞定轮转,再也没出过这种低级错误。
Loguru 的核心优势:
- 开箱即用,无需繁琐配置
- 自动支持日志轮转、压缩
- 彩色输出,一眼看清日志级别
- 异常追踪更友好,直接定位到代码行
2.2 快速上手:三行代码搞定日志
安装很简单:
pip install loguru
然后,在你的策略文件开头加上:
from loguru import logger
logger.debug("策略初始化完成")
logger.info("加载数据,共 5000 根 K 线")
logger.warning("滑点设置超过 0.1%,请注意回测偏差")
logger.error("数据源连接超时,请检查网络")
logger.critical("资金曲线回撤超过 30%,策略已暂停")
嗯,就这么简单。你不需要再写 logging.basicConfig() 那一套了。Loguru 默认就会输出到控制台,而且带颜色——绿色是 INFO,黄色是 WARNING,红色是 ERROR。一眼就能看出问题在哪。
logger.info("="*50) 作为分隔线,这样每次运行都能清晰看到新的会话从哪里开始。
2.3 日志分级:别把所有信息都混在一起
日志分级是个老生常谈的话题,但很多人就是做不到。你想想看,如果 DEBUG 信息和 CRITICAL 错误混在一起,你找 Bug 的时候得翻多少页?
Loguru 支持的标准级别:
| 级别 | 数值 | 使用场景 |
|---|---|---|
| TRACE | 5 | 最细粒度的调试信息,比如每笔交易的中间变量 |
| DEBUG | 10 | 开发阶段的信息,比如信号计算过程 |
| INFO | 20 | 常规运行信息,比如开仓、平仓、持仓变化 |
| SUCCESS | 25 | Loguru 自定义级别,用于标记成功事件 |
| WARNING | 30 | 潜在问题,比如滑点异常、数据缺失 |
| ERROR | 40 | 可恢复的错误,比如单次下单失败 |
| CRITICAL | 50 | 致命错误,策略必须停止 |
我在项目中遇到过一个问题:同事把所有日志都写成 logger.info(),结果生产环境日志文件一天就涨到 2GB。后来我强制要求:INFO 只记录策略状态变化,DEBUG 才记录计算细节。日志量直接降了 80%。
2.4 日志轮转:别让日志撑爆你的硬盘
量化策略跑起来,尤其是实盘,日志增长速度快得吓人。我曾经见过一个团队,回测跑了三个月,日志文件 50GB,最后服务器磁盘满了,策略直接挂掉。
Loguru 的轮转配置,一句话搞定:
from loguru import logger
logger.add(
"strategy_{time:YYYY-MM-DD}.log",
rotation="1 day", # 每天轮转一次
retention="30 days", # 保留最近 30 天的日志
compression="zip", # 压缩旧日志
level="INFO", # 只记录 INFO 及以上级别
enqueue=True # 异步写入,不影响策略性能
)
这里有几个参数值得细说:
- rotation:可以按时间("1 day"),也可以按大小("100 MB")。我建议实盘用时间轮转,方便按天排查问题。
- retention:自动清理旧日志。别舍不得删,超过 30 天的日志基本不会再看了。
- compression:压缩成 .zip 或 .gz。文本日志压缩率很高,能省 90% 的空间。
- enqueue:异步写入。策略跑得飞快,日志写入不能拖后腿。
enqueue=False,结果在高频策略里日志写入成了性能瓶颈。记住,实盘一定要开异步。
2.5 在策略中埋点:定位 Bug 的“摄像头”
埋点,就是在关键位置插入日志。说白了,就是告诉程序:“嘿,你走到这里了,告诉我发生了什么。”
我常用的埋点位置:
- 信号生成处:记录计算信号时的关键变量
- 开仓/平仓条件判断处:记录为什么满足/不满足条件
- 下单前后:记录订单参数和返回结果
- 异常捕获处:记录完整的异常堆栈
举个例子,一个简单的均线策略埋点:
def generate_signal(data):
# 计算均线
ma5 = data['close'].rolling(5).mean()
ma20 = data['close'].rolling(20).mean()
# 埋点:记录均线值
logger.debug(f"最新价格: {data['close'].iloc[-1]:.2f}, "
f"MA5: {ma5.iloc[-1]:.2f}, MA20: {ma20.iloc[-1]:.2f}")
# 判断信号
if ma5.iloc[-1] > ma20.iloc[-1] and ma5.iloc[-2] <= ma20.iloc[-2]:
logger.info(f"金叉信号触发,时间: {data.index[-1]}")
return 1 # 买入
elif ma5.iloc[-1] < ma20.iloc[-1] and ma5.iloc[-2] >= ma20.iloc[-2]:
logger.info(f"死叉信号触发,时间: {data.index[-1]}")
return -1 # 卖出
else:
logger.debug("无信号,继续持仓")
return 0
你看,每个关键节点都有日志。如果回测结果不对,翻日志就能知道是信号没触发,还是触发错了方向。
logger.trace() 级别的开关,专门记录每笔交易的完整上下文。平时不开启,出问题时打开它,就能拿到最详细的信息。
2.6 知识体系总览
下面这张图,是我对日志系统的理解。你可以把它当成一个检查清单:
这张图把日志系统拆成了五个维度。你写策略的时候,可以对照着检查:分级做好了吗?轮转配了吗?埋点够细吗?性能有影响吗?调试方便吗?
2.7 实战建议:从今天开始改变
说了这么多,其实就一句话:别等到出 Bug 了才想起日志。我见过太多人,策略跑得好好的,突然某天收益曲线不对劲,然后花三天时间翻代码,最后发现是数据源少传了一个字段。如果一开始就埋好点,十分钟就能定位到问题。
我个人建议,每个策略项目都建一个 logs/ 目录,日志文件名带上日期。这样你回头看的时候,能清楚知道哪天发生了什么。
嗯,日志的艺术,说白了就是“未雨绸缪”四个字。今天花十分钟配好日志,明天可能省下十个小时的调试时间。这笔账,怎么算都划算。
公众号:蓝海资料掘金营,微信deep3321