问题描述
解决方案
这是一个基于 PTrade 平台的策略示例。该策略会在 after_trading_end 函数(盘后处理)中计算并打印当前的累计收益率和历史最大回撤。
为了演示效果,我在 handle_data 中添加了一个简单的买入逻辑(第一天全仓买入),以便资金曲线发生波动,从而能计算出有意义的收益率和回撤数据。
策略代码
def initialize(context):
"""
初始化函数
"""
# 设置股票池,这里以恒生电子为例
g.security = '600570.SS'
set_universe(g.security)
# 初始化用于计算最大回撤的全局变量
# 记录历史最高权益,初始设为0
g.max_portfolio_value = 0
# 记录历史最大回撤,初始设为0
g.max_drawdown = 0
# 设置一个标志位,用于演示买入操作
g.bought = False
def handle_data(context, data):
"""
盘中运行函数
"""
# 简单的演示逻辑:如果没买过,就全仓买入
if not g.bought:
# 获取当前现金
cash = context.portfolio.cash
# 全仓买入
order_value(g.security, cash)
log.info("买入 %s" % g.security)
g.bought = True
def after_trading_end(context, data):
"""
盘后运行函数,每日收盘后执行一次
"""
# 1. 获取当前账户总资产(现金+持仓市值)
current_value = context.portfolio.portfolio_value
# 2. 获取初始资金
initial_capital = context.portfolio.starting_cash
# 3. 计算累计收益率
# 公式:(当前净值 - 初始资金) / 初始资金
cumulative_return = (current_value - initial_capital) / initial_capital
# 4. 计算最大回撤
# 更新历史最高权益
if current_value > g.max_portfolio_value:
g.max_portfolio_value = current_value
# 计算当前的回撤幅度
# 公式:1 - (当前净值 / 历史最高净值)
# 注意防止除以0的情况(虽然初始化后通常不会为0)
if g.max_portfolio_value > 0:
current_drawdown = 1 - (current_value / g.max_portfolio_value)
else:
current_drawdown = 0
# 更新历史最大回撤
if current_drawdown > g.max_drawdown:
g.max_drawdown = current_drawdown
# 5. 打印结果
# 获取当前日期
current_date = context.blotter.current_dt.strftime("%Y-%m-%d")
log.info("=" * 30)
log.info("【每日结算】日期: %s" % current_date)
log.info("当前总资产: %.2f" % current_value)
log.info("累计收益率: %.2f%%" % (cumulative_return * 100))
log.info("当前回撤: %.2f%%" % (current_drawdown * 100))
log.info("历史最大回撤: %.2f%%" % (g.max_drawdown * 100))
log.info("=" * 30)
代码逻辑说明
-
initialize:- 初始化了两个全局变量
g.max_portfolio_value和g.max_drawdown,分别用于记录账户历史上的最高净值和出现过的最大回撤幅度。
- 初始化了两个全局变量
-
handle_data:- 为了让账户资金发生变动(从而产生收益和回撤),这里写了一个最简单的逻辑:在策略开始运行时全仓买入一只股票并持有。
-
after_trading_end:- 这是 PTrade 专门用于盘后处理的函数,会在每个交易日的 15:30 左右运行。
- 收益率计算:使用
context.portfolio.portfolio_value(当前总资产)和context.portfolio.starting_cash(初始资金)进行计算。 - 最大回撤计算:
- 首先判断当前净值是否创出新高,如果是,则更新
g.max_portfolio_value。 - 然后计算当前回撤:
(历史最高净值 - 当前净值) / 历史最高净值。 - 最后将当前回撤与历史记录的
g.max_drawdown比较,取较大值作为新的历史最大回撤。
- 首先判断当前净值是否创出新高,如果是,则更新
- 日志输出:使用
log.info将计算结果打印到日志面板中,方便回测或实盘监控查看。
Q&A
Q: 为什么要在 after_trading_end 中计算而不是 handle_data?
A: handle_data 在盘中随行情变动频繁触发(分钟级或Tick级),如果在那里打印日志会产生大量冗余信息。after_trading_end 每天收盘后只执行一次,非常适合做每日的盈亏统计和结算报告。
Q: context.portfolio.returns 可以直接用吗?
A: 可以。context.portfolio.returns 是 PTrade 提供的累计收益率属性。上述代码中我手动计算了 (current_value - initial_capital) / initial_capital,其结果与 context.portfolio.returns 是一致的,手动计算有助于理解原理。
Q: 如何查看打印的日志?
A: 在 PTrade 客户端的回测界面下方,或者实盘界面的“日志”选项卡中,可以看到 log.info 输出的内容。