🚀 AI 一键生成 joinquant 策略代码

解决 JoinQuant 模拟交易内存溢出、进程被杀及 get_bars 内存泄漏问题

joinquant | 发布时间: 2026/5/20
以下内容由 EasyQuant 生成。

问题描述

策略模拟莫名爆内存? 模拟程序运行结束后台还在执行什么? get_bars如何释放内存?

问题:
回测通过的代码,模拟时候出现几个问题,搞了几天都解决不掉, 烦请聚宽高人给看看.....

1. 用内存监测代码,程序运行结束时内存只有1.8042G,正常运行完之后,过了一段时间(大概2个小时),跳出“进程占用内存过大(3.8G),被强制杀掉!”。
1)试过多次,一直有这个问题,难道是内存监控代码有问题?代码是从社区下载的聚宽高人的代码:
def info_memo(context,time):
info = psutil.virtual_memory()
print ('memory_info:',time,round(psutil.Process(os.getpid()).memory_info().rss/1024.0/1024.0/1024.0,4))
2)如果真是内存占用过大,为什么不是在程序正常运行中爆掉,而是过了一段时间才出现强制杀掉的提示?

2.在提示进程被强制杀掉之后,过了若干时间,程序又自动开始执行,而且,时间显示是从更早的时间开始!如图,进程杀掉是在9:55之后, 过了一段时间后程序自动执行是从9:49:33开始,和被杀掉的进程的开始执行时间相同,是什么情况?

3.内存泄露
社区其他高人碰到的get_bars内存释放不掉的问题,也遇到了!
函数中调用get_bars取数据,用del,gc.collect()释放内存完全无用,代码反复优化,发现原因就是get_bars调用,没法继续优化下去了,很无力!代码如下:

for stock in security: #security是剔除ST,停牌等的当日股票列表,大概2400个
today_data = get_bars(stock, count = 229, unit='1d',fields='open','close','high','low','volume','money'],
include_now=True)
del today_data
gc.collect()

换成get_price,history,attribute_history尝试,差别不大,而且都del释放不掉!

解决方案

针对您在 JoinQuant 模拟交易中遇到的内存溢出、进程被杀以及 get_bars 内存泄漏问题,以下是详细的解答和优化方案:

1. 为什么程序运行结束后才爆内存被杀?

原因分析:模拟盘的状态保存与序列化机制
在 JoinQuant 的模拟交易中,每天的交易逻辑运行结束后,进程并不会立即退出,而是会进入状态保存阶段(相当于休眠)。系统会使用 pickle 将您的用户账户、持仓信息以及全局变量 g 对象序列化并保存到磁盘,以便第二天恢复。

  • 如果您的 g 对象中存储了大量的数据(例如缓存了大量的 DataFrame、历史行情数据等),在执行 pickle.dumps 序列化时,会瞬间消耗大量的内存。
  • 这就是为什么您的程序在正常逻辑执行完时内存只有 1.8G,但在随后的状态保存阶段,内存飙升到 3.8G 从而触发了系统的内存限制(模拟盘进程占用内存不能超过 3G)被强制杀掉。

解决方案:

  • 清理不必要的全局变量:在每天的 after_trading_end 中,将 g 中不需要跨日保存的大型数据清理掉(如 g.history_data = None)。
  • 使用双下划线忽略序列化:如果某些变量不需要被保存,可以在命名时以 __ 开头(例如 g.__temp_data),系统在序列化时会自动忽略这些变量。

2. 为什么进程被杀后会自动重启,且时间回退?

原因分析:系统的容错与恢复机制
当模拟盘进程因为内存超限或其他异常被系统强制杀掉后,JoinQuant 的底层调度系统会检测到任务失败,并尝试进行自动重试/恢复

  • 恢复时,系统会加载上一次成功保存的状态(通常是当天的初始状态或上一个成功的检查点)。
  • 因此,程序重新启动时,逻辑时间(context.current_dt)会回退到崩溃前的时间点(例如 9:49:33),重新执行未完成的逻辑。

3. get_bars 内存释放不掉(内存泄漏)怎么办?

原因分析:底层缓存机制与循环调用

  1. 缓存机制:为了加快运行速度,JoinQuant 系统默认启用了底层缓存机制。您通过 get_barsget_price 等 API 获取的数据会被缓存在内存中,因此使用 delgc.collect() 是无法释放这部分内存的。
  2. 循环调用 API:您在 for 循环中对 2400 只股票逐一调用 get_bars,这不仅会产生大量的缓存对象,还会带来极大的网络和函数调用开销,导致内存迅速膨胀和运行缓慢。

解决方案:

方案 A:关闭系统缓存(治标)
如果您确实不需要缓存,可以在 initialize 函数中调用 disable_cache() 来关闭缓存机制。这样获取的数据在使用完毕后就可以被正常回收。

def initialize(context):
    disable_cache() # 关闭缓存机制,防止内存占用过大

注意:关闭缓存会导致策略运行速度明显下降。

方案 B:批量获取数据(治本,强烈推荐)
get_barsget_price 等 API 都是支持传入股票列表进行批量获取的。您完全不需要使用 for 循环单只获取,一次性获取 2400 只股票的数据不仅内存占用更集中、更易管理,而且运行速度会提升数十倍。

优化后的代码示例:

# security 是包含 2400 只股票的 list
# 直接传入 list,df=False 返回 dict (key为股票代码,value为 numpy.ndarray)
# df=True 返回 MultiIndex 的 DataFrame
today_data = get_bars(security, count=229, unit='1d', 
                      fields=['open','close','high','low','volume','money'], 
                      include_now=True, df=False)

# 随后您可以直接从字典中提取单只股票的数据进行处理
for stock in security:
    stock_data = today_data.get(stock)
    if stock_data is not None:
        # 进行您的逻辑计算
        pass

# 处理完毕后,清理大对象
today_data = None

总结建议:

  1. 检查并清理 g 对象中的庞大缓存数据,避免序列化时爆内存。
  2. 坚决避免在 for 循环中单只股票调用数据 API,改用传入 list 批量获取。
  3. 如果内存依然紧张,在 initialize 中加入 disable_cache()