🚀 AI 一键生成 qmt 策略代码

如何在 QMT 中计算策略的 Alpha 和 Beta 值?

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

问题描述

请教我如何计算一个投资策略的 Alpha 和 Beta 系数

解决方案

在量化交易中,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("评价: 策略未跑赢市场风险调整后的收益")

代码实现详解

  1. 数据收集 (handlebar)

    • QMT 的 ContextInfo.get_net_value(index) 可以在回测时获取指定 bar 的策略净值。
    • 我们在 handlebar 中逐日记录日期和净值,存入 ContextInfo.strategy_records 列表。
  2. 数据对齐 (calculate_metrics)

    • 这是最关键的一步。策略的交易日和基准指数的交易日必须严格对齐。
    • 我们使用 pandas.concat(..., axis=1, join='inner') 来合并策略净值和基准收盘价,join='inner' 会自动丢弃那些只有一方有数据的日期(例如停牌或数据缺失的日子),保证计算的准确性。
  3. 计算收益率

    • 使用 pct_change() 计算每日的涨跌幅。
    • 注意:Alpha/Beta 是基于收益率计算的,而不是基于价格或净值绝对值计算的。
  4. 线性回归

    • 我们使用 np.polyfit(x, y, 1) 进行一次多项式拟合(即线性回归)。
    • X轴:市场超额收益 ($R_m - R_f$)。
    • Y轴:策略超额收益 ($R_p - R_f$)。
    • 斜率:即为 Beta
    • 截距:即为 日度 Alpha
  5. 年化处理

    • 回归得到的 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 获取资金账号的总资产来计算每日收益率,计算逻辑是一样的。