问题描述
解决方案
在 QMT (Quant Trader) 平台中计算持仓股票之间的相关性,主要通过以下步骤实现:
- 获取持仓列表:通过交易接口获取当前账户的持仓股票代码。
- 获取历史行情:使用
ContextInfo.get_market_data_ex获取这些股票在指定时间段内的历史收盘价。 - 数据清洗与合并:将数据整理为 Pandas DataFrame 格式,确保日期对齐。
- 计算相关性:使用 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)
代码详解
-
获取持仓 (
get_trade_detail_data):- 使用
get_trade_detail_data(accid, type, 'POSITION')获取账户当前的持仓对象列表。 - 遍历列表提取
m_strInstrumentID(证券代码),并过滤掉持仓量为 0 的记录。
- 使用
-
获取数据 (
get_market_data_ex):- 这是 QMT 推荐的高效数据接口。
count=60: 设定样本数量。相关性计算通常需要一定长度的时间序列(如 30天、60天或 120天)才有统计意义。dividend_type='front': 关键参数。计算相关性时必须使用复权价格(通常是前复权),否则分红除权导致的价格跳变会严重扭曲相关性结果。
-
数据清洗 (Pandas):
- 接口返回的是字典格式
{code: DataFrame},需要将其转换为以日期为索引、股票代码为列的二维 DataFrame。 - 使用
dropna()去除缺失数据,保证所有参与计算的股票在同一天都有数据,实现时间轴对齐。
- 接口返回的是字典格式
-
计算相关性 (
df.corr):- Pandas 内置的
corr()函数默认计算皮尔逊 (Pearson) 相关系数。 - 结果是一个矩阵,数值范围在 -1 到 1 之间。
- 接近 1:强正相关(同涨同跌)。
- 接近 -1:强负相关(此消彼长)。
- 接近 0:不相关。
- Pandas 内置的
常见问题 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 绑定。