问题描述
解决方案
在量化交易中,Alpha ($\alpha$) 和 Beta ($\beta$) 是衡量策略表现和风险的两个核心指标,它们通常基于 资本资产定价模型 (CAPM) 进行计算。
核心概念
- Beta ($\beta$):衡量策略相对于市场基准(如沪深300)的系统性风险。
- $\beta = 1$:策略波动与市场一致。
- $\beta > 1$:策略波动大于市场(进攻型)。
- $\beta < 1$:策略波动小于市场(防御型)。
- Alpha ($\alpha$):衡量策略相对于市场基准的超额收益(即剔除市场波动后的主动收益)。
- $\alpha > 0$:策略跑赢了市场风险调整后的预期收益。
计算公式
根据 CAPM 模型:
$$E(R_p) - R_f = \alpha + \beta \times (E(R_m) - R_f) + \epsilon$$
其中:
- $R_p$:策略的日收益率。
- $R_m$:基准(市场)的日收益率。
- $R_f$:无风险利率(通常取国债收益率或固定值,如年化 3%)。
QMT 策略代码实现
以下是一个完整的 Python 策略示例。该代码会在回测结束时(最后一根 K 线),自动提取策略的历史净值和基准行情,计算并打印 Alpha 和 Beta 系数。
注意:此代码主要用于回测模式,因为 ContextInfo.get_net_value() 在回测中才会有完整的历史净值记录。
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
from datetime import datetime
def init(ContextInfo):
# 1. 设置基准指数,这里使用沪深300
ContextInfo.benchmark_code = '000300.SH'
# 2. 设置无风险利率 (假设年化 3%)
ContextInfo.risk_free_rate_annual = 0.03
# 3. 用于记录策略每日净值和日期的容器
ContextInfo.strategy_records = []
print("策略初始化完成,基准:{}".format(ContextInfo.benchmark_code))
def handlebar(ContextInfo):
# 获取当前K线的时间戳
timetag = ContextInfo.get_bar_timetag(ContextInfo.barpos)
# 转换为 YYYYMMDD 格式,方便后续与行情数据对齐
current_date = timetag_to_datetime(timetag, '%Y%m%d')
# 获取当前策略净值 (回测模式下有效)
# get_net_value(index) 获取指定索引处的净值
net_value = ContextInfo.get_net_value(ContextInfo.barpos)
# 记录日期和净值
ContextInfo.strategy_records.append({
'date': current_date,
'strategy_net_value': net_value
})
# 仅在回测的最后一根K线执行计算逻辑
if ContextInfo.is_last_bar():
calculate_metrics(ContextInfo)
def calculate_metrics(ContextInfo):
"""
计算 Alpha 和 Beta 的核心逻辑
"""
print("=== 开始计算 Alpha 和 Beta ===")
# 1. 将策略记录转换为 DataFrame
df_strategy = pd.DataFrame(ContextInfo.strategy_records)
if df_strategy.empty:
print("无策略运行数据")
return
# 设置日期为索引,确保唯一性
df_strategy.set_index('date', inplace=True)
# 2. 获取基准指数的历史行情数据
# 时间范围:从策略记录的第一天到最后一天
start_date = df_strategy.index[0]
end_date = df_strategy.index[-1]
# 使用 get_market_data_ex 获取基准收盘价
# 注意:period 需要与策略运行周期一致,这里假设是日线 '1d'
benchmark_data = ContextInfo.get_market_data_ex(
fields=['close'],
stock_code=[ContextInfo.benchmark_code],
period='1d',
start_time=start_date,
end_time=end_date,
count=-1
)
if ContextInfo.benchmark_code not in benchmark_data:
print("未获取到基准数据")
return
df_benchmark = benchmark_data[ContextInfo.benchmark_code]
# 确保基准数据的索引也是字符串格式的日期 YYYYMMDD,以便对齐
# get_market_data_ex 返回的 index 通常是 YYYYMMDDHHMMSS 格式,需要截取前8位
df_benchmark.index = df_benchmark.index.astype(str).str.slice(0, 8)
df_benchmark.rename(columns={'close': 'benchmark_close'}, inplace=True)
# 3. 数据合并与清洗
# 将策略净值和基准收盘价合并,取交集(确保日期对齐)
df_merged = pd.concat([df_strategy, df_benchmark], axis=1, join='inner')
# 4. 计算日收益率
# 策略日收益率
df_merged['ret_strategy'] = df_merged['strategy_net_value'].pct_change()
# 基准日收益率
df_merged['ret_benchmark'] = df_merged['benchmark_close'].pct_change()
# 去除第一行 NaN 数据
df_merged.dropna(inplace=True)
if len(df_merged) < 2:
print("数据样本不足,无法计算")
return
# 5. 准备回归所需数据
# 将年化无风险利率转换为日无风险利率 (按252个交易日计算)
rf_daily = ContextInfo.risk_free_rate_annual / 252
# 计算超额收益 (Excess Returns)
y = df_merged['ret_strategy'] - rf_daily # 策略超额收益
x = df_merged['ret_benchmark'] - rf_daily # 市场超额收益
# 6. 计算 Beta 和 Alpha
# 方法:使用 NumPy 的线性拟合 (y = mx + c)
# polyfit(x, y, 1) 返回 [斜率(Beta), 截距(Daily Alpha)]
beta, alpha_daily = np.polyfit(x, y, 1)
# 将日 Alpha 转换为年化 Alpha
# 简单年化公式:alpha_annual = alpha_daily * 252
# 或者复利公式:(1 + alpha_daily) ^ 252 - 1
alpha_annual = alpha_daily * 252
# 7. 输出结果
print("-" * 30)
print("统计区间: {} 至 {}".format(start_date, end_date))
print("样本数量: {} 天".format(len(df_merged)))
print("基准指数: {}".format(ContextInfo.benchmark_code))
print("-" * 30)
print("Beta 系数: {:.4f}".format(beta))
print("Alpha系数 (年化): {:.2%}".format(alpha_annual))
print("Alpha系数 (日度): {:.6f}".format(alpha_daily))
print("-" * 30)
# 解释
if beta > 1:
print("评价: 策略波动大于市场 (进攻型)")
else:
print("评价: 策略波动小于市场 (防御型)")
if alpha_annual > 0:
print("评价: 策略获得了正向超额收益")
else:
print("评价: 策略未跑赢市场风险调整后的收益")
代码实现详解
-
数据收集 (
handlebar):- QMT 的
ContextInfo.get_net_value(index)可以在回测时获取指定 bar 的策略净值。 - 我们在
handlebar中逐日记录日期和净值,存入ContextInfo.strategy_records列表。
- QMT 的
-
数据对齐 (
calculate_metrics):- 这是最关键的一步。策略的交易日和基准指数的交易日必须严格对齐。
- 我们使用
pandas.concat(..., axis=1, join='inner')来合并策略净值和基准收盘价,join='inner'会自动丢弃那些只有一方有数据的日期(例如停牌或数据缺失的日子),保证计算的准确性。
-
计算收益率:
- 使用
pct_change()计算每日的涨跌幅。 - 注意:Alpha/Beta 是基于收益率计算的,而不是基于价格或净值绝对值计算的。
- 使用
-
线性回归:
- 我们使用
np.polyfit(x, y, 1)进行一次多项式拟合(即线性回归)。 - X轴:市场超额收益 ($R_m - R_f$)。
- Y轴:策略超额收益 ($R_p - R_f$)。
- 斜率:即为 Beta。
- 截距:即为 日度 Alpha。
- 我们使用
-
年化处理:
- 回归得到的 Alpha 是日频的,通常我们需要将其乘以 252(一年的大致交易日数)来得到年化 Alpha,这样更符合业界的交流习惯。
常见问题 Q&A
Q: 为什么我的 Alpha 是负数?
A: 这意味着在承担了相应的市场风险(Beta)后,策略的实际收益低于理论上的预期收益。可能是因为交易成本(手续费、滑点)过高,或者选股能力未能跑赢基准。
Q: 无风险利率 ($R_f$) 应该设为多少?
A: 在国内市场,通常可以使用 10 年期国债收益率或一年期定期存款利率。为了简化计算,代码中默认设为了 3% (0.03)。如果设为 0,计算出的 Alpha 会略有不同,但在短期回测中影响不大。
Q: 这段代码可以在实盘中使用吗?
A: ContextInfo.get_net_value() 主要用于回测。在实盘模式下,你需要自己维护一个账户净值的历史列表(例如每天收盘后记录一次),或者通过 get_trade_detail_data 获取资金账号的总资产来计算每日收益率,计算逻辑是一样的。