Backtrader探讨如何处理除权分红
1. 问题
在Backtrader回测过程中,回测过程中,如果策略是使用价格均线,就会产生误差,特别是除权分红比例比较大的情况下,也会对回测计算的结果造成不小的影响。
为了处理分红和除权造成的不准确,处理方法:
(1)通过前复权和后复权
选择前复权或后复权主要取决于回测的目的和对市场历史数据解读的角度。以下是对两种复权方式的详细阐述:
-
前复权
定义与原理:前复权是以当前股价为基准,将之前的分红、送股等因素进行调整,使得股价走势看起来更加连续和直观。简单来说,就是把除权除息前的价格按照现在的价格进行折算,从而消除由于分红、送股等造成的价格缺口。- 优点:
可以更清晰地观察股票价格的长期走势,有助于投资者判断股票的趋势和价值。
在Backtrader中,前复权是默认的复权方式,使用它可以使回测过程中的交易价格更加准确地反映实际情况。 - 缺点:
如果使用前复权数据进行计算,会存在使用未来函数的问题。这是因为前复权是根据当前的价格和历史分红送股等信息,将历史价格进行调整,使得历史价格与当前价格相匹配。这样的处理方式实际上包含了未来的信息,即在回测时使用了未来发生的分红送股等事件的数据,违反了回测中应有的“在当时时点上,只能使用当时已知信息”的原则。 - 局限性:
可能会扭曲某些短期的价格波动,因为它是基于长期的历史数据进行调整。在比较不同股票的走势时,仅仅依靠前复权可能不够准确,因为不同的股票在分红送股的政策和频率上有很大差异。
- 优点:
-
后复权
定义:后复权则是将除权除息后的价格按照除权除息前的价格进行折算,通常用于保持历史价格数据的连续性,便于投资者分析股票的长期走势。
强调的是历史价格数据的连续性。在某些情况下,后复权能更好地反映股票的真实价值,特别是在分析股票的长期表现时。- 优点:后复权能够更好地模拟真实的交易环境,因为它反映了如果投资者今天买入股票,然后经历分红、送股等情况后的股价变化。避免了未来函数对于回测的真实性的影响。
- 缺点:对于长期的投资分析来说,后复权可能不太适合,因为对于后复权后,价格会比较高,甚至是非常高,对于回测过程中的买入、卖出数量 的计算,对于持仓的管理,都会造成非常大的影响。
-
理解考虑:
如果希望从市场历史数据中获取更多关于股票真实价值的信息,并且是在考虑长期投资效果,回测周期是在一年以上,甚至更长,希望能够反映股票在分红和除权后的实际表现,后复权可能更适合。
如果是三个月左右的中短期投资回测,就直接用市价即可。
前复权,实际上是将未来的除权分红信息倒推到过去,以此为基准回测计算,应该就是使用了未来函数,是不准确的。
未来函数 指在技术分析中,引用或利用当时还没有发生的数据对之前发出的判断进行修正的函数。
明确了使用后复权计算更加符合回测的需求,那么如何处理股价过高的 问题,只能通过数学标准化的处理方法,对价格进行变换。
(2)标准化处理数据
标准化是一种常见的数据预处理方法,通过缩放数据使其具有统一的尺度,从而消除不同变量间的量纲影响。在股票价格数据中,标准化可以使得价格数据在相同的尺度上进行比较和分析,从而减小后复权对交易策略的影响。
找了四个常用的标准化方法,看看是否可用。
-
- Z-Score标准化(零均值和单位方差标准化)
Z-Score标准化是一种常用的方法,它通过减去均值并除以标准差来实现数据的标准化,使得数据的均值为0,标准差为1。这种方法适用于大多数机器学习算法,因为它保持了数据的原始分布,只是改变了数据的尺度。
- Z-Score标准化(零均值和单位方差标准化)
-
- Min-Max标准化(归一化)
Min-Max标准化将数据缩放到一个指定的范围,通常是0到1之间。这种方法通过减去最小值并除以最大值和最小值的差来实现。
- Min-Max标准化(归一化)
-
- 小数定标标准化
小数定标标准化通过移动数据的小数点位置来进行标准化,移动的位数取决于数据绝对值的最大值。
- 小数定标标准化
-
- 均值归一化法
均值归一化法通过减去均值并除以最大值和最小值的差来实现数据的标准化。
- 均值归一化法
2.测试代码
(1)策略
class MyStrategy(bt.Strategy):
params = (
('short_window', 15),
('long_window', 60),
('stop_loss_pct', 0.05), # 5% stop loss
('take_profit_pct', 0.10), # 10% take profit
('perc_cash',0.8), # 买入现金比例
('printlog',False)
)
def __init__(self):
self.dataclose = self.datas[0].close
self.order = None
self.buyprice = None
self.buycomm = None
self.position_size = 0
# Initialize moving averages
self.sma_short = bt.indicators.SimpleMovingAverage(
self.datas[0], period=self.params.short_window)
self.sma_long = bt.indicators.SimpleMovingAverage(
self.datas[0], period=self.params.long_window)
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
return
if order.status in [order.Completed]:
if order.isbuy():
self.log(f'BUY EXECUTED, Price: {order.executed.price}, Size: {order.executed.size}, Cost: {order.executed.value}, Comm: {order.executed.comm},Position:{self.position.size}')
self.buyprice = order.executed.price
self.buycomm = order.executed.comm
elif order.issell(): # Sell
self.log(f'SELL EXECUTED, Price: {order.executed.price}, Size: {order.executed.size},Cost: {order.executed.value}, Comm: {order.executed.comm},Position:{self.position.size}')
self.bar_executed = len(self)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Order Canceled/Margin/Rejected