问题描述
解决方案
在 JoinQuant(聚宽)平台中,g 和 context 是两个最核心的对象,但它们的用途、生命周期和持久化机制有显著的区别。
核心区别总结
| 特性 | g (Global Object) | context (Context Object) |
|---|---|---|
| 定义 | 全局变量容器,专门用于存储用户自定义的变量。 | 策略上下文对象,存储系统当前的账户状态、时间、持仓等信息。 |
| 用途 | 保存策略运行中需要跨函数使用的自定义数据(如:计数器、股票池、标志位)。 | 获取系统数据(如:当前时间、可用资金、持仓详情、回测日期)。 |
| 持久化 (模拟交易) | 支持。模拟交易每天收盘后,g 中的数据会被序列化保存到磁盘,第二天重启时自动恢复。 |
系统管理。系统会自动维护账户状态,但用户若手动向 context 添加自定义变量,虽也支持持久化,但不推荐。 |
| 安全性 | 高。专为用户设计,不会与系统变量冲突。 | 低(针对写入)。向 context 写入自定义变量可能覆盖系统现有或未来新增的属性。 |
| 读写权限 | 完全读写。 | 系统属性(如 portfolio)通常是只读的;用户自定义属性可读写。 |
1. g (全局变量对象)
g 是 "Global" 的缩写,它是专门为用户设计的全局变量容器。
- 持久化机制:在模拟交易中,策略进程会在每天收盘后暂停(休眠),第二天开盘前重启。系统会使用
pickle库将g对象序列化保存。这意味着你在g中存储的变量(如g.days_count = 10)在第二天依然存在。 - 序列化限制:
- 只有能被
pickle序列化的对象才能保存在g中(大部分 Python 原生类型如 list, dict, str, int 都可以)。 - 不可序列化对象(如数据库连接
query对象、文件句柄)不能直接保存在g中,否则会导致模拟盘保存失败。 - 临时变量:如果你希望某个变量不被保存(例如每次重启都重新初始化的变量),可以将变量名以双下划线开头(如
g.__temp_data),系统在序列化时会忽略它。
- 只有能被
- 大小限制:序列化后的状态大小不能超过 30M。
2. context (策略信息总览)
context 包含了策略运行时的所有环境信息和账户状态。
- 主要包含的系统属性:
context.current_dt: 当前单位时间的开始时间 (datetime对象)。context.portfolio: 账户信息(资金、持仓价值、收益等)。context.subportfolios: 子账户信息列表。context.universe: 当前的股票池。
- 使用建议:
- 主要用于读取系统状态(例如判断当前时间、查询剩余资金)。
- 虽然技术上允许你向
context添加自定义变量(例如context.my_variable = 1),但官方强烈建议不要这样做。因为如果未来聚宽系统升级,增加了一个名为my_variable的系统属性,你的代码就会产生冲突或被覆盖。
我应该把数据保存在哪里?
结论:你应该把所有的自定义数据保存在 g 对象中。
- 需要跨函数使用的数据:例如在
initialize中选出的股票列表,需要在handle_data中交易,请保存在g.security_list。 - 需要跨交易日记忆的数据:例如记录策略已经运行了多少天,或者记录上一次买入的价格,请保存在
g中。 - 临时的大数据或不可序列化对象:建议在
process_initialize(每次进程启动时运行)中初始化,并赋值给g中以__开头的变量(如g.__query_object)。
代码示例
# -*- coding: utf-8 -*-
def initialize(context):
# 【推荐】将自定义变量保存在 g 中
g.security = '000001.XSHE'
g.days_counter = 0
# 【不推荐】虽然可以运行,但不要向 context 写入自定义变量
# context.my_stock = '000001.XSHE'
def handle_data(context, data):
# 1. 使用 g 中的变量
g.days_counter += 1
# 2. 读取 context 中的系统信息
current_time = context.current_dt
available_cash = context.portfolio.available_cash
# 打印日志
log.info("策略运行第 %s 天" % g.days_counter)
log.info("当前时间: %s, 可用资金: %s" % (current_time, available_cash))
# 简单的交易逻辑
if g.days_counter % 5 == 0:
order(g.security, 100)
def process_initialize(context):
# 模拟盘重启时执行,用于初始化不能被序列化保存的对象
# 使用双下划线开头,告诉系统不要尝试保存这个变量到磁盘
g.__q = query(valuation)
Q&A
Q: 为什么模拟交易重启后,我在 initialize 里定义的 g 变量没有重置?
A: 因为在模拟交易中,initialize 函数只在策略创建的第一天执行一次。之后的每一天,系统是恢复之前保存的 g 对象状态,而不是重新运行 initialize。如果你需要每次重启都运行某些代码,请使用 process_initialize(context) 函数。
Q: context.portfolio 可以修改吗?
A: 不可以。context 中的系统属性(如 portfolio, positions)是只读的,或者由系统底层维护。如果你想改变持仓或资金,必须通过 order(下单)或 inout_cash(出入金)等 API 来实现,不能直接赋值修改。
Q: g 对象能存多大的数据?
A: 序列化后不能超过 30MB。如果超过这个限制,模拟盘保存状态时会失败。如果需要存储大量数据(如巨大的 DataFrame),建议写入文件(使用 write_file)或仅存储必要的状态标识,在运行时重新计算或获取数据。