问题描述
解决方案
在聚宽(JoinQuant)平台中,要计算并记录每天的盈亏金额,核心逻辑是利用 context.portfolio.total_value(当前总资产)减去前一交易日的总资产。
我们可以利用全局变量 g 来存储“昨日总资产”,并在每天收盘后(after_trading_end)进行计算和记录。
以下是具体的实现步骤和完整代码示例。
实现思路
- 初始化:在
initialize函数中,定义一个全局变量g.last_total_value,初始值为启动资金(context.portfolio.starting_cash)。 - 每日计算:在每天收盘后(建议使用
after_trading_end函数),获取当前的context.portfolio.total_value。 - 计算差额:
当日盈亏 = 当前总资产 - 昨日总资产。 - 记录数据:使用
record()函数将盈亏金额绘制在回测图表上,方便直观查看。 - 更新状态:计算完成后,将
g.last_total_value更新为当前总资产,供第二天使用。
完整策略代码
# -*- coding: utf-8 -*-
# 导入函数库
from jqdata import *
def initialize(context):
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
### 股票相关设定 ###
# 股票类每笔交易时的手续费是:买入时佣金万分之三,卖出时佣金万分之三加千分之一印花税, 每笔交易佣金最低扣5块钱
set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
# 定义一个全局变量, 保存要操作的股票
g.security = '000001.XSHE'
# 【关键步骤1】初始化昨日总资产,初始值为启动资金
g.last_total_value = context.portfolio.starting_cash
# 设置交易函数,每天开盘时运行
run_daily(market_open, time='09:30')
# 【关键步骤2】设置收盘后处理函数,用于计算当日盈亏
# after_trading_end 会在每天 15:30 运行
run_daily(after_trading_end, time='after_trading_end')
def market_open(context):
# 一个简单的买入策略示例,用于产生盈亏波动
if g.security not in context.portfolio.positions:
# 如果没有持仓,用一半资金买入
order_value(g.security, context.portfolio.available_cash * 0.5)
else:
# 如果有持仓,且盈利超过5%则卖出(仅为演示产生交易)
cost = context.portfolio.positions[g.security].avg_cost
price = context.portfolio.positions[g.security].price
if price > cost * 1.05:
order_target(g.security, 0)
def after_trading_end(context):
# 【关键步骤3】获取当前总资产(现金 + 持仓市值)
current_total_value = context.portfolio.total_value
# 【关键步骤4】计算当日盈亏金额
# 当日盈亏 = 今日总资产 - 昨日总资产
daily_pnl = current_total_value - g.last_total_value
# 【关键步骤5】使用 record 函数记录,这样回测结束后可以在图表中看到曲线
# key='daily_pnl' 是图例名称,value 是具体的数值
record(daily_pnl=daily_pnl)
# 打印日志,方便在日志区查看具体数值
log.info("日期: %s, 当日盈亏: %.2f, 当前总资产: %.2f" % (context.current_dt.date(), daily_pnl, current_total_value))
# 【关键步骤6】更新昨日总资产,为下一个交易日做准备
g.last_total_value = current_total_value
代码解析
g.last_total_value: 这是一个全局变量。在策略第一天运行前,它等于初始资金。after_trading_end: 这个函数在每天交易结束后(15:30)调用。此时当天的收盘价已确定,计算出的总资产是准确的。record(daily_pnl=daily_pnl): 这个函数非常重要。它会在回测结果页面的收益曲线下方增加一个新的图表区域,专门显示daily_pnl的走势。- 逻辑闭环: 每天计算完盈亏后,必须执行
g.last_total_value = current_total_value,这样明天的计算基准才是今天的收盘资产。
Q&A
Q: 为什么要在 after_trading_end 中计算,而不是 handle_data?
A: handle_data 或 market_open 通常在盘中运行。盘中价格在不断跳动,计算出的盈亏是浮动的。而 after_trading_end 在收盘后运行,此时使用的是当天的收盘价(Close),计算出的盈亏是当天的最终结算盈亏,更加准确且具有统计意义。
Q: 如果我在盘中出入金(inout_cash),这个计算逻辑还准确吗?
A: 如果策略中有使用 inout_cash 进行出入金操作,上述简单逻辑会将出入金视为盈亏。如果需要剔除出入金影响,公式应修改为:
当日盈亏 = 当前总资产 - 昨日总资产 - 当日出入金总额。
可以通过 context.portfolio.inout_cash 获取累计出入金,通过记录昨日累计出入金来计算当日变化量。
Q: record 函数记录的数据在哪里看?
A: 回测结束后,在收益率曲线图的下方,会多出一个坐标轴或图表区域,显示你自定义记录的 daily_pnl 曲线。同时,你也可以通过 get_backtest(id).get_records() 在研究环境中提取这些数据。