问题描述
解决方案
在聚宽(JoinQuant)的回测环境中,对于股票的分红(派息)和送股(转增),处理方式取决于您是否开启了动态复权(真实价格)模式。
以下是两种模式下的具体处理逻辑及税务说明:
1. 真实价格模式(强烈推荐)
这是最接近真实交易环境的模式,能够避免“未来函数”并准确模拟资金变动。
- 开启方式:
在initialize函数中调用set_option('use_real_price', True)。 - 处理逻辑:
- 价格表现:回测中使用的是当时真实的交易价格(即未复权价格)。当发生分红或送股时,股价会在除权除息日自然下跌。
- 账户变动:
- 分红(现金):系统会在分红到账日(通常为除权除息日)将税后现金自动打入账户的
available_cash(可用资金)中。 - 送股/转增:系统会自动增加持仓的股票数量(
total_amount)。如果调整后的数量是小数,则向下取整到整数。
- 分红(现金):系统会在分红到账日(通常为除权除息日)将税后现金自动打入账户的
- 日志提示:系统会在日志中打印出分红或送股的信息,提示账户中的现金或持仓发生了变化。
2. 传统前复权模式(默认模式,不推荐)
如果不设置 use_real_price,系统默认使用此模式。
- 处理逻辑:
- 价格表现:回测全程使用基于回测结束日(或当前最新日期)的前复权价格。这意味着历史上的价格已经被“平滑”处理过,除权缺口被填补。
- 账户变动:由于价格已经是复权后的,系统不会在除权日给账户发放现金或增加股数。
- 缺点:这种模式存在严重的“未来函数”。例如,某股票10年前价格是20元,经过多次分红送股后,前复权价格可能变成了2元。回测时,系统会认为当时就是2元,导致你用同样的资金买入了比实际多得多的股票,从而导致回测收益虚高且不准确。
3. 股息红利税的计算
在回测和模拟交易中,红利税的扣除规则如下:
- 现实规则:A股根据持股期限实行差异化征税(持股1个月以内税率20%,1个月至1年税率10%,超过1年免税)。
- 回测规则:由于回测引擎难以精确追踪每一笔具体持仓的对应卖出时间来计算持股时长,聚宽在回测及模拟交易中,统一按照 20% 的税率计算并扣除红利税。
4. 代码示例
为了保证回测的准确性,建议在策略中始终开启真实价格模式:
# -*- coding: utf-8 -*-
def initialize(context):
# 开启真实价格(动态复权)模式,这是处理分红送股最准确的方式
set_option('use_real_price', True)
# 设置基准
set_benchmark('000300.XSHG')
# 选取一只股票作为示例
g.security = '000001.XSHE'
def handle_data(context, data):
# 简单的买入逻辑
if g.security not in context.portfolio.positions:
order(g.security, 1000)
# 打印当前的持仓数量和现金,观察分红送股日的变化
pos = context.portfolio.positions[g.security]
log.info("当前持仓: %s, 数量: %d, 可用资金: %.2f" % (g.security, pos.total_amount, context.portfolio.available_cash))
总结
- 未开启真实价格:分红送股体现在价格的平滑上,账户不发生现金和股数变动(数据有偏差)。
- 开启真实价格:股价除权下跌,账户自动获得现金分红(扣20%税)和送转的股数(数据准确)。
Q&A
Q: 为什么开启真实价格模式后,我获取的历史价格(history API)和交易软件看到的不一样?
A: 开启 use_real_price 后,history、attribute_history 等API获取的仍然是基于当前回测日期的前复权价格,这是为了方便计算技术指标(如均线)。但是,order 下单撮合时使用的是真实价格。如果你需要获取历史上的真实价格(不复权),需要在调用数据API时指定 fq=None。
Q: 分红的钱是什么时候到账的?
A: 在聚宽的回测中,分红现金通常在除权除息日(Ex-date)的开盘前或收盘结算时处理并计入可用资金(视具体回测引擎版本微调,通常T日可用)。
Q: 模拟交易中分红税也是扣20%吗?
A: 是的。由于模拟交易系统无法预知用户何时卖出股票,无法确定持股期限,因此为了保守计算,统一按照20%扣除红利税。实际实盘交易中券商会根据你的持股时长自动计算补扣。