内置指标O-Z
经过前面的努力,我们已经实践了大半backtrader的内置指标,现在只剩下不多的几个了。
首先是OLS系列,在Backtrader中,OLS(普通最小二乘法)通常不作为一个内置指标存在,而是作为一个统计学方法用于回归分析。
OLS_Slope_InterceptN
类:
- 这个类继承自
PeriodN
,用于计算两个数据序列之间的线性回归(斜率和截距)。
OLS_TransformationN
类:
- 这个类也继承自
PeriodN
,用于计算数据的标准化分数(z-score)。
OLS_BetaN
类:
- 这个类继承自
PeriodN
,用于计算两个数据序列之间的回归系数(beta)。
CointN
类:
- 这个类继承自
PeriodN
,用于计算两个数据序列之间的协整检验分数(coint_t)和p值。
这个现在咱也看不懂,就继续往下看吧
'OLS_BetaN', # 最小二乘法分析
'OLS_Slope_InterceptN', 'OLS_TransformationN', 'PeriodN',
'OperationN', 'Oscillator', 'OscillatorMixIn',
'PercentChange', 'PercentRank', 'PercentagePriceOscillator', 'PercentagePriceOscillatorShort',
'ParabolicSAR',
'PivotPoint', 'PlusDirectionalIndicator', 'PrettyGoodOscillator', 'PriceOscillator',
'RSI_EMA', 'RSI_SMA', 'RSI_Safe', 'UpDay', 'UpDayBool', #RSI
'RateOfChange', 'RateOfChange100', # ROC
'ReduceN',
'SmoothedMovingAverage',
'SmoothedMovingAverageEnvelope', 'SmoothedMovingAverageOscillator',
'StandardDeviation',
'Stochastic', # KD
'StochasticFast', 'StochasticFull',
'SumN',
'TripleExponentialMovingAverage', #
'TripleExponentialMovingAverageEnvelope', 'TripleExponentialMovingAverageOscillator',
'Trix', # Trix
'TrixSignal',
'WeightedAverage',
'WeightedMovingAverage', 'WeightedMovingAverageEnvelope', 'WeightedMovingAverageOscillator',
'WilliamsAD',
'WilliamsR', # 威廉
'ZeroLagExponentialMovingAverage',
'ZeroLagExponentialMovingAverageEnvelope',
'ZeroLagExponentialMovingAverageOscillator',
'ZeroLagIndicator',
'ZeroLagIndicatorEnvelope',
'ZeroLagIndicatorOscillator',
剔除一些指标(有些是基本操作指标,有的是功能指标,有的是已经讲过的指标中的某个部分,剩下主要需要实践的指标为:
- PSAR
- RSI
- KD
- Trix
- WilliamsR
014_ParabolicSAR
A_抛物线方程与SAR
ParabolicSAR
,它代表的是抛物线止损指标(Parabolic Stop and Reverse),通常缩写为 PSAR。这个指标由 J. Welles Wilder, Jr. 在 1978 年的著作《New Concepts in Technical Trading Systems》中定义,用于相对强弱指数(RSI)的交易系统。PSAR 指标旨在提供入场(和反转)的信号。
"Parabolic" 在中文中的意思是“抛物线的”。这个词汇经常用于数学和物理学中,描述一个特定的几何形状——抛物线,它是一种由二次方程定义的曲线。
在直角坐标系中,抛物线的标准方程通常有两种形式,取决于抛物线的开口方向:
开口向上或向下的抛物线: 标准形式的方程是 y=ax2+bx+cy,其中 a,b,和 c 是常数,且 a≠0。
- 当 a>0时,抛物线开口向上。
- 当 a<0 时,抛物线开口向下。
开口向左或向右的抛物线: 标准形式的方程是 x=ay2+by+c,其中 a,bb和 c 是常数,且 a≠0。
- 当 a>0 时,抛物线开口向右。
- 当 a<0 时,抛物线开口向左。
a 的值决定了抛物线的“宽度”和开口方向,b和 c(或 h和 k)决定了抛物线在坐标系中的位置。如果 a的绝对值较大,抛物线会较窄;如果 a 的绝对值较小,抛物线会较宽。
Parabolic SAR (PSAR) 指标的计算虽然涉及到抛物线的概念,但它并不是直接使用上述提到的标准抛物线方程来计算的。PSAR 指标的计算方法是基于价格变动的速度和方向,以及一个动态调整的加速因子(Acceleration Factor, AF),来确定潜在的停止和反转点。这里的“抛物线”实际上是指指标试图跟随价格变动的路径,这种路径在某种程度上类似于抛物线的形状,但并不是标准的数学抛物线方程。
PSAR 指标的计算逻辑可以概括为以下几个步骤:
初始化:选择一个初始的 SAR 值,通常是周期内的最高价(如果看涨)或最低价(如果看跌)。
加速因子(AF):开始时设置一个较小的 AF 值,比如 0.02,每次当价格达到新的高点或低点,AF 值会按一定比例增加,通常每次增加 0.02,直到达到一个最大值,比如 0.2。
更新 SAR 值:使用以下公式来更新 SAR 值:
- 对于上升趋势:SARnew=SARold+AF×(EP−SARold)
- 对于下降趋势:SARnew=SARold−AF×(SARold−EP) 其中 EP 是极值点
趋势反转:当价格穿越 SAR 点时,表明趋势可能发生反转,此时 SAR 点会更新到最近的高点或低点,并重置 AF 值。
PSAR 指标的这种计算方式,使得指标点(SAR)的移动路径在图表上呈现出一种类似于抛物线的曲线,这是因为 AF 的动态调整使得 SAR 点能够加速或减速跟随价格的变动。这种设计旨在提供一个动态的止损点,帮助交易者在趋势中保持仓位,同时在趋势反转时及时退出。
B_PSAR的公式与应用
在股票软件中,这个指标叫SAR,在TDX中为系统指标无法打开,在DFCF和THS中虽然能打开这个指标,但都是已经封装成函数的,并且DFCF的默认参数为10,2%,20% 而THS是4,2%,20%。至于这个指标公式是怎么得出来的,还需要通过backtrader的源码去理解:
class ParabolicSAR(PeriodN):
lines = ('psar',)
params = (
('period', 2), # when to start showing values
('af', 0.02),
('afmax', 0.20),
)
def prenext(self):
if len(self) == 1: # 第1根K线不计算
self._status = [] # empty status
return # not enough data to do anything
elif len(self) == 2: # 第2根K线调用nextstart
self.nextstart() # kickstart calculation
else: # 其他调用next
self.next() # regular calc
def nextstart(self):
# Calculate the status for previous length
status.sar = (self.data.high[0] + self.data.low[0]) / 2.0
status.af = self.p.af
if self.data.close[0] >= self.data.close[-1]: # uptrend
status.tr = not True # uptrend when reversed
status.ep = self.data.low[-1] # ep from prev trend
else:
status.tr = not False # downtrend when reversed
status.ep = self.data.high[-1] # ep from prev trend
self.next()
def next(self):
hi = self.data.high[0]
lo = self.data.low[0]
tr = status.tr
sar = status.sar
# Check if the sar penetrated the price to switch the trend
if (tr and sar >= lo) or (not tr and sar <= hi):
tr = not tr # reverse the trend
sar = status.ep # new sar is prev SIP (Significant price)
ep = hi if tr else lo # select new SIP / Extreme Price
af = self.p.af # reset acceleration factor
else: # use the precalculated values
ep = status.ep
af = status.af
# Update sar value for today
self.lines.psar[0] = sar
# Update ep and af if needed
if tr: # long trade
if hi > ep:
ep = hi
af = min(af + self.p.af, self.p.afmax)
else: # downtrend
if lo < ep:
ep = lo
af = min(af + self.p.af, self.p.afmax)
sar = sar + af * (ep - sar) # calculate the sar for tomorrow
# make sure sar doesn't go into hi/lows
if tr: # long trade
lo1 = self.data.low[-1]
if sar > lo or sar > lo1:
sar = min(lo, lo1) # sar not above last 2 lows -> lower
else:
hi1 = self.data.high[-1]
if sar < hi or sar < hi1:
sar = max(hi, hi1) # sar not below last 2 highs -> highest
# new status has been calculated, keep it in current length
# will be used when length moves forward
newstatus = self._status[not plenidx]
newstatus.tr = tr
newstatus.sar = sar
newstatus.ep = ep
newstatus.af = af
PSAR计算逻辑的关键在于是否转向,转向就会产生新的ep值,不转向就继续以af为步长进行递进,但不能大过afmax。
在backtrader中调用PSAR也非常的简单,直接在bt.indicators.PSAR即可
class PSAR_test(BaseSt):
params = (
('stra_name','PSAR测试'),
('tradeCnt',1),
('sucessCnt',0),)
def __init__(self):
self.order = None
sar1 = bt.indicators.PSAR(self.data)
在没有上买卖策略时默认只显示指标的图线
C_PSAR策略回测
简单添加买卖点进行回测
class PSAR_test(BaseSt):
params = (
('stra_name','PSAR测试'),
('p1',10),
('tradeCnt',1),
('sucessCnt',0),)
def __init__(self):
self.order = None
sar1 = bt.indicators.PSAR(self.data,period=self.p.p1)
self.crsup = bt.indicators.CrossUp(self.data.close,sar1.psar)
self.crsdn = bt.indicators.CrossDown(self.data.close,sar1.psar)
def next(self):
if self.order: # 检查是否有指令等待执行
return
if not self.position: # 没有持仓 才会进入
if self.crsup :
self.order = self.buy() # 执行买入
else:
if self.crsdn :
self.order = self.sell() # 执行卖出
经过对54支股票进行回测,将最后的策略收益进行正态分布计算如下:
大多样本的情况下,PSAR的正收益情况要明显好于MACD的经典策略。可以看到SAR的策略比较稳健,但很多时候股价是掉下来很多了才会触发卖出信号,所以它的收益效率并不会很高。前面也谈到过这些指标原来用于国际市场,是可以卖空的,因此也可以理解为它的卖点并不是先买了再卖的最佳点,而是卖空的比较好的点。
再回顾一下前面的介绍:
PSAR这个指标由 J. Welles Wilder, Jr. 在 1978 年的著作《New Concepts in Technical Trading Systems》中定义,用于相对强弱指数(RSI)的交易系统。PSAR 指标旨在提供入场(和反转)的信号。
也就是说PSAR原本就是结合在RSI的交易系统中使用的,那么我们后续尝试着能否在RSI中加入PSAR的指标。
015_RSI指标与策略
A_简介与公式
RSI最早被用于期货交易中,后来人们发现用该指标来指导股票市场投资效果也十分不错,并对该指标的特点不断进行归纳和总结。现在,RSI已经成为被投资者应用最广泛的技术指标之一。
RSI指标正是根据供求平衡的原理,通过测量某一个期间内股价上涨总幅度占股价变化总幅度平均值的百分比,来评估多空力量的强弱程度,进而提示具体操作的。
相对强弱指标RSI 是由韦尔斯.怀尔德(Welles Wilder)提出的,是衡量证券自身内在相对强度的指标。 RSI指标是韦尔斯.怀尔德首创的,发表在他的《技术交易系统新思路》一书中。
怀尔德推荐的默认时间跨度是14天,他论证了应用月周期28日的一半是有效的。
RSI指标在backtrader中的默认参数是14,而我们股票软件中最常见的是6,12,24,那么到底参数设置多少合适,后面我们可以进行参数优化。
来看backtrader的源码
class UpDay(Indicator):
'''
Formula:
- upday = max(close - close_prev, 0) #收盘价与昨收的差值(上涨必然>0)
'''
def __init__(self):
self.lines.upday = Max(self.data - self.data(-self.p.period), 0.0)
class DownDay(Indicator):
'''
Formula:
- downday = max(close_prev - close, 0) # 昨收与今收的差值,下跌为正
'''
def __init__(self):
self.lines.downday = Max(self.data(-self.p.period) - self.data, 0.0)
class UpDayBool(Indicator):
'''
Formula:
- upday = close > close_prev '''
class DownDayBool(Indicator):
'''
Formula:
- downday = close_prev > close '''
class RelativeStrengthIndex(Indicator):
'''Formula:
- up = upday(data)
- down = downday(data)
- maup = movingaverage(up, period)
- madown = movingaverage(down, period)
- rs = maup / madown
- rsi = 100 - 100 / (1 + rs)'''
lines = ('rsi',)
params = (
('period', 14), ('movav', MovAv.Smoothed),
('upperband', 70.0), ('lowerband', 30.0),
('safediv', False), ('safehigh', 100.0),
('safelow', 50.0), ('lookback', 1),
)
def __init__(self):
upday = UpDay(self.data, period=self.p.lookback)
downday = DownDay(self.data, period=self.p.lookback)
maup = self.p.movav(upday, period=self.p.period)
madown = self.p.movav(downday, period=self.p.period)
if not self.p.safediv:
rs = maup / madown
else:
highrs = self._rscalc(self.p.safehigh)
lowrs = self._rscalc(self.p.safelow)
rs = DivZeroByZero(maup, madown, highrs, lowrs)
self.lines.rsi = 100.0 - 100.0 / (1.0 + rs)
所以,backtrader中内置的RSI是分别计算上涨的差值及其移动平均线maup和下跌的差值的移动平均线madown,然后用maup/madown,最后再进行换算成百分比的数值。
B_与股票软件公式的差异
在股票软件中一般都有RSI的指标公式,但其计算逻辑看上去与backtrader并不相同
LC:=REF(CLOSE,1);
RSI1:SMA(MAX(CLOSE-LC,0),N1,1)/SMA(ABS(CLOSE-LC),N1,1)*100;
RSI2:SMA(MAX(CLOSE-LC,0),N2,1)/SMA(ABS(CLOSE-LC),N2,1)*100;
RSI3:SMA(MAX(CLOSE-LC,0),N3,1)/SMA(ABS(CLOSE-LC),N3,1)*100;
最大的差异在于这里使用了ABS()来获取daydown,而backtrader中是 max(lc-c,0),根据公式
上涨时C>LC,则上式=100,而下跌时C<LC,分子为0则式子=0。举例说明:
- 如果涨3天跌3天,假设涨跌均为2,则上式
= 6/12 = 50%;
- 如果涨3天跌3天,涨比跌多即涨2跌1,则(2+2+2)/ (2+2+2+1+1+1) = 6/9 = 67%
- 如果涨4天跌2天,跌幅比较大涨2跌5,则(2+2+2+2)/(2+2+2+2+5+5) = 8/18 = 44%
从这些数据说明,股票软件的RSI指标同时体现了上涨的概率以及上涨能力。
backtrader虽然计算逻辑看上去不一样,但其实得到的结果是一样的
- 同样涨3天跌3天,涨跌均为2,则 [(2+2+2)/6] /[(2+2+2)/6] = 1, 100 - 100/(1+rs) = 50
- 涨3跌3,涨多跌少 则 [(2+2+2)/6]/[(1+1+1)/6] = 2 , 100-100/(1+2) = 66.6%
- 涨4跌2,跌幅大 则 [(2+2+2+2)/6]/[(5+5)/6] = 0.8, 100-100/(1+0.8) = 44%
C_经典RSI策略
首先是股票软件的RSI相对强弱专家系统,这里的指标公式是这样的:
N:=6;
LL:=20;
LH:=80;
LC:=REF(CLOSE,1);
WRSI:=SMA(MAX(CLOSE-LC,0),N,1)/SMA(ABS(CLOSE-LC),N,1)*100;
ENTERLONG:CROSS(WRSI,LL);
EXITLONG:CROSS(LH,WRSI);
也就是使用了6日的RSI上穿20买入,下穿80卖出。
在backtrader中,RSI的默认周期是14,上限是70,下限是30,因此典型的策略是14日的RSI上穿30买入,下穿70卖出,我们可以分别制作这两个策略,看一看效果。
仍然是有股票的适应性,某些股票的走势适合RSI专家系统(比如广汇,保险),而有的适合backtrader的默认周期14(比如华新、海信)。
D_非金叉死叉类指标的问题
如果我们仔细去观察每一图的话,这两个策略都会存在很大的问题,如下图所示,单纯的用上穿20或30进行买入,并没有考虑到指标可能会在20附近来回振荡,而股价一路向下,它没有向上去触发卖点就可能一直亏损下去。
这个问题是大多数非金叉/死叉类策略的共性问题,因为上穿和下穿的位置不相等,就可能无法形成闭环,我们回忆一下BOLL线的策略其实也是如此,处于下跌趋势过程中一个小波动很容易触发买点,但几乎无法触发卖点,就会持续亏损。
对于这类的策略,是非常有必要添加限制条件的,比如简单的止损条件,又比如前面PSAR加入到RSI中来的组合条件,后续我们再仔细研究一些组合策略。
这里我们需要有一个概念就是单一的指标策略可能在某一段走势中非常适合,但它可能有很大的bug在里面,我们的多支股票回测使用单一策略大多不是非常完善的,就会有很大的偶然性,它们都不算是成熟的交易系统。
简单的说,就是不要迷信单一的指标策略。(虽然某些单一的指标天然带止损,但毕竟是少数)
016_KDJ指标与策略
A_Stochastic指标简介
不知道有没有同款的,刚学习了一点backtrader就想把KDJ指标用上去,然后一查Reference发现没有名叫“KDJ”或"KD"的指标?
打开股票软件,用CTRL+F打开公式编辑器,选择超买超卖型指标,就能看到“KDJ随机指标”这一项。所以KDJ是随机指标把里面的公式的几个参数公式变成首字母缩写缩写的专用名词,它本身就是“随机指标”,英文即为Stochastic。
在backtrader或ta-lib中,KD指标被称为Stochastic,这是因为这些库的开发者(外国人)更倾向于使用这个指标的英文名称,以保持与国际市场的一致性。
KD指标在某些交易平台或技术分析库中被称为Stochastic(随机指标),主要是因为它起源于期货市场,由乔治·莱恩(George Lane)首创。这个指标最早是以KD指标的形式出现,而KD指标又是在威廉指标(William's %R)的基础上发展起来的。KDJ指标的中文名称是随机指标,它通过比较当前价格与过去一段时期内的波幅所处的相对位置,来体现市场的方向及强弱。
KDJ指标的计算涉及到未成熟随机值(RSV)、K值、D值和J值。RSV是计算的基础,它体现了即时价格相对于市场波幅所处的位置高低。K值是RSV的加权平均,而D值则是K值的加权平均。J值的计算公式为3K - 2D,它衡量的是K值与D值的差值,因此J线比K线和D线更为敏感。
在实际应用中,KDJ指标能够比较迅速、快捷、直观地研判行情,被广泛用于股市的中短期趋势分析。它在设计过程中主要是研究最高价、最低价和收盘价之间的关系,同时也融合了动量观念、强弱指标和移动平均线的一些优点。因此,KDJ指标能够比较真实地反映价格的波动,其提示作用更加明显。
B_KD指标公式
1)RSV的值
RSV(Raw Stochastic Value),即原始随机值,它是根据百分比和移动统计学原理,使用“当前周期N”内的最高价,最低价,收盘价,计算出三者间的比例关系,即
可以通过下图对RSV的值有一个清晰的认识:
2)K,D,J的值
请注意,KDJ的值在backtrader内置的随机指标与在股票软件中的指标公式是不一样的。我们可以通过源码清楚的知道这个:
class _StochasticBase(Indicator):
lines = ('percK', 'percD',)
params = (('period', 14), ('period_dfast', 3), ('movav', MovAv.Simple),
('upperband', 80.0), ('lowerband', 20.0),
('safediv', False), ('safezero', 0.0))
def __init__(self):
highesthigh = Highest(self.data.high, period=self.p.period)
lowestlow = Lowest(self.data.low, period=self.p.period)
knum = self.data.close - lowestlow
kden = highesthigh - lowestlow
self.k = 100.0 * (knum / kden)
self.d = self.p.movav(self.k, period=self.p.period_dfast)
class Stochastic(_StochasticBase):
'''
The regular (or slow version) adds an additional moving average layer and
thus:
- The percD line of the StochasticFast becomes the percK line
- percD becomes a moving average of period_dslow of the original percD
'''
alias = ('StochasticSlow',)
params = (('period_dslow', 3),)
def __init__(self):
super(Stochastic, self).__init__()
self.lines.percK = self.d
self.l.percD = self.p.movav(self.l.percK, period=self.p.period_dslow)
由源码可知,backtrader中内部类_StochasticBase的self.k就是RSV,但它的默认参数是14;这里的self.d是k的3日简单移动平均线。而到了Stochastic类中,把self.d再赋给了percK,然后把d的3日简单移动平均线赋给了percD。
而在股票软件的指标公式中的代码如下:
N=9;
M1=3;
M2=3;
RSV=(CLOSE-LLV(LOW,N))/(HHV(HIGH,N)-LLV(LOW,N))*100;
a=SMA(RSV,M1,1);
b=SMA(a,M2,1);
e=3*a-2*b;
K:a;
D:b;
J:e;
RSV计算是一致的,即(收盘价- 周期N内最低价 )/(周期N内最高价-周期N内最低价);
但K的计算并不是使用的简单移动平均线(MovingAverageSimple),而是平滑移动平均线(SmoothedMovingAverage),包括D的计算也是K的3日平滑移动平均线。
这一点是会造成一些差别的,可以在股票软件中同时绘制两种不同的线做对比。
从上面来看,TKDJ中的K仍是默认(蓝线),与副图2的标准KDJ的K是相同的,K2是使用简单移动平均线做出的线(红线),可以明显看到,平滑移动平均线跟它的名字一样,线条相对更加的平滑,在某些角度看,简单移动平均线的线条跳变较大,几乎和J的形态有些近似了。
3) SMA与EMA
MA与SMA的差异
前面只讨论了简单移动平均线和EMA的差别,现在我们详细的看一下这几个xMA之间的差别。
首先是股票软件中的MA与SMA,它们分别代表backtrader中的sma(simpleMA)和smma(SMoothMA),之前已经提到过,MA的计算是周期内所有数的平均值,因此平均值在开始的n-1个是没有数据的NaN,如MA10就会有9个空格,因此用MA计算出的K2(周期为3)会有2个空格,而根据K2的3日平均线计算的D2就会有2+2=4个空格。
但SMA(平滑移动平均线)就不一样了,它并不是把收盘价的几个数据进行计算,而只是用当前的收盘价和前一天的K值按比例计算得到,所以从第1个数据就有非空数值。
SMA的用法与公式解析
SMA(X,N,M):X的N日移动平均,M为权重,如Y=(X*M+Y'*(N-M))/N
这样看会比较难以搞清关系,我们还是先看一下backtrader的源码:
class SmoothedMovingAverage(MovingAverageBase):
'''
- new_value = (old_value * (period - 1) + new_data) / period
'''
lines = ('smma',)
def __init__(self):
self.lines[0] = ExponentialSmoothing(
self.data,
period=self.p.period,
alpha=1.0 / self.p.period)
在这里,SmoothMA的三个参数分别是收盘价,周期数(3),阿尔法因子=1/3 ,我们看到这里的1.0是写死的,也就是说SmoothMA只需要2个参数即可。再返回股票软件里关于SMA的用法,就可以理解到权重M其实在backtrader里被写死为1了。
于是SMA(C,3,1)就可以理解为当前收盘价占1/3,而昨天的平均值占2/3; 而SMA(C,5,1)就变成了当天收盘价占1/5,昨天的平均值占4/5。这里的周期数并不是把N个数进行计算,而就是一个分母。假设我们需要的是当前(收盘价+昨日平均值)/2,即权重为1:1就相当于各1/2,那么周期就可以设定为2,权重保持默认的1即可。
上面的表格是通过股票软件的指标公式导出的数据,而"计算xx"列是按照MA和SMA的逻辑自己拉公式进行的计算,首先这两列计算值与导出的数据完全一致,然后要注意一下第1个数据,按道理是要判别的如果是第一个数据,则等于收盘价。
指标公式或init中的运用
从上面的EXCEL公式,也能看到明显的差别,MA是直接从收盘价取数据就可以了,非常的简单;而如果需要与自身的上一个值做运算,就会出现自己调用自己的情况,这个我们也在backtrader中尝试过,要自己实现它比较简单的方案是在next()中进行处理,例如:
self.sma1[0] = self.data.close[0]* (1/3) + self.sma1[-1]*(2/3)
但这种方式是没办法放到init中去进行设置的,也就不能直接在股票软件的指标公式中去使用的。之前我们会在这上面耗费大量的时间,想各种野路子的逻辑来达到计算的目的,但是会搞得很复杂,需要借助中间变量等等。但最终搞出来的结果很容易出现问题。
现在把SMA的算法搞明白之后,就可以轻松的应用SMA/EMA/DMA来达到这些要求。
典型例子1 - 求当前收盘价与上一个值的平均值
EXCEL公式中,当前值为收盘价与上一个值的累加(以上表为例,假设在K列,则单元格公式为K5=(K4+B5)/2。
在指标公式中,只需要知道权重为0.5即1/2,就可以得到默认权重因子M=1时的分母为2,即周期数为2,于是得到SMA_value = SMA(C,2,1)即可
在backtrader中,同理使用SMMA来获取,虽然结果看起来只是使用SMMA的indicators,但其中的逻辑运用却是非常巧妙的。
sma11 = bt.indicators.SMMA(self.data,period=2)
典型例子2 - 求当前收盘价与上一值的累加
这个例子在EXCEL中十分的常见,即当前收盘价与自身上一个值相加,单元格公式为 M5=M4+B5。
这个在指标公式中,首先权重为1/2,然后要累加使用SUM()函数,从开始进行累加,参数2为0,公式为:
SMA1:SMA(C,N1,1); // 通过SMA得到平均值
SUMSMA1:SUM(SMA1,0); // 累加
EMA的计算回顾
在第1节里就讨论了与EMA的问题,当时简单的从pandas的ewm和mean()来计算EMA的值,然后自己通过For in的循环来给给新列"calc_ema5"赋值。
由这个计算逻辑得到的calc_ema5的值与pandas内置的函数方法得到相同的结果。
现在我们已经对SMMA有了比较清晰的认识,在此基础上我们可以进一步来学习和实践EMA的内容了。
参考文档
首先,概念上EMA是指数移动平均(Exponential Moving Average),也叫权重移动平均(Weighted Moving Average),是一种给予近期数据更高权重的平均方法。
EMA也是一种用当前收盘价和上一个值做计算得到的,那么EMA与SMA之间有什么差异呢?我们直接看源码:
class SmoothedMovingAverage(MovingAverageBase):
'''
- new_value = (old_value * (period - 1) + new_data) / period
Formula:
- movav = prev * (1.0 - smoothfactor) + newdata * smoothfactor
'''
lines = ('smma',)
def __init__(self):
self.lines[0] = ExponentialSmoothing(
self.data,
period=self.p.period,
alpha=1.0 / self.p.period)
class ExponentialMovingAverage(MovingAverageBase):
'''
Formula:
- movav = prev * (1.0 - smoothfactor) + newdata * smoothfactor
'''
lines = ('ema',)
def __init__(self):
self.lines[0] = es = ExponentialSmoothing(
self.data,
period=self.p.period,
alpha=2.0 / (1.0 + self.p.period))
self.alpha, self.alpha1 = es.alpha, es.alpha1
由源码可知,无论是SMMA还是EMA,在init中均采用的ExponentialSmoothing进行的运算得到的,它们看上去都几乎一模一样,唯一的差别在于平滑因子上,SMMA是直接平滑占比为1/N(例如N=3则为1/3)而EMA的平滑因子为2/(1+N),当N=3时平滑因子为2/4=1/2,很明显1/2比1/3的权重更大。所以EMA可以看做是SMMA中的一种,而这种给予近期数据更高的权重。
由上,如果我们需要得到与上一个值有运算关系的值,可以使用SMMA或者EMA,比如:
- 权重1/2,即上一个值与收盘价相同权重,用SMMA则周期N = 2 (factor = 1/2)来达到要求,而EMA的周期需要设为3 (factor = 2/(1+3) = 1/2)来达到要求
- 权重0.2,用SMMA则周期为5,factor = 1/5 = 0.2; EMA周期为9,factor = 2/(1+9) = 0.2
我们在股票软件中做个简单的测试,如下指标公式下
SMA1:SMA(C,2,1);
EMA1:EMA(C,3);
SMA2:SMA(C,5,1);
EMA2:EMA(C,9);
得到的数据可确认SMA(C,2)的数据与EMA(C,3)数据相同,SMA(C,5)与EMA(C,9)的数据相同。
4) DMA的问题
在连续的了解了SMMA(SmoothedMovingAverage),EMA(ExponentialMovingAverage)之后,我们再来看一下DMA的问题。
首先,这里的DMA是股票软件中的指标公式,它的公式为
DMA(X,A),求X的动态移动平均.
算法:Y=A*X+(1-A)*Y',其中Y'表示上一周期Y值,A必须大于0且小于1.A支持变量.
例如:
DMA(CLOSE,VOL/CAPITAL)表示求以换手率作平滑因子的平均价
由动态可知,这里的D代表的是"Dynamic",所以它既不是backtrader内置指标里的.dma也不是.dema,这个千万不要搞错了!
.dma (DicksonMovingAverage).dema(DoubleExponentialMovingAverage)
这个DMA在backtrader内置指标里其实是 \indicators\basicops.py中的ExponentialSmoothingDynamic(指数平滑移动平均线+动态)。从上面SMMA和EMA的源码我们看到无论SMMA还是EMA,其实调用的都是ExponentialSmoothing(指数平滑移动平均线),它们的平滑因子是固定的,比如说SMMA就是1/N,EMA就是2/(1+N),而DMA是动态指数平滑,也就意味着平滑因子可以是个变量。
如果我们不需要变动的平滑因子,那么DMA(C,0.5)就等同于SMMA(C,2,1)或者EMA(C,3),于是在股票软件中编写代码公式输出
SMA1:SMA(C,2,1);
EMA1:EMA(C,3);
DMA1:DMA(C,0.5);
由上表可知,只要掌握了平滑因子的计算逻辑,那么就可以使用SMMA,EMA,DMA中的任意一个来得到我们需要的数据。而且相对而言,DMA甚至连换算都不需要,是直接给出权重系数的,是最容易理解的。
并且,在DMA中能接收变化的平滑因子,这个从第2节的Kama指标的源码可以看到:
class AdaptiveMovingAverage(MovingAverageBase):
alias = ('KAMA', 'MovingAverageAdaptive',)
lines = ('kama',)
def __init__(self):
direction = self.data - self.data(-self.p.period)
volatility = SumN(abs(self.data - self.data(-1)), period=self.p.period)
er = abs(direction / volatility) # efficiency ratio
fast = 2.0 / (self.p.fast + 1.0) # fast ema smoothing factor
slow = 2.0 / (self.p.slow + 1.0) # slow ema smoothing factor
sc = pow((er * (fast - slow)) + slow, 2) # scalable constant
self.lines[0] = ExponentialSmoothingDynamic(self.data,
period=self.p.period,
alpha=sc)
最后一句,调用的是ExponentialSmoothingDynamic,参数3的alpha平滑因子传递的是sc,也就是前面所计算得到的,是个一直变化的数值。
017_TRIX_三重指数平滑
A_简介与公式
内置指标至此已经实践的差不多了,此时再来看TRIX就感觉比较的轻松。
TRIX(Triple Exponential Average)是一种动量指标,由Jack Hutson在1980年代初期开发。它主要用于过滤价格行为中的无关波动,以便更清晰地识别价格趋势的改变。TRIX是通过三次指数平滑移动平均来计算的,旨在突出价格的重大转折点。
三重指数平滑平均线(TRIX)属于中长线指标。它过滤掉许多不必要的波动来反映股价的长期波动趋势。在使用均线系统的交叉时,有时会出现骗线的情况,有时还会出现频繁交叉的情况,通常还有一个时间上的确认。为了解决这些问题,因而发明了TRIX这个指标把均线的数值再一次地算出平均数,并在此基础上算出第三重的平均数。这样就可以比较有效地避免频繁出现交叉信号。
TRIX指标又叫三重指数平滑移动平均指标,其英文全名为“Triple Exponentially Smoothed Average”,是一种研究股价趋势的长期技术分析工具。与MACD、RSI、KDJ等指标一样,TRIX也是技术分析最常见的参考指标之一。
TRIX的特点就是平均线的多重应用,从而过滤短期波动的干扰。来看源码
class Trix(Indicator):
'''
Formula:
- ema1 = EMA(data, period)
- ema2 = EMA(ema1, period)
- ema3 = EMA(ema2, period)
- trix = 100 * (ema3 - ema3(-1)) / ema3(-1)
The final formula can be simplified to: 100 * (ema3 / ema3(-1) - 1)
The moving average used is the one originally defined by Wilder,
the SmoothedMovingAverage
'''
lines = ('trix',)
params = (('period', 15), ('_rocperiod', 1), ('_movav', MovAv.EMA),)
def __init__(self):
ema1 = self.p._movav(self.data, period=self.p.period)
ema2 = self.p._movav(ema1, period=self.p.period)
ema3 = self.p._movav(ema2, period=self.p.period)
self.lines.trix = 100.0 * (ema3 / ema3(-self.p._rocperiod) - 1.0)
class TrixSignal(Trix):
lines = ('signal',)
params = (('sigperiod', 9),)
def __init__(self):
super(TrixSignal, self).__init__()
self.l.signal = self.p._movav(self.lines[0], period=self.p.sigperiod)
在backtrader中,周期默认为15,连着三次对收盘价进行EMA平均计算,我们刚刚复习过EMA的用法,EMA的N=15时,平滑因子为2/(1+15) = 1/8 = 0.125,即权重为0.125,因此公式也可以改为三重 SMA(C,8,1) 或者 DMA(C,0.125)来完成。
这个可以简单的通过股票软件的指标公式来实现,但由于EMA的N需要换算到DMA的平滑因子,相当于固定值15才能换算成DMA的0.125,一旦N被调整为其他数值就不相等了。
MTR:=EMA(EMA(EMA(CLOSE,N),N),N); //N必须=15
TRIX:(MTR-REF(MTR,1))/REF(MTR,1)*100;
MATRIX:MA(TRIX,M) ;
MTR2:=DMA(DMA(DMA(C,0.125),0.125),0.125); //用DMA来实现
TRIX2:(MTR2-REF(MTR2,1))/REF(MTR2,1)*100;
从上面的指标公式,也可以发现股票软件中的MATRIX与backtrader里后续计算的signal的计算方法是不同的,很明显股票软件中这里用了MA()进行简单平均值计算,而backtrader中仍然使用的EMA来计算,最终结果会有一些差异,因为EMA会比MA更加的平滑。这里就体现出当初股票软件在开发时那些程序员的功底是相当的学深厚,这里把它改为MA后,就会少量使金叉或死叉信号提前了,相当于上涨过程中买入的更早,而转向下跌过程中卖出的更早,可能会有较多的收益。
B_TRIX策略回测
TRIX指标的周期通常设置为15或18天,但可以根据交易者的需要进行调整。在交易中,TRIX可以作为趋势跟踪指标,以及生成买卖信号的工具:
- 买入信号:当TRIX线从下方穿越其零线向上时,表明潜在的上升趋势,可以考虑买入。
- 卖出信号:当TRIX线从上方穿越其零线向下时,表明潜在的下降趋势,可以考虑卖出。
TRIX由下往上交叉其平均线时,为长期买进信号;
TRIX由上往下交叉其平均线时,为长期卖出信号;
我们直接使用内置指标的金叉死叉进行策略设置
class St_TRIX_01(BaseSt):
params = (
('stra_name','三重指数平均TRIX'),
('P1',15),
('tradeCnt',1),
('sucessCnt',0),)
def __init__(self):
self.order = None
# trix1 = bt.indicators.TRIX(self.data) # 不使用,少一个signal
trix_sig = bt.indicators.TrixSignal(self.data)
tr1 = trix_sig.l.trix
sig1 = trix_sig.l.signal
self.crs = bt.indicators.CrossOver(tr1, sig1)
def next(self):
if self.order: # 检查是否有指令等待执行
return
if not self.position: # 没有持仓 才会进入
if self.crs >0 :
self.order = self.buy() # 执行买入
else:
if self.crs <0:
self.order = self.sell() # 执行卖出
单支股票回测的图形输出如下,当趋势明显的时候收益还是不错的;但是三重的平均后会造成平均线的移动非常的缓慢,有比较严重的滞后情况,以下图第1笔交易为例,买入点是在已经上涨了一波之后,而卖出点也是从高点掉下来很多,只要是出现快速上涨和快速下跌TRIX就表现明显的延迟,从而无法用于短线和超短线的操作。
多支股票回测的数据统计如下
与稳健性比较好的KAMA相比,首先TRIX用于中长期趋势,因此总交易次数远少于KAMA,两年的周期中出手约15将左右。胜率上看一半左右能比KAMA要高,实际上指标的胜率往往取决于这支股票本身的走势是否曲折,是否在下跌过程中不断抵抗后再次下跌等。从最大盈利上看,基本上都低于KAMA的,而最大亏损上容易出现比较大的值。最后策略收益上看,略逊于KAMA,总的来说还是相当不错的单指标策略。
单支股票的走势可能会比较极端,但指数ETF(特别是宽基)不会,据有人研究,TRIX指标对ETF基金有效,另外可以用参数优化来得到比较合适的参数,周期设置的越小则波动会较大,年出手次数会增多,而周期设置偏大一年就出手不了一只手的数量。
018_威廉指标WR
A_简介与公式
威廉指标(Williams %R或简称W%R),是由Larry Williams于1973年首创的,WMS表示的是市场处于超买还是超卖状态。
威廉指数又称威廉超买超卖指数(Williams Overbought/Oversold Index),它由拉瑞. 威廉(Larry Williams)在1973年所著的《我如何赚取百万美元》一书中首先发表,因而以他的名字命名。 威廉指数主要用于研究股价的波动,通过分析股价波动变化中的峰与谷决定买卖时机。它利用振荡点来反映市场的超买超卖现象,可以预测循期内的高点与低点,从而显示出有效的买卖信号,是用来分析市场短期行情走势的技术指标。
网上有很多关于威廉指标的介绍和应用文章,但很多都不太对,包括百度词条里也有很多东拼西凑出来的东西,前一句“WR高于80”后一句就是“低于-80时”,这就像AI引用了各种文章内容,但里面却有很多相互矛盾的地方。
我们可以从backtrader的源码来了解威廉指标到底是怎么设计的。
class WilliamsR(Indicator):
'''
Developed by Larry Williams to show the relation of closing prices to
the highest-lowest range of a given period.
Known as Williams %R (but % is not allowed in Python identifiers)
Formula:
- num = highest_period - close
- den = highestg_period - lowest_period
- percR = (num / den) * -100.0
'''
lines = ('percR',)
params = (('period', 14),
('upperband', -20.0),
('lowerband', -80.0),)
def __init__(self):
h = Highest(self.data.high, period=self.p.period)
l = Lowest(self.data.low, period=self.p.period)
c = self.data.close
self.lines.percR = -100.0 * (h - c) / (h - l)
在这里看到了威廉指标的公式也是由num和den的关系组成的,这个恰好与随机指标即KD的起手设计非常的类似,见下面_StochasticBase的源码
class _StochasticBase(Indicator):
params = (('period', 14), ('period_dfast', 3), ('movav', MovAv.Simple),)
def __init__(self):
highesthigh = Highest(self.data.high, period=self.p.period)
lowestlow = Lowest(self.data.low, period=self.p.period)
knum = self.data.close - lowestlow
kden = highesthigh - lowestlow
self.k = 100.0 * (knum / kden)
首先,num
和 den
是两个缩写词,分别代表:
num
是 "numerator" 的缩写,意思是“分子”。den
是 "denominator" 的缩写,意思是“分母”。
两个指标都首先计算了周期内的最高值和最低价,将分母赋值为最高值减最低值;而分子部分两者是不同的,KD用了收盘价减最低值而威廉指标用的是最高价减收盘价,恰好是以收盘价为分割的上和下两个部分。
然后下一步制作百分比的时候,两者也用了不同的转换,KD是直接乘上100,而威廉是乘上负100。虽然这里有两步稍有差别,但不管是从最高值与收盘价还是从收盘价与最低价的计算,其强度和趋势都是相同的。
到这里威廉指标已经完成了,而KD指标刚计算到RSV,它的百分比的K值还需要进行默认周期为3的移动平均线(无论是backtrader中的MA3还是股票软件中KD的SMMA3)。这也就表明威廉指标相当于移动平均前的KD的RSV,换种说法就是它的反应比KD还要快!
在众多的指标中,KD已经是反应最快的几个指标之一了,但把源码和理论计算一比较,WR的反应恐怕还更快于KD指标,这个我们可以在期货1分钟图上来看,就可以发现WR确实能比KD更快一步指出趋势变化,但反应快也就对应着容易错,就像高手打架,你看他耸肩了预判他要踢腿,于是你沉肘去挡,但对方看到你已经提前防了,他也就可能马上变招。
B_关于威廉指标的变形
backtrader中内置的指标我们认为它是更接近于原著的。于是我们把backtrader内置的WR的公式原本的移植到股票软件中
//股票软件原公式
WR1:100*(HHV(HIGH,N)-CLOSE)/(HHV(HIGH,N)-LLV(LOW,N));
//backtrader移植
H1:=HHV(HIGH,N);
L1:=LLV(LOW,N);
NUM:=H1-C;
DEN:=H1-L1;
WR_BT:(NUM/DEN)*-100.0;
以上指标公式代码分别对应副图1(WR)和副图2(TWR1),在周期都设为14的情况下,WR1与TWR1(都是蓝色线)就是关于0的镜面映射,因为它们的区别只是最后一步乘以100和-100的差异。但是图线的上下颠倒过来了,其实上穿买入,下穿卖出更符合多数人的观察习惯,因此backtrader的内置WR会感觉舒服一点。
但是backtrader的数据都是负数,需要二次心算,容易造成偶尔的反应错误,比如我有时就会抽风似地感觉-60比-50大,这在超短线时这么判断会延误时机和出现重大失误,并不适合于在图线上表示。
由上,有人采用100再去减掉股票软件内置WR的变形,得到了上图副图3的图形,从图上看到,TWR2中的WR1与TWR1完全一致,但数值由负数换成了100以内的正数,既实现了上穿买下穿卖的传统思维方式,又使用正数简化计算反应的步骤,是相当不错的一个变形。
//WR变形,用100去减原来的WR
WR1:100 - 100*(HHV(HIGH,N)-CLOSE)/(HHV(HIGH,N)-LLV(LOW,N));
//将WR1计算整理
/*
WR1 = 100 - 100*(H-C)/(H-L)
= 100*(H-L)/(H-L) - 100*(H-C)/(H-L)
= 100*[(H-L) - (H-C)]/(H-L)
= 100*[ C - L] /(H-L) */
// KD指标中的RSV和K的计算公式
RSV:(CLOSE-LLV(LOW,N))/(HHV(HIGH,N)-LLV(LOW,N)) *100.0;
K:SMA(RSV,M1,1);
再次经过换算,100去减WR1可整理成 100*(C-L)/(H-L)的形式,而这个形式完全等同于KD指标中的RSV计算公式。所以,变形过的WR指标其趋势和幅度与原WR没有差别,在策略应用上只需要把值用100去减就不会造成误差;但变形过后的WR其实就是KD随机指标中的RSV(Raw Stochastic Value)的计算,也就是说威廉指标相当于KD的前置RSV的运算。再简单的说就是KD指标已经包含了WR的计算,K可以认为就是WR的SMMA(3)的移动平均线。这也就是为什么WR反应比KD指标更快速的原因。
C_威廉指标简单策略回测与分析
只不过在股票软件的威廉指标里,使用了周期为10和6的两条线,网上有些相应的使用方法:
- 高位两线合一,洗盘完毕
- 向下破80数值为第一买点
- 向下破50数值为稳健买点
- 两线合一上穿20为卖点
这些说明在逻辑上都是片面的,第一买点,稳健买点表示对于持仓position还有判断等分仓位操作,这在backtrader里还挺复杂的,目前我们先不用分仓,还是使用简单的一买对应一卖
class St_WR_01(BaseSt):
params = (
('stra_name','威廉指标策略'),
('P1',14),
('tradeCnt',1),
('sucessCnt',0),)
def __init__(self):
self.order = None
WR1 = bt.indicators.WilliamsR(self.data)
self.crsdn = bt.indicators.CrossDown(WR1, -20.0)
self.crsup = bt.indicators.CrossUp(WR1, -80.0)
def next(self):
if self.order: # 检查是否有指令等待执行
return
if not self.position: # 没有持仓 才会进入
if self.crsup:
self.order = self.buy() # 执行买入
else:
if self.crsdn:
self.order = self.sell() # 执行卖出
由图可知,较多情况下,上穿-80后顺利达到-20以上再下穿能产生盈利;还有部分情况是上穿-80后没有直接上行而是产生反复,这其中一种是后续几个周期就会上冲过-20但已经提前持仓了,还有一种是很长一段周期内价格下跌但无法触发卖出,就会产生比较严重的亏损,见蓝色圈部分。
针对这些不稳定的情况,对于多数指标都要添加一些限制因素,比如卖出后,又上涨了(踏空),怎么再次触发买点,又比如买入后又下跌了,怎么触发卖点(止损点)等,添加固定的百分比限制线会有作用,比如上冲-80后如果又向穿-70止损卖出,而下穿-20卖出后WR再次向上穿-30就再次买入,但这种受到限制也多,因为每一波的行情并不相同,某一次恰好都触发了就会很好,但有些时候就差那么一点点就会亏损。
目前想到的有两种方式,一种是每次记录买入和卖出点,例如高位下穿卖出再次上穿+10买入?产生追击效果,又例如低位WR上穿买入,发现几个周期内没有明显上升还产生了10%范围内的下穿则卖出?进行止损。
另一种是检查最近周期内WR的最低值,上穿百分比与最低值超过一定百分比买入,卖出则检查最近周期WR的最高值,下穿百分比根据最高值的差值计算,满足一定条件则卖出。
内置指标小结
至此,我们通过第2节的A开头,第3节的B-E,第4节的F-N和本节的O-Z,完成了大部分重要的内置指标的学习和实践。现在对应着indicators\__init__.py来回顾小结一下:
from .basicops import *
from .mabase import *
# moving averages (so envelope and oscillators can be auto-generated)
from .sma import * from .ema import * from .smma import *
from .wma import * from .dema import * from .kama import *
from .zlema import * from .hma import * from .zlind import *
from .dma import *
from .deviation import *
# depend on basicops, moving averages and deviations
from .atr import * from .aroon import *
from .bollinger import * from .cci import *
from .crossover import * from .dpo import *
from .directionalmove import *
from .envelope import *
from .heikinashi import * from .lrsi import * from .macd import *
from .momentum import * from .oscillator import *
from .percentchange import * from .percentrank import *
from .pivotpoint import * from .prettygoodoscillator import *
from .priceoscillator import *
from .psar import * from .rsi import * from .stochastic import *
from .trix import * from .tsi import * from .ultimateoscillator import *
from .williams import *
from .rmi import * from .awesomeoscillator import * from .accdecoscillator import *
from .dv2 import * # depends on percentrank
from .kst import * from .ichimoku import *
from .hurst import * from .ols import * from .hadelta import *
- 平均线系列中,sma,ema,smma,kama (其实还有basicops中的指数平滑和动态平滑)
- 第二区中的 aroon , bollinger,cci,envelope,heikinashi,macd,
- momentum + roc + kst , pivotpoint(斐波那契), psar, stochastic(KD),trix,williams,
- dv2简介还有ichimoku等
基本上前面的18个指标组已经涵盖了大部分的内置指标组。从内置指标的源码上我们学到了很多,通过与股票软件的指标公式进行对比,也让我们发现很多不一样的地方。经过多个内置指标的实践和研究,我们也可以发现某些指标之间的相似和差异,比如MACD与AccDecOsc除了均线使用MA和EMA的不同外,逻辑顺序都是一致的,还比如说威廉指标与KDJ前置段的计算是等效的;而布林带百分比与CCI形态上几乎一致,使用到的偏差计算一个使用平均偏差,而另一个使用标准差。
还有一些指标是前置指标,比如DMI指标中需要使用到ATR(真实波幅),又比如说KnowSureThing要使用到ROC,而ROC又基于momentum指标等。
通过对内置指标的比较充分的学习实践,就如同有了一个基础和纲领,在此基础上进行的指标或策略创作能够尽可能的不要重复造轮子,尽可能的有效而不是问题一堆的残次品。
通过对内置指标单策略的回测的单个图形细节以及多股票的统计数据研究,让我们了解到单指标策略大多都有着侧重点和偶然性,适合某些股票但又特别不适合另一部分股票,比如说布林带经典策略统计下来胜率是比较高的,但有些时候买入后无法触发卖点,可能股价一直下跌而导致严重亏损,需要添加其他因子或策略来改善这个问题。而有的指标本身就是个多策略区的应用,比如一目均衡表指标,线有至少3条以下,还有两种围住的区间,在不同的区间应该是需要使用不同的策略。所以,后续实践的一个方向会是组合指标、混合指标、多策略的,可能我们会用到多个if..elif..else的判断语句。
通过对内置指标的实践,收获颇丰~