问题描述
解决方案
在聚宽(JoinQuant)平台上,策略因内存占用过大而失败通常是因为回测过程中缓存了过多的历史数据、全局变量(g对象)存储了过大的DataFrame,或者未及时释放不再使用的对象。
以下是针对聚宽平台的具体优化方法和建议:
1. 关闭系统自动缓存 (最直接有效)
聚宽系统默认会开启缓存以加快回测速度,但这会消耗大量内存。如果你的策略逻辑复杂或涉及大量标的,建议在 initialize 中显式关闭缓存。
- API:
disable_cache() - 副作用: 策略运行速度会变慢,但能显著降低内存峰值。
2. 优化全局变量 g 的使用
g 对象中的变量会被持久化(序列化),且在模拟盘中每次重启都会恢复。
- 不要在
g中存储巨大的 DataFrame 或 List:例如,不要把几千只股票过去一年的行情数据存到g.history_data中。 - 不要跨日缓存行情数据:API 文档明确建议“不要跨日期缓存 API 返回的结果”。应在
handle_data或定时函数中即取即用,用完即弃。 - 清理不再使用的变量:如果
g中某些数据只在盘前使用,用完后可以使用del g.variable手动删除。
3. 优化数据获取方式
- 使用
df=False:在调用history或attribute_history时,如果不需要 pandas DataFrame 的高级功能,可以将参数设置为df=False。返回的dict或numpy.ndarray比 DataFrame 占用内存小得多。 - 按需获取:不要一次性获取所有股票的数据。例如,不要在
initialize里一次性取全市场股票的行情,而是在需要计算信号时,分批次或只针对特定股票池获取。
4. 手动触发垃圾回收 (GC)
Python 的垃圾回收机制有时会有延迟。对于内存敏感的策略,可以在每天收盘后手动强制回收内存。
5. 避免大对象的累积
- 检查列表/字典:检查代码中是否有
list.append()或dict赋值操作在不断增加长度而从未清理(例如记录每一笔订单详情到全局列表中,回测时间一长就会爆内存)。 - 绘图函数
record:不要在record中传入过大的数据结构,只记录必要的数值。
优化后的代码结构示例
下面是一个应用了上述优化技巧的代码框架:
# -*- coding: utf-8 -*-
import gc
def initialize(context):
# 1. 【关键】关闭缓存,牺牲速度换取内存空间
disable_cache()
# 设定基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 初始化全局变量,只存必要的小数据
g.security_pool = []
def before_trading_start(context):
# 每日更新股票池,避免长期存储大列表
g.security_pool = get_index_stocks('000300.XSHG')
def handle_data(context, data):
# 2. 【关键】数据即取即用,不要存入全局变量 g
# 3. 【关键】如果不需要DataFrame特性,使用 df=False 节省内存
# 获取过去5天的收盘价,返回的是 dict (key为股票代码, value为numpy数组)
hist_data = history(5, unit='1d', field='close', security_list=g.security_pool, df=False)
for stock in g.security_pool:
# 使用 numpy 数组进行计算,内存占用更低
prices = hist_data[stock]
if len(prices) > 0:
ma5 = prices.mean()
current_price = prices[-1]
# 简单的交易逻辑示例
if current_price > ma5:
order_target_value(stock, 10000)
elif current_price < ma5:
order_target(stock, 0)
def after_trading_end(context):
# 4. 【关键】每日收盘后手动强制垃圾回收
gc.collect()
print("今日交易结束,已执行内存回收")
总结
如果遇到内存溢出(OOM),首先尝试在 initialize 中添加 disable_cache()。如果问题依旧,请检查代码逻辑,确保没有在 g 对象中通过循环不断累积数据(如 g.data_list.append(...)),并尝试在 after_trading_end 中加入 gc.collect()。
Q&A
Q: disable_cache() 会让回测慢多少?
A: 这取决于策略获取数据的频率。如果策略频繁调用 history 或 get_price,关闭缓存后,每次调用都会重新从磁盘或数据库读取数据,速度可能会下降 30% 到 50% 甚至更多,但能有效避免内存崩溃。
Q: 为什么不能在 g 变量里存大的 DataFrame?
A: g 变量是为了持久化设计的,聚宽平台会定期将 g 序列化保存到磁盘。如果 g 过大(超过 30MB),不仅序列化会失败,而且在内存中长期驻留会导致可用内存迅速耗尽。
Q: df=False 返回的是什么数据类型?
A: 当设置 df=False 时,history 和 attribute_history 返回的是一个字典(Dict),Key 是股票代码,Value 是 numpy.ndarray。Numpy 数组比 Pandas DataFrame 更加轻量,内存效率更高。