问题描述
解决方案
在PTrade量化交易平台中,进行盘后数据统计和复盘分析主要依赖于 after_trading_end 函数。
该函数是一个可选的生命周期函数,会在每个交易日的 15:30(盘后)自动触发。这是汇总当日盈亏、检查持仓变化、统计成交记录以及打印复盘日志的最佳时机。
以下是一个完整的策略示例,展示了如何在盘后统计当日的资产状况、持仓详情以及当日成交记录。
策略代码示例
def initialize(context):
"""
初始化函数
"""
# 设置股票池,这里以恒生电子为例
g.security = '600570.SS'
set_universe(g.security)
# 用于记录昨日净值,以便计算当日盈亏
g.last_total_value = context.portfolio.starting_cash
def before_trading_start(context, data):
"""
盘前处理:记录开盘前的总资产,用于盘后计算当日盈亏
"""
g.last_total_value = context.portfolio.portfolio_value
def handle_data(context, data):
"""
盘中逻辑:这里简单模拟一个买入操作用于产生数据
"""
# 如果没有持仓,买入100股
if get_position(g.security).amount == 0:
order(g.security, 100)
log.info("盘中触发买入: %s" % g.security)
def after_trading_end(context, data):
"""
盘后处理:数据统计与复盘分析
运行时间:交易日 15:30
"""
log.info("=" * 30)
log.info("【开始盘后复盘统计】日期: %s" % context.blotter.current_dt.strftime("%Y-%m-%d"))
# 1. 账户资金统计
portfolio = context.portfolio
total_value = portfolio.portfolio_value # 总资产
cash = portfolio.cash # 可用资金
daily_pnl = total_value - g.last_total_value # 当日盈亏
daily_return = (daily_pnl / g.last_total_value) * 100 if g.last_total_value > 0 else 0 # 当日收益率
log.info("[资金概况]")
log.info("总资产: %.2f" % total_value)
log.info("可用现金: %.2f" % cash)
log.info("当日盈亏: %.2f" % daily_pnl)
log.info("当日收益率: %.2f%%" % daily_return)
log.info("累计收益率: %.2f%%" % (portfolio.returns * 100))
# 2. 持仓详情统计
positions = portfolio.positions
log.info("-" * 20)
log.info("[持仓详情]")
if len(positions) > 0:
for stock_code, pos in positions.items():
# 过滤掉已清仓(数量为0)的标的
if pos.amount > 0:
# 获取当前收盘价(注意:after_trading_end时data为空,需用get_price或pos.last_sale_price)
current_price = pos.last_sale_price
market_value = pos.amount * current_price
float_pnl = pos.amount * (current_price - pos.cost_basis) # 简单浮动盈亏计算
log.info("标的: %s | 持仓: %d | 成本: %.2f | 现价: %.2f | 市值: %.2f | 浮盈: %.2f" % (
stock_code,
pos.amount,
pos.cost_basis,
current_price,
market_value,
float_pnl
))
else:
log.info("当前无持仓")
# 3. 当日成交记录复盘
log.info("-" * 20)
log.info("[今日成交记录]")
# 获取当日所有成交
trades = get_trades()
if trades:
# get_trades 返回的是字典,key是订单ID,value是成交列表
for order_id, trade_list in trades.items():
for trade in trade_list:
# trade结构: [成交编号, 委托编号, 标的代码, 买卖类型, 成交数量, 成交价格, 成交金额, 成交时间]
# 注意:不同版本PTrade返回结构可能微调,建议参考文档或打印查看
stock = trade[2]
action = trade[3]
amount = trade[4]
price = trade[5]
time_str = trade[7]
log.info("时间: %s | 标的: %s | 操作: %s | 数量: %s | 均价: %.2f" % (
time_str, stock, action, amount, price
))
else:
log.info("今日无成交")
log.info("【盘后复盘统计结束】")
log.info("=" * 30)
代码解析
-
after_trading_end(context, data):- 这是核心函数,专门用于盘后处理。
- 注意:在此函数中,参数
data通常为空或不包含实时行情数据,因此获取价格应依赖context.portfolio.positions中的last_sale_price或者调用get_price获取收盘价。
-
资金统计 (
context.portfolio):- 通过
context.portfolio对象获取当前的总资产 (portfolio_value) 和现金 (cash)。 - 为了计算当日盈亏,我们在
before_trading_start中记录了开盘前的净值g.last_total_value,然后在盘后进行减法计算。
- 通过
-
持仓遍历 (
context.portfolio.positions):context.portfolio.positions是一个字典,键是股票代码,值是Position对象。- 通过遍历这个字典,可以获取每只股票的持仓量 (
amount)、持仓成本 (cost_basis) 和最新价格 (last_sale_price)。
-
成交记录 (
get_trades()):get_trades()函数返回当日的成交回报。- 这对于复盘非常重要,可以用来核对策略在当天的交易逻辑是否符合预期。
常见问题 (Q&A)
Q: after_trading_end 函数在回测和实盘中的运行时间一致吗?
A: 在回测模式下,该函数通常在模拟时间的 15:30 运行。在实盘交易模式下,运行时间由券商配置决定,通常也是在收盘后的 15:30 左右执行。
Q: 我可以在 after_trading_end 中下单交易吗?
A: 不可以。该函数运行在盘后(15:30),此时交易所已闭市,普通的 order 系列函数无法撮合成交。如果需要进行盘后固定价格交易(科创板/创业板等),需使用专门的 after_trading_order 接口,且通常需要在交易时段内或特定的盘后时段调用。
Q: 如何将统计的数据导出到本地文件?
A: PTrade 不支持 import os 或随意读写本地文件系统。在回测中,可以使用 log.info 输出并在日志中查看。如果是在研究环境中,可以使用 Python 的标准文件操作写入到 get_research_path() 获取的目录下,例如:
path = get_research_path() + "daily_report.txt"
with open(path, 'a') as f:
f.write("Date: %s, Value: %s\n" % (date, value))
Q: 为什么盘后获取的 data 参数里没有数据?
A: data 参数主要用于 handle_data 中传输实时的 tick 或 bar 数据。在 after_trading_end 阶段,行情推送已经结束,因此该参数通常不包含行情数据。如需获取收盘价,请使用 get_history 或查看持仓对象中的价格属性。