🚀 AI 一键生成 joinquant 策略代码

JoinQuant 策略编程:context 和 g 全局变量的正确使用方法

joinquant | 发布时间: 2025/11/26
以下内容由 EasyQuant 生成。

问题描述

在策略中,context 对象和 g 对象有什么区别,应该如何正确使用它们?

解决方案

在聚宽(JoinQuant)的策略编写中,contextg 是两个最核心的对象,贯穿于策略运行的始终。简单来说,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 调用的函数)之间共享数据。

关键特性

  1. 生命周期长:在 initialize 中定义后,在整个回测或模拟交易期间一直存在。
  2. 模拟盘持久化:在模拟交易中,系统每天收盘后会把 g 对象保存(Pickle序列化),第二天开盘前恢复。这保证了策略的连续性(例如记录“持仓天数”)。
  3. 序列化限制:因为需要保存到磁盘,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. 常见误区与注意事项

  1. 不要在 g 中存储 query 对象

    • 错误做法:在 initialize 中写 g.q = query(valuation)。这会导致模拟盘重启失败,因为 query 对象无法被序列化保存。
    • 正确做法:如果需要全局复用 query,可以使用以双下划线开头的变量名(如 g.__q),或者在 process_initialize(每次进程启动时运行)中初始化它。
  2. 不要修改 context 的属性

    • 错误做法context.portfolio.available_cash = 1000000。这不会真的增加你的资金,只会导致数据不一致或报错。资金变化必须通过 order 系列函数或 inout_cash 函数产生。
  3. g 对象的大小限制

    • 在模拟交易中,g 对象序列化后的大小不能超过 30MB。不要在 g 中存储过大的 DataFrame 或历史数据列表。
  4. 跨函数传参

    • 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 对象时,会自动忽略以 __ 开头的变量。这对于存储数据库连接对象或极大的临时数据非常有用。