我们知道价格变化的速度本身就在变化,传统简单均线受困于固定周期参数,这使得不论市场的走势如何,短期均线灵敏度高,更贴近价格走势,但在市场震荡时期反复转向,造成频繁发出错误开平仓信号;长期均线在趋势判断上更加可靠,但在市场加速上涨或下跌时反应迟钝,造成错过最佳的买卖点。
因此虽然传统简单均线可以在一定程度适应行情,但是却很难根据市场变化去进行调整,进而更好的把握趋势。特别在长期震荡行情中,不仅得不到正收益而且付出高额的交易成本,为了解决这个问题,我们引入考夫曼创立的自适应均线。
在《精明交易者》中,作者考夫曼(Kaufman)提出了“自适应移动平均线”,简称AMA。该均线考虑到了市场价格变化速率,在普通均线的基础上增加了平滑系数,并自适应动态调整均线的灵敏度,可以在慢速趋势和快速趋势之间自我调整。当市场出现盘整、趋势不明显时期,AMA倾向于慢速移动平均线。当市场波动较大,趋势明显,价格沿一个方向快速移动时,AMA倾向于快速移动平均线。 考夫曼均线本质上是根据一段时间内的价格波动率进行调整,计算出了合适的入场阈值提供了最佳的买卖点位。也就是说,它分为两部分主逻辑,第二部分逻辑在波动率层面做了又一次自适应。从而反应市场真实的趋势,便于快速抓住趋势性上涨和下跌的时机,同时规避市场来回震荡的影响。
有经验的交易者都习惯于在趋势展开的行情中使用快速均线,在震荡较多的行情中使用慢速均线。但如何把这个方法数量化,让程序来区分这两种行情?这里就需要引入“效率”的概念。
如果价格一致朝一个方向运行,每天收盘价的变化贡献于总的运行幅度,那么就被称为高效率;如果价格涨涨跌跌,很多次收盘价的变化相互抵消,那么就被称为低效率。这类似于物理学中的位移,如果价格在10天内上涨了100个点,我们可称为高效率,如果价格在10天内上涨了10个点,我们可以称为低效率。
第一步:计算价格效率
价格效率是建立在市场移动的速度和方向以及市场中噪声量的基础之上的,假设价格效率是在0~1之间,0表示市场没有移动,只有噪声;1表示市场只有移动,没有噪声。如果价格在10天内上涨了100个点,每天移动10个点,其价格效率就是:100 / (10 * 10) = 1;如果价格在10天内上涨了10个点,但每天震荡10个点,其价格效率就是:10 / (10 * 10) = 0.1
其计算公式是:首先计算价格变动值,即当根K线价格与前N根K线的价格差的绝对值;然后计算价格波动值,即N根K线内,所有价格变动绝对值的总和;最后计算效率系数,即价格变动值除以价格波动值。
由此可见,在价格变动值一定条件下,市场波动越大,效率系数越小,此时使用慢速移动平均线更能把握整体趋势走向,因为慢速平均线不易被市场短期波动改变方向;反之,价格变动值一定条件下,市场波动越小,效率系数越大,此时应该使用快速(短期)移动平均线。
第二步:计算平滑系数
考夫曼用一系列的移动平均速度来描述平滑系数,其计算方式与EMA类似,根据价格所占权重,重新定义快速和慢速趋势速度系数,比如可以将2天的平均称为快速,30天的平均称为慢速。其中快速趋势系数是:2 /(2 + 1)= 2 / 3 = 0.66667;慢速趋势系数是:2 /(30 + 1) = 2 / 31 = 0.06452。它们的差值是:0.60215。
上面公式中的n1和n2是交易周期数,并且n1小于n2。默认n1为2,n2为30。最后利用效率比率计算平滑系数,也就是:效率系数 * 0.60215 + 0.06452。
可见,当市场波动越大,趋势明显时,平滑系数更加趋向于选择快速趋势系数快速趋势系数,反之,在市场震荡盘整,趋势不明显时期,平滑系数更趋向于选择慢速趋势系数慢速趋势系数。
第三步:计算AMA值
因为在效率系数太低时,可能会取消交易,所以卡夫曼建议在计算AMA值之前,对最后的平滑系数再次乘方。
假设昨天的AMA值是40,当前的价格是47,它们之间有7个点的差值。那么在一个高效市场,其AMA值提高将近3.1个点,这几乎是差值的一半。在一个低效市场,这个差值几乎不会对AMA值产生影响。
'''backtest start: 2019-01-01 00:00:00 end: 2021-01-01 00:00:00 period: 1d basePeriod: 1d exchanges: [{"eid":"Futures_CTP","currency":"FUTURES"}] ''' # 导入库 import talib import numpy as np mp = 0 # 定义一个全局变量,用于控制虚拟持仓 # 把K线数组转换成收盘价数组,用于计算AMA的值 def get_close(r): arr = [] for i in r: arr.append(i['Close']) return arr # 判断两根AMA交叉 def is_cross(arr1, arr2): if arr1[-2] < arr2[-2] and arr1[-1] > arr2[-1]: return True # 程序主函数 def onTick(): _C(exchange.SetContractType, "c000") # 订阅期货品种 bar_arr = _C(exchange.GetRecords) # 获取K线数组 if len(bar_arr) < 100: # 如果K线数组长度过小于就直接返回 return close_arr = get_close(bar_arr) # 把K线数组转换成收盘价数组 np_close_arr = np.array(close_arr) # 把列表转换为numpy.array ama1 = talib.KAMA(np_close_arr, 10).tolist() # 计算短期AMA ama2 = talib.KAMA(np_close_arr, 100).tolist() # 计算长期AMA last_close = close_arr[-1] # 获取最新价格 global mp # 全局变量,用于控制虚拟持仓 if mp == 1 and is_cross(ama2, ama1): exchange.SetDirection("closebuy") # 设置交易方向和类型 exchange.Sell(last_close - 1, 1) # 平多单 mp = 0 # 设置虚拟持仓的值,即空仓 if mp == -1 and is_cross(ama1, ama2): exchange.SetDirection("closesell") # 设置交易方向和类型 exchange.Buy(last_close, 1) # 平空单 mp = 0 # 设置虚拟持仓的值,即空仓 if mp == 0 and is_cross(ama1, ama2): exchange.SetDirection("buy") # 设置交易方向和类型 exchange.Buy(last_close, 1) # 开多单 mp = 1 # 设置虚拟持仓的值,即有多单 if mp == 0 and is_cross(ama2, ama1): exchange.SetDirection("sell") # 设置交易方向和类型 exchange.Sell(last_close - 1, 1) # 开空单 mp = -1 # 设置虚拟持仓的值,即有空单 def main(): while True: onTick() Sleep(1000)