🚀 AI 一键生成 joinquant 策略代码
立即体验

聚宽因子拥挤度择时:如何优化长周期回测的内存和 API 调用?

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

问题描述

聚宽因子拥挤度择时:如何优化长周期回测的内存和 API 调用?
聚宽因子拥挤度策略长周期回测经常内存超限时,如何减少 get_current_data 和 get_fundamentals 调用、复用缓存并进行向量化计算?

解决方案

在聚宽(JoinQuant)平台上进行因子拥挤度择时策略的长周期回测时,由于涉及大量标的的历史行情、财务指标及因子计算,策略极易触发平台的内存限制(3G)或因高频调用API导致运行缓慢。以下是针对内存和API调用的深度优化方案:

1. 内存优化:合理控制缓存机制

聚宽默认开启了数据缓存以加速运行,但在长周期、大股票池回测中,缓存会不断累积导致内存溢出。

  • 关闭缓存:在 initialize 中调用 disable_cache()。虽然这会略微降低单次查询速度,但能显著降低内存占用,防止进程被系统强制杀死。
def initialize(context):
    # 关闭缓存机制,防止长周期回测内存超限
    disable_cache()
    set_option('use_real_price', True)

2. 减少 get_current_data 的高频调用

get_current_data() 是一个高耗时且在盘中按需获取数据的API。在 handle_data 或分钟回测中,切忌在循环中对每只股票单独调用。

  • 优化方案:在循环外部一次性获取所有标的的当前数据快照,或者改用 get_bars / attribute_history 批量获取。
# 错误示范:在循环中对每只股票调用
# for stock in g.stocks:
#     current_data = get_current_data()[stock]

# 正确示范:在循环外一次性获取,利用 dict 机制按需加载
def handle_data(context, data):
    current_data = get_current_data()
    for stock in g.stocks:
        if current_data[stock].paused:
            continue
        # 执行择时逻辑

3. 优化 get_fundamentals:从“每日查询”到“按季/按月批量获取”

因子拥挤度计算通常需要市值(valuation)和财务报表数据。每天在盘中调用 get_fundamentals 会产生极大的API开销。

  • 优化方案
    1. 使用 get_fundamentals_continuously:一次性获取多日的财务数据,减少I/O次数。
    2. 按调仓周期获取:如果策略是按周或按月调仓,仅在调仓日的 before_market_open 中批量获取一次,并存入全局变量 g 中复用。
def before_market_open(context):
    # 仅在需要调仓或计算拥挤度时批量获取财务数据
    q = query(valuation.code, valuation.market_cap, valuation.pe_ratio).filter(valuation.code.in_(g.stocks))
    # 获取当前时间前一天的最新财务数据
    g.fundamental_df = get_fundamentals(q, date=context.previous_date)

4. 向量化计算与因子中性化优化

在计算因子拥挤度(如因子收益率标准差、行业集中度、因子自相关性)时,避免使用 Python 的 for 循环,应充分利用 pandasnumpy 的向量化操作,并结合 jqfactor 的内置函数。

  • 去极值与标准化向量化
from jqfactor import winsorize_med, standardlize, neutralize

def calculate_crowding_factor(context):
    # 假设已获取因子值 DataFrame: df_factors (index为日期,columns为股票)
    # 1. 中位数去极值 (向量化处理)
    clean_factors = winsorize_med(g.factor_df, scale=3, axis=1)
    # 2. 标准化
    std_factors = standardlize(clean_factors, axis=1)
    # 3. 行业与市值中性化
    neutral_factors = neutralize(std_factors, how=['jq_l1', 'market_cap'], date=context.previous_date, axis=1)
    return neutral_factors

5. 状态持久化与模拟盘重启优化

在长周期模拟盘中,g 对象的大小不能超过 30M。不要将不可序列化的对象(如数据库连接 query、打开的文件等)存入 g。对于这些对象,应在 process_initialize 中以双下划线开头命名进行初始化:

def process_initialize(context):
    # 每次进程重启时初始化,系统序列化 g 时会自动忽略双下划线开头的变量
    g.__q = query(valuation.code, valuation.market_cap)