🚀 AI 一键生成 qmt 策略代码

如何计算投资组合中股票的相关性矩阵以分散风险?

qmt | 发布时间: 2025/12/21
以下内容由 EasyQuant 生成。

问题描述

怎么计算持仓股票之间的相关性?

解决方案

在 QMT (Quant Trader) 平台中计算持仓股票之间的相关性,主要通过以下步骤实现:

  1. 获取持仓列表:通过交易接口获取当前账户的持仓股票代码。
  2. 获取历史行情:使用 ContextInfo.get_market_data_ex 获取这些股票在指定时间段内的历史收盘价。
  3. 数据清洗与合并:将数据整理为 Pandas DataFrame 格式,确保日期对齐。
  4. 计算相关性:使用 Pandas 的 .corr() 方法计算皮尔逊相关系数矩阵。

以下是完整的策略代码示例,您可以直接在 QMT 的策略编辑器中使用。

策略代码实现

# -*- coding: gbk -*-
import pandas as pd
import numpy as np

def init(ContextInfo):
    # 设置资金账号,请修改为您实际的资金账号
    # 格式如:'6000000248'
    ContextInfo.accid = 'YOUR_ACCOUNT_ID' 
    
    # 设置账号类型:'STOCK'股票, 'FUTURE'期货
    ContextInfo.accountType = 'STOCK'
    
    print("策略初始化完成,请在盘中或回测时运行 handlebar 计算相关性")

def handlebar(ContextInfo):
    # 为了避免每个tick都计算,这里示例仅在最后一根K线计算
    if not ContextInfo.is_last_bar():
        return

    # 1. 获取当前持仓列表
    # 注意:get_trade_detail_data 需要在实盘或有模拟交易环境时生效
    # 如果是纯回测且未使用实盘账户对象,可能需要根据 ContextInfo.get_universe() 或自定义逻辑获取代码
    positions = get_trade_detail_data(ContextInfo.accid, ContextInfo.accountType, 'POSITION')
    
    stock_list = []
    for pos in positions:
        # 过滤掉持仓量为0的标的
        if pos.m_nVolume > 0:
            stock_list.append(pos.m_strInstrumentID)
    
    # 如果没有持仓,直接返回
    if not stock_list:
        print("当前无持仓,无法计算相关性。")
        return

    print(f"当前持仓标的: {stock_list}")

    # 2. 获取历史行情数据
    # 设定计算相关性的窗口长度,例如过去 60 个交易日
    lookback_count = 60
    
    # 使用 get_market_data_ex 获取数据 (推荐接口)
    # dividend_type='front' 使用前复权数据,消除分红配股对价格相关性的干扰
    market_data = ContextInfo.get_market_data_ex(
        fields=['close'],
        stock_code=stock_list,
        period='1d',
        start_time='',
        end_time='',
        count=lookback_count,
        dividend_type='front',
        fill_data=True,
        subscribe=False
    )

    # 3. 数据处理:将字典转换为 DataFrame
    # market_data 的结构是 {code: DataFrame}
    price_dict = {}
    for code, df in market_data.items():
        if not df.empty:
            price_dict[code] = df['close']
    
    if not price_dict:
        print("未获取到行情数据")
        return

    # 合并为一个 DataFrame,索引为日期,列名为股票代码
    df_prices = pd.DataFrame(price_dict)
    
    # 删除包含空值的行(确保只计算所有股票都有数据的日期)
    df_prices.dropna(inplace=True)

    if df_prices.empty:
        print("数据清洗后为空,无法计算相关性")
        return

    # 4. 计算相关性矩阵
    # method='pearson' 是最常用的线性相关系数
    correlation_matrix = df_prices.corr(method='pearson')

    # 5. 输出结果
    print("=" * 30)
    print(f"持仓股票近 {lookback_count} 日收盘价相关性矩阵:")
    print(correlation_matrix)
    print("=" * 30)

    # 示例:找出相关性最高的股票对(不包含自身)
    # 将矩阵对角线(自身相关性)设为 NaN 以便过滤
    np.fill_diagonal(correlation_matrix.values, np.nan)
    
    # 展平并排序
    corr_series = correlation_matrix.unstack()
    corr_series = corr_series.sort_values(ascending=False)
    
    # 打印相关性最高的 3 对
    print("相关性最高的 3 对股票:")
    print(corr_series.head(6)[::2]) # 取前6个并跳过重复的对(A-B 和 B-A)

代码详解

  1. 获取持仓 (get_trade_detail_data):

    • 使用 get_trade_detail_data(accid, type, 'POSITION') 获取账户当前的持仓对象列表。
    • 遍历列表提取 m_strInstrumentID(证券代码),并过滤掉持仓量为 0 的记录。
  2. 获取数据 (get_market_data_ex):

    • 这是 QMT 推荐的高效数据接口。
    • count=60: 设定样本数量。相关性计算通常需要一定长度的时间序列(如 30天、60天或 120天)才有统计意义。
    • dividend_type='front': 关键参数。计算相关性时必须使用复权价格(通常是前复权),否则分红除权导致的价格跳变会严重扭曲相关性结果。
  3. 数据清洗 (Pandas):

    • 接口返回的是字典格式 {code: DataFrame},需要将其转换为以日期为索引、股票代码为列的二维 DataFrame。
    • 使用 dropna() 去除缺失数据,保证所有参与计算的股票在同一天都有数据,实现时间轴对齐。
  4. 计算相关性 (df.corr):

    • Pandas 内置的 corr() 函数默认计算皮尔逊 (Pearson) 相关系数。
    • 结果是一个矩阵,数值范围在 -1 到 1 之间。
      • 接近 1:强正相关(同涨同跌)。
      • 接近 -1:强负相关(此消彼长)。
      • 接近 0:不相关。

常见问题 Q&A

Q: 为什么计算相关性要用复权价格?
A: 股票发生分红或拆股时,股价会发生除权缺口(例如 10 送 10,股价减半)。如果不使用复权价格,这种非市场因素导致的价格剧烈变动会被算法误认为是真实的下跌,从而导致计算出的相关性系数失真。通常建议使用前复权 (front)。

Q: 如何在回测模式下使用此代码?
A: 上述代码中的 get_trade_detail_data 主要用于实盘或模拟交易账户。如果您是在纯回测模式下(不涉及模拟账户对象),可以将获取持仓的部分修改为 stock_list = ContextInfo.get_universe() 或者直接读取回测引擎中的 ContextInfo.holdings(如果您的策略逻辑中有维护该变量)。

Q: 计算出的相关性矩阵如何应用?
A:

  • 风险分散:如果持仓中多只股票相关性极高(>0.8),说明持仓同质化严重,风险未分散。
  • 配对交易:寻找相关性高但近期价差扩大的股票对进行套利。
  • 对冲:寻找与持仓负相关的标的进行风险对冲。

Q: 报错 NameError: name 'get_trade_detail_data' is not defined 怎么办?
A: 请确保代码运行在 QMT 的完整 Python 环境中。如果在某些极简模式或非标准环境中,可能需要检查 API 引用。通常在标准的 QMT 策略编辑器中,该函数是全局可用的。如果是在 init 中调用,请确保账户已通过 set_account 绑定。