问题描述
解决方案
在聚宽(JoinQuant)的策略编写中,context 和 g 是两个最核心的对象,贯穿于策略运行的始终。简单来说,context 代表“系统的当前状态”(只读为主),而 g 代表“策略的全局记忆”(读写为主)。
以下是详细的区别对比与使用指南:
1. 核心定义与区别
| 特性 | context 对象 | g 对象 |
|---|---|---|
| 全称 | Context(上下文环境) | Global(全局变量容器) |
| 定义者 | 系统定义。由回测/模拟交易引擎自动生成和更新。 | 用户定义。由策略开发者在 initialize 中初始化并管理。 |
| 主要用途 | 获取账户信息(资金、持仓)、时间信息(当前日期)、回测设定等。 | 存储策略逻辑变量(如目标股票、计数器、状态标记),在不同函数间传递数据。 |
| 读写权限 | 只读(建议)。尝试修改 context 中的系统属性(如强行修改现金余额)通常无效或报错。 |
读写。用户可以随意赋值、修改、删除其中的变量。 |
| 持久化 | 自动重置。每次运行或按天重启时,系统会根据交易记录重新构建。 | 持久保存。在模拟交易中,g 对象的内容会被序列化保存到磁盘,第二天重启时自动恢复(除非变量名以 __ 开头)。 |
2. context 对象详解与使用
context 是一个包含当前策略运行环境所有信息的容器。你主要通过它来“看”现在的状况,而不是去“改”它。
常用属性
context.portfolio: 账户信息总览。context.portfolio.available_cash: 当前可用资金。context.portfolio.total_value: 总资产(现金+持仓市值)。context.portfolio.positions: 当前持仓字典(Key为标的代码)。
context.current_dt: 当前单位时间的datetime对象(回测时的“现在”)。context.previous_date: 前一个交易日的date对象。context.run_params: 回测的运行参数(如开始日期、结束日期、频率)。
正确使用示例
def handle_data(context, data):
# 1. 获取当前时间
now = context.current_dt
# 2. 获取当前可用资金
cash = context.portfolio.available_cash
# 3. 判断某只股票是否持仓
if '000001.XSHE' in context.portfolio.positions:
# 获取该股票的持仓数量
amount = context.portfolio.positions['000001.XSHE'].total_amount
# 获取持仓成本
cost = context.portfolio.positions['000001.XSHE'].avg_cost
3. g 对象详解与使用
g 是一个全局对象,专门用于在不同的函数(如 initialize, handle_data, run_daily 调用的函数)之间共享数据。
关键特性
- 生命周期长:在
initialize中定义后,在整个回测或模拟交易期间一直存在。 - 模拟盘持久化:在模拟交易中,系统每天收盘后会把
g对象保存(Pickle序列化),第二天开盘前恢复。这保证了策略的连续性(例如记录“持仓天数”)。 - 序列化限制:因为需要保存到磁盘,
g中存储的对象必须支持序列化(pickle)。- 不能保存:数据库连接对象(如
query)、打开的文件句柄、网络连接对象。 - 可以保存:字符串、数字、列表、字典、DataFrame、Series 等。
- 不能保存:数据库连接对象(如
正确使用示例
def initialize(context):
# 初始化全局变量
g.security = '000001.XSHE' # 要操作的股票
g.days_held = 0 # 持仓天数计数器
g.stop_loss_pct = 0.10 # 止损比例
def handle_data(context, data):
# 读取全局变量
stock = g.security
# 修改全局变量
if context.portfolio.positions[stock].total_amount > 0:
g.days_held += 1
else:
g.days_held = 0
# 使用全局变量进行逻辑判断
if g.days_held > 5:
order_target(stock, 0)
log.info("持仓超过5天,卖出")
4. 常见误区与注意事项
-
不要在
g中存储query对象:- 错误做法:在
initialize中写g.q = query(valuation)。这会导致模拟盘重启失败,因为query对象无法被序列化保存。 - 正确做法:如果需要全局复用
query,可以使用以双下划线开头的变量名(如g.__q),或者在process_initialize(每次进程启动时运行)中初始化它。
- 错误做法:在
-
不要修改
context的属性:- 错误做法:
context.portfolio.available_cash = 1000000。这不会真的增加你的资金,只会导致数据不一致或报错。资金变化必须通过order系列函数或inout_cash函数产生。
- 错误做法:
-
g对象的大小限制:- 在模拟交易中,
g对象序列化后的大小不能超过 30MB。不要在g中存储过大的 DataFrame 或历史数据列表。
- 在模拟交易中,
-
跨函数传参:
run_daily调用的自定义函数,其第一个参数必须是context。如果需要传递其他参数,通常就是通过g对象来传递。
总结
- Context 是你的仪表盘:看时间、看钱、看持仓。
- g 是你的笔记本:记策略参数、记中间状态、记计数器。
Q&A
Q: 为什么模拟交易重启后,我在 initialize 里定义的 g.my_var 还是昨天的值,没有被重置?
A: 因为模拟交易重启时不会重新执行 initialize 函数(该函数只在策略创建时执行一次)。系统会从磁盘读取昨天保存的 g 对象状态。如果你希望每次重启都重置某些变量,请使用 process_initialize(context) 函数,或者使用 after_code_changed(context)(仅在修改代码后触发)。
Q: 我可以在 context 里自己添加变量吗?比如 context.my_signal = 1?
A: 虽然技术上可行(Python 动态语言特性),且聚宽文档提到 context 也可以像 g 一样添加变量并持久化,但强烈不建议这样做。为了代码的可读性和规范性,请将所有自定义变量存储在 g 对象中,将 context 严格视为系统状态容器。
Q: 如何在 g 对象中存储不希望被持久化保存的临时变量?
A: 变量名以两个下划线开头,例如 g.__temp_data。系统在序列化保存 g 对象时,会自动忽略以 __ 开头的变量。这对于存储数据库连接对象或极大的临时数据非常有用。