🚀 AI 一键生成 ptrade 策略代码

PTrade 策略进阶:在策略运行时动态计算夏普比率和最大回撤

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

问题描述

如何在我的策略运行时,每天动态计算并记录当前的夏普比率和最大回撤?

解决方案

在PTrade中,虽然回测结束后系统会自动生成绩效报告,但在策略运行过程中(尤其是实盘或模拟盘),你可能希望每天收盘后实时了解当前的风险指标。

要实现这一功能,我们需要在策略代码中手动维护一个“历史净值列表”或“历史收益率列表”,并在每天盘后(after_trading_end)进行计算。

核心逻辑说明

  1. 最大回撤 (Max Drawdown)

    • 需要维护一个变量 g.max_portfolio_value 记录历史最高总资产。
    • 每日收盘计算:1 - (当前总资产 / 历史最高总资产)
  2. 夏普比率 (Sharpe Ratio)

    • 需要维护一个列表 g.daily_returns 记录每天的收益率。
    • 公式:(平均日收益率 - 日无风险利率) / 日收益率标准差 * sqrt(252)
    • 注:为了简化计算,通常将无风险利率设为0或固定值。

完整策略代码示例

以下代码展示了如何在一个简单的策略中嵌入这两个指标的计算。你可以将 initialize 中的变量初始化和 after_trading_end 中的计算逻辑复制到你自己的策略中。

import numpy as np

def initialize(context):
    # 1. 设置示例股票池(仅作演示,可替换为你自己的逻辑)
    g.security = '600570.SS'
    set_universe(g.security)
    
    # 2. 初始化统计所需的全局变量
    # 记录每日收益率的列表
    g.daily_returns = []
    # 记录上一个交易日的总资产,初始化为起始资金
    g.last_portfolio_value = context.portfolio.portfolio_value
    # 记录历史最高总资产,初始化为起始资金
    g.max_portfolio_value = context.portfolio.portfolio_value
    
    # 设定无风险利率 (例如年化 3%),用于夏普比率计算
    g.risk_free_rate_annual = 0.03

def handle_data(context, data):
    # --- 简单的买入持有逻辑用于演示 ---
    # 如果没有持仓,则全仓买入
    if context.portfolio.positions[g.security].amount == 0:
        order_value(g.security, context.portfolio.cash)

def after_trading_end(context, data):
    """
    盘后运行函数,用于计算并记录当天的夏普比率和最大回撤
    """
    # 获取当前账户总资产
    current_value = context.portfolio.portfolio_value
    
    # --- 1. 计算并更新最大回撤 ---
    # 更新历史最高资产
    if current_value > g.max_portfolio_value:
        g.max_portfolio_value = current_value
    
    # 计算当前回撤
    drawdown = 0
    if g.max_portfolio_value > 0:
        drawdown = 1 - (current_value / g.max_portfolio_value)
    
    # --- 2. 计算并更新夏普比率 ---
    # 计算当日收益率
    if g.last_portfolio_value > 0:
        daily_ret = (current_value / g.last_portfolio_value) - 1
        g.daily_returns.append(daily_ret)
    
    # 更新昨日资产,供明天使用
    g.last_portfolio_value = current_value
    
    # 计算夏普比率 (至少需要2天数据才能计算标准差)
    sharpe_ratio = 0.0
    if len(g.daily_returns) > 1:
        # 将年化无风险利率转换为日无风险利率
        rf_daily = g.risk_free_rate_annual / 252.0
        
        # 计算收益率的均值和标准差 (ddof=1 表示样本标准差)
        avg_return = np.mean(g.daily_returns)
        std_return = np.std(g.daily_returns, ddof=1)
        
        # 防止标准差为0导致除零错误
        if std_return > 0:
            # 年化夏普 = (日均收益 - 日无风险利率) / 日标准差 * sqrt(252)
            sharpe_ratio = ((avg_return - rf_daily) / std_return) * np.sqrt(252)
            
    # --- 3. 打印日志 ---
    # 格式化输出,保留小数位
    log.info("【绩效监控】日期: %s, 当前总资产: %.2f" % (
        str(context.blotter.current_dt), current_value
    ))
    log.info("【绩效监控】最大回撤: %.2f%%, 夏普比率: %.4f" % (
        drawdown * 100, sharpe_ratio
    ))

代码详解

  1. 初始化 (initialize):

    • 我们引入了 g.daily_returns 来存储每天的收益率数据流。
    • g.max_portfolio_value 用于“锚定”历史最高点,这是计算回撤的基础。
  2. 盘后计算 (after_trading_end):

    • 最大回撤: 逻辑非常直接,即 (最高点 - 当前点) / 最高点。如果当前点创新高,回撤为0。
    • 夏普比率:
      • 首先计算当天的简单收益率:(今天净值 / 昨天净值) - 1
      • 使用 numpy 计算历史收益率序列的平均值 (mean) 和标准差 (std)。
      • 年化处理: 夏普比率通常是年化的,因此我们将结果乘以 $\sqrt{252}$(假设一年有252个交易日)。
      • 无风险利率: 代码中简单地将年化3%平摊到每天。如果你想计算“信息比率”或不考虑无风险利率,可以将 rf_daily 设为 0。
  3. 兼容性:

    • 代码使用了 Python 3.5 兼容的字符串格式化 % 语法,确保在 PTrade 旧版本环境中也能稳定运行。
    • 使用了 numpy 库,这是 PTrade 环境内置支持的标准科学计算库。

Q&A

Q1: 为什么计算出的夏普比率和PTrade回测报告里的不完全一样?
A: 可能有细微差异。原因包括:1. 无风险利率的取值不同(PTrade系统可能默认设为0或其它值);2. 收益率计算方式(简单收益率 vs 对数收益率);3. 年化因子的处理(有些系统按365天,有些按250或252天)。上述代码使用的是标准的年化252天计算法。

Q2: 这个代码会影响策略运行速度吗?
A: 几乎不会。after_trading_end 每天只运行一次,且 numpy 的计算效率极高。即使运行数年积累了上千个数据点,计算耗时也是毫秒级的。

Q3: 如果我想计算最近30天的滚动夏普比率怎么办?
A: 你可以在计算 np.meannp.std 时,对列表进行切片。例如:

recent_returns = g.daily_returns[-30:] # 取最后30个数据
avg_return = np.mean(recent_returns)
# ...后续计算相同