第二章:日志的艺术: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%。

核心原则: 生产环境只保留 INFO 及以上级别。DEBUG 日志只在开发阶段开启。

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 的“摄像头”

埋点,就是在关键位置插入日志。说白了,就是告诉程序:“嘿,你走到这里了,告诉我发生了什么。”

我常用的埋点位置:

  1. 信号生成处:记录计算信号时的关键变量
  2. 开仓/平仓条件判断处:记录为什么满足/不满足条件
  3. 下单前后:记录订单参数和返回结果
  4. 异常捕获处:记录完整的异常堆栈

举个例子,一个简单的均线策略埋点:

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 知识体系总览

下面这张图,是我对日志系统的理解。你可以把它当成一个检查清单:

日志系统核心架构 Loguru 日志系统 日志分级 TRACE / DEBUG / INFO SUCCESS / WARNING ERROR / CRITICAL 生产环境只保留 INFO+ 日志轮转 rotation: 按时间/大小轮转 retention: 自动清理旧日志 compression: 压缩节省空间 enqueue: 异步写入防阻塞 策略埋点 信号生成处记录变量 开平仓条件判断处 下单前后记录参数 异常捕获处记录堆栈 性能优化 异步写入 (enqueue=True) 按级别过滤,减少 I/O 调试定位 彩色输出,一眼定位 异常追踪到代码行

这张图把日志系统拆成了五个维度。你写策略的时候,可以对照着检查:分级做好了吗?轮转配了吗?埋点够细吗?性能有影响吗?调试方便吗?

2.7 实战建议:从今天开始改变

说了这么多,其实就一句话:别等到出 Bug 了才想起日志。我见过太多人,策略跑得好好的,突然某天收益曲线不对劲,然后花三天时间翻代码,最后发现是数据源少传了一个字段。如果一开始就埋好点,十分钟就能定位到问题。

我个人建议,每个策略项目都建一个 logs/ 目录,日志文件名带上日期。这样你回头看的时候,能清楚知道哪天发生了什么。

嗯,日志的艺术,说白了就是“未雨绸缪”四个字。今天花十分钟配好日志,明天可能省下十个小时的调试时间。这笔账,怎么算都划算。


公众号:蓝海资料掘金营,微信deep3321