问题描述
解决方案
在PTrade量化交易平台中,处理股票分红、送股(除权除息)导致的数据异常,最核心的方法是使用复权数据(Rehabilitation)。
如果不使用复权数据,当股票发生分红或拆股时,股价会自然回落(产生缺口),导致技术指标(如均线、MACD)出现断崖式下跌,从而触发错误的卖出信号或止损逻辑。
以下是详细的解决方案和代码实现。
核心解决方案:使用前复权 (fq='pre')
在 PTrade API 中,获取历史行情的函数 get_history 和 get_price 都有一个参数 fq。
fq='pre'(前复权):这是策略中最常用的模式。它以当前价格为基准,向下调整历史价格。这样可以保持K线的连续性,消除除权除息造成的“假跌”缺口,保证技术指标计算的准确性。fq=None(不复权):默认值。返回交易所的原始价格。如果策略逻辑依赖真实成交价(如某些套利策略),才使用此项。fq='post'(后复权):以股票上市价为基准,向上调整当前价格。通常用于计算长期的资产回报率,但在实盘交易信号生成中使用较少。
代码实现示例
下面的策略展示了如何正确获取前复权数据来计算均线,从而避免分红送股导致的错误信号。
def initialize(context):
"""
初始化函数
"""
# 设置我们要操作的股票,例如:贵州茅台
g.security = '600519.SS'
set_universe(g.security)
# 设置均线周期
g.short_window = 5
g.long_window = 20
def handle_data(context, data):
"""
盘中运行函数
"""
security = g.security
# ------------------------------------------------------------------
# 关键点:获取历史数据时,务必设置 fq='pre' (前复权)
# 如果不设置或设置为None,分红送股当天历史数据与当前价格会产生巨大缺口
# ------------------------------------------------------------------
# 获取过去20天的收盘价,使用前复权
hist_data = get_history(g.long_window, '1d', 'close', security, fq='pre')
# 确保数据长度足够
if len(hist_data) < g.long_window:
return
# 提取收盘价数组
# 注意:get_history返回的数据结构可能因版本不同略有差异,通常取values
close_prices = hist_data['close'].values
# 计算均线
# 使用前复权数据计算出的均线是平滑的,不会受除权影响
ma_short = close_prices[-g.short_window:].mean()
ma_long = close_prices[-g.long_window:].mean()
# 获取当前持仓
position = get_position(security).amount
# 获取当前可用资金
cash = context.portfolio.cash
# 获取当前价格 (data中的价格通常是实时的不复权价格,但在比较逻辑中,
# 只要历史均线是复权的,趋势判断就是正确的)
current_price = data[security]['close']
# 简单的双均线策略逻辑
# 金叉:短均线上穿长均线 -> 买入
if ma_short > ma_long and position == 0:
log.info("金叉出现,执行买入。MA5: %.2f, MA20: %.2f" % (ma_short, ma_long))
order_value(security, cash)
# 死叉:短均线下穿长均线 -> 卖出
elif ma_short < ma_long and position > 0:
log.info("死叉出现,执行卖出。MA5: %.2f, MA20: %.2f" % (ma_short, ma_long))
order_target(security, 0)
def after_trading_end(context, data):
"""
盘后处理(可选)
"""
# 如果需要查询具体的除权除息信息,可以使用 get_stock_exrights
# 这通常用于统计或特殊策略,不是常规趋势策略必须的
ex_rights_info = get_stock_exrights(g.security, date=context.blotter.current_dt)
if ex_rights_info is not None:
log.info("今日 %s 存在除权除息信息: %s" % (g.security, ex_rights_info))
进阶:如何查询具体的除权除息信息?
如果你的策略是专门针对分红(例如“抢权”策略或“填权”策略),你需要知道具体的送股比例和分红金额。可以使用 get_stock_exrights 接口。
# 示例:检查某只股票在回测当日是否发生除权除息
def check_corporate_action(context, security):
# 获取当日的除权除息信息
# date参数传入当前回测日期
info = get_stock_exrights(security, date=context.blotter.current_dt)
if info is not None and not info.empty:
# info 是一个 DataFrame
# bonus_ps: 每股分红
# allotted_ps: 每股送股
# rationed_ps: 每股配股
log.info("股票 %s 今日发生除权除息" % security)
log.info(info)
return True
return False
总结
-
通用策略(趋势、均线、突破等):
- 必须在
get_history或get_price中显式指定fq='pre'。 - 这是解决“分红送股导致策略出错”的最标准方法。
- 必须在
-
持仓处理:
- 在PTrade的回测引擎中,当发生分红送股时,系统会自动调整你的持仓数量(增加股数)和账户资金(增加现金分红)。你不需要手动写代码去调整
context.portfolio.positions,引擎会处理交割。
- 在PTrade的回测引擎中,当发生分红送股时,系统会自动调整你的持仓数量(增加股数)和账户资金(增加现金分红)。你不需要手动写代码去调整
-
避免未来函数:
- 前复权数据在回测时会修改“过去”的价格以适应“现在”的价格。在实盘中,前复权是动态调整的。只要策略逻辑是基于相对趋势(如均线交叉、RSI超买超卖),使用前复权是安全的且正确的。
Q&A: 常见问题解答
Q: 为什么我的策略在分红那天突然全仓卖出了?
A: 很可能是因为你使用了默认的 fq=None(不复权)。分红或拆股会导致股价在除权日大幅“低开”。例如10送10,股价会减半。如果你的策略有“跌幅超过5%止损”的逻辑,使用不复权数据就会误触发止损。使用 fq='pre' 可解决此问题。
Q: 实盘交易中,get_history 获取的数据也是复权的吗?
A: 是的,只要设置了 fq='pre',实盘中获取的历史K线也是经过前复权处理的,能够与实时行情平滑衔接。
Q: 我可以直接修改 data[security]['close'] 的值吗?
A: 不可以。handle_data 中的 data 对象包含的是当下的快照数据(Snapshot),是交易所推送的真实价格。你无法改变它,也不应该改变它。你应该调整的是你用来计算指标的历史数据(即通过 get_history 获取的数据)。
Q: get_stock_exrights 返回 None 是什么意思?
A: 表示该股票在查询的日期没有发生除权除息行为。这是正常现象,绝大多数交易日都会返回 None。