SuperTrend V.1--超级趋势线系统

Author: homily, Created: 2020-04-20 22:10:36, Updated: 2023-10-08 19:57:57

img

一、故事由来

我的好朋友燃哥观察了这个指标很久,在元旦以前推荐给我,讨论是否可以转化成量化。 可惜拖延症犯了,一直拖到现在才来帮他完成这样一个心愿,其实也是最近对算法的领悟突飞猛进。 估摸着某一天写一个pine的翻译器。一切皆可python。。 好了废话不多说,我们来介绍一下这个传说中的超级趋势线。。

二、系统介绍

CMC Markets 新一代智能交易系统 —— 超级趋势线(Supertrend) 这里有一篇文章介绍这个系统。 img

在CMC Markets中的新一代智能交易系统中,在技术指标中选取“超级趋势线”调取即可使用, 如图中所示,可以根据自身喜好对上涨的信号、下跌的信号调节“颜色和粗细”。 那么什么是超趋势指标?在理解超趋势指标公式之前,理解ATR是必要的,因为超趋势使用ATR值来计算指标值。

其中的主要算法下面也有一张图来介绍 img

大致看一下,主要描述是HL2(k线均价)乘以n倍ATR的通道。做趋势突破。 但文章写得比较简略。没有详细的算法。随后我想到了最牛的社区Tradingview。 果不奇然。上面果然有。 img

从图上看,还是比较切合趋势的。但可惜的是它只是一个Alert的报警信号。

三、学习源码

看着代码还不算太长,那我们就翻译过来试一下吧。!(っ•̀ω•́)っ✎⁾⁾! img 完整pine代码如上。。

四、代码转化

这里我们在FMZ新建一个策略,起名SuperTrade img

接着我们来设置2个参数Factor、Pd img

为了更好的简化代码的操作,便于理解,这样要用到python的高级数据扩展包pandas

中午吃饭的时候我问梦梦老师,FMZ是否支持这个库。下午一看居然可以用了。 梦梦老师真的太厉害了。

1.我们要导入pandas库time库 2.在main函数当中设置使用季度合约(主要跑okex) 3.设定一个循环doTicker()15分钟检测1次。 将代码跑在15分钟的周期上 接着我们在doTicker()中写主要策略。

import pandas as pd
import time

def main():
    exchange.SetContractType("quarter")
    preTime = 0
    Log(exchange.GetAccount())
    while True:
        records = exchange.GetRecords(PERIOD_M15)
        if records and records[-2].Time > preTime:
            preTime = records[-2].Time
            doTicker(records[:-1])
        Sleep(1000 *60)
        

4.我们要取回k线的OHCLV 所以用GetRecords() 5.我们将取回的数据导入pandas M15 = pd.DataFrame(records) 6.我们要修改表的头部标签。 M15.columns = [‘time’,‘open’,‘high’,‘low’,‘close’,‘volume’,‘OpenInterest’] 其实就是将’open’,‘high’,‘low’,'close’ 的首字母改成小写,便于后期写代码不要一会大写一会小写。

def doTicker(records):
    M15 = pd.DataFrame(records)
    M15.columns = ['time','open','high','low','close','volume','OpenInterest']  

7.给数据集合增加一列hl2 hl2=(high+low)/2

#HL2
M15['hl2']=(M15['high']+M15['low'])/2

8.接着我们来计算ATR 因为ATR的计算要导入一个变量length,它的取值是Pd

接着我们通过查阅麦语言手册,ATR真實波動幅度均值的算法步骤如下: TR : MAX(MAX((HIGH-LOW),ABS(REF(CLOSE,1)-HIGH)),ABS(REF(CLOSE,1)-LOW)); ATR : RMA(TR,N)

其中TR的值取下面3个差值的最大一个 1、当前交易日的最高价与最低价间的波幅 HIGH-LOW 2、前一交易日收盘价与当个交易日最高价间的波幅 REF(CLOSE,1)-HIGH) 3、前一交易日收盘价与当个交易日最低价间的波幅 REF(CLOSE,1)-LOW) 所以TR : MAX(MAX((HIGH-LOW),ABS(REF(CLOSE,1)-HIGH)),ABS(REF(CLOSE,1)-LOW));

在python计算中

M15['prev_close']=M15['close'].shift(1)

要先设立一个prev_close 去取close在上一行的数据,也就是将close右移1格成立一个新的参数

ranges= [M15['high'] - M15['low'],M15['high']-M15['prev_close'],M15['low']-M15['prev_close']]

接着定义一个中间变量 记录TR的3个对比值的数组。(HIGH-LOW)(high-prev_close)(low-prev_close)

M15['tr'] = pd.DataFrame(ranges).T.abs().max(axis=1)

我们在数据集合当中定义新的一列取名TR,TR的取值是取中间变量绝对值的最大一个,使用abs()和max()函数

    alpha = (1.0 / length) if length > 0 else 0.5
    M15['atr']=M15['tr'].ewm(alpha=alpha, min_periods=length).mean()

最后我们要计算ATR的值,ATR : RMA(TR,N),据查RMA的算法其实就是一个固定值变种的EMA算法。 N是我们导入的变量,其中ATR的默认参数是14。这里我们导入alpha=length的倒数。

===

然后用ewm算法计算ema 完整ATR计算过程如下

    #ATR(PD)
    length=Pd
    M15['prev_close']=M15['close'].shift(1)
    ranges= [M15['high'] - M15['low'],M15['high']-M15['prev_close'],M15['low']-M15['prev_close']]
    M15['tr'] = pd.DataFrame(ranges).T.abs().max(axis=1)
    alpha = (1.0 / length) if length > 0 else 0.5
    M15['atr']=M15['tr'].ewm(alpha=alpha, min_periods=length).mean()

9始计算Up和Dn

    M15['Up']=M15['hl2']-(Factor*M15['atr'])
    M15['Dn']=M15['hl2']+(Factor*M15['atr'])

Up=hl2 -(Factor * atr) Dn=hl2 +(Factor * atr) 是不是很简单呢。

下面是TV当中15行-21行的核心代码段

TrendUp=close[1]>TrendUp[1]? max(Up,TrendUp[1]) : Up
TrendDown=close[1]<TrendDown[1]? min(Dn,TrendDown[1]) : Dn

Trend = close > TrendDown[1] ? 1: close< TrendUp[1]? -1: nz(Trend[1],1)
Tsl = Trend==1? TrendUp: TrendDown

linecolor = Trend == 1 ? green : red

这一段的主要意思是想表达, 如果处于看涨阶段,(下方线)TrendUp = max(Up,TrendUp[1]) 如果处于下跌阶段,(上方线)TrendDown=min(Dn,TrendDown[1]) 也就是说在一个趋势中,ATR的值一直在使用一种类似强盗布林策略的技术。 不断将通道的另一侧收窄

这里TrendUp和TrendDown每一次的计算都需要进行自我迭代。 就是每一步都要拿上一步的自己来计算。 所以要对数据集合做循环遍历。

这里先要对数据集合新建字段TrendUp,TrendDown,Trend,linecolor。并给定他们一个初始值 接着使用fillna(0)语法将之前计算的结果中带有空值的数据填上0

    M15['TrendUp']=0.0
    M15['TrendDown']=0.0
    M15['Trend']=1
    M15['Tsl']=0.0
    M15['linecolor']='Homily'
    M15 = M15.fillna(0)

启用一个for循环 在循环中采用python三目运算

    for x in range(len(M15)):

计算TrendUp TrendUp = MAX(Up,TrendUp[-1]) if close[-1]>TrendUp[-1] else Up 大致意思是 如果 上一个close>上一个TrendUp,成立取Up和上一个TrendUp当中最大的值,不成立取Up值,并传递给当前TrendUp

        M15['TrendUp'].values[x] = max(M15['Up'].values[x],M15['TrendUp'].values[x-1]) if (M15['close'].values[x-1]>M15['TrendUp'].values[x-1]) else M15['Up'].values[x]

同理,计算TrendDown TrendDown=min(Dn,TrendDown[-1]) if close[-1]<TrendDown[-1] else Dn 大致意思是 如果 上一个close<上一个TrendDown,成立取Dn和上一个TrendDown当中最小的值,不成立取Dn值,并传递给当前TrendDown

        M15['TrendDown'].values[x] = min(M15['Dn'].values[x],M15['TrendDown'].values[x-1]) if (M15['close'].values[x-1]<M15['TrendDown'].values[x-1]) else M15['Dn'].values[x]

下面是计算控制方向的flag,我简化了一下伪代码 Trend= 1 if (close > TrendDown[-1]) else (x) x = -1 if (close< TrendUp[-1]) else Trend[-1]

意义是是 如果 收盘价>上一个 TrendDown 则取1(看多) 不成立取x 如果 收盘价<上一个 TrendUp 则取-1(看空)不成立取上一个Trend (意思是是不变) 翻译成图像语言就是突破上轨转换flag看多,突破下轨转换flag看空,其他时间不变。

        M15['Tsl'].values[x] = M15['TrendUp'].values[x] if  (M15['Trend'].values[x]==1) else M15['TrendDown'].values[x]

计算Tsl和Linecolor Tsl= rendUp if (Trend==1) else TrendDown Tsl 是用来在图像上表示SuperTrend 的值。意思是看多的时候在图上标记下轨,看空的时候在图上标记上轨。 linecolor= ‘green’ if (Trend==1) else ‘red’ linecolor 的含义是 如果看多 则标记绿线 ,如果看空则标记空色(主要是用途Tradingview展示)

        M15['Tsl'].values[x] = M15['TrendUp'].values[x] if  (M15['Trend'].values[x]==1) else M15['TrendDown'].values[x]
        M15['linecolor'].values[x]= 'green' if ( M15['Trend'].values[x]==1) else  'red'

接着23-30行的代码主要是plot绘图 这里不做详解。

最后还有2行代码用于买入卖出信号控制 Tradingview中,他的含义是 反转了Flag以后给出信号 将条件语句转换成为python。 如果上一个Trend flag从-1变成1 代表突破上方阻力 开多 如果上一个Trend flag从1变成-1 代表突破下发支撑 开空

    if(M15['Trend'].values[-1] == 1 and M15['Trend'].values[-2] == -1):
        Log('SuperTrend V.1 Alert Long',"Create Order Buy)
    if(M15['Trend'].values[-1] == -1 and M15['Trend'].values[-2] == 1):
        Log('SuperTrend V.1 Alert Long',"Create Order Sell)

本段完整代码如下:

    M15['TrendUp']=0.0
    M15['TrendDown']=0.0
    M15['Trend']=1
    M15['Tsl']=0.0
    M15['linecolor']='Homily'
    M15 = M15.fillna(0)
    
    for x in range(len(M15)):
        M15['TrendUp'].values[x] = max(M15['Up'].values[x],M15['TrendUp'].values[x-1]) if (M15['close'].values[x-1]>M15['TrendUp'].values[x-1]) else M15['Up'].values[x]
        M15['TrendDown'].values[x] = min(M15['Dn'].values[x],M15['TrendDown'].values[x-1]) if (M15['close'].values[x-1]<M15['TrendDown'].values[x-1]) else M15['Dn'].values[x]
        M15['Trend'].values[x] = 1 if (M15['close'].values[x] > M15['TrendDown'].values[x-1]) else ( -1 if (M15['close'].values[x]< M15['TrendUp'].values[x-1])else M15['Trend'].values[x-1] )
        M15['Tsl'].values[x] = M15['TrendUp'].values[x] if  (M15['Trend'].values[x]==1) else M15['TrendDown'].values[x]
        M15['linecolor'].values[x]= 'green' if ( M15['Trend'].values[x]==1) else  'red'
        
    if(M15['Trend'].values[-1] == 1 and M15['Trend'].values[-2] == -1):
        Log('SuperTrend V.1 Alert Long',"Create Order Buy)
        Log('Tsl=',Tsl)
    if(M15['Trend'].values[-1] == -1 and M15['Trend'].values[-2] == 1):
        Log('SuperTrend V.1 Alert Long',"Create Order Sell)
        Log('Tsl=',Tsl)

img img

五、全部代码

我调整了一下整体的代码结构。 并将做多做空相关下单指令合并到策略中。 下面是完整代码

'''backtest
start: 2019-05-01 00:00:00
end: 2020-04-21 00:00:00
period: 15m
exchanges: [{"eid":"Futures_OKCoin","currency":"BTC_USD"}]
'''

import pandas as pd
import time

def main():
    exchange.SetContractType("quarter")
    preTime = 0
    Log(exchange.GetAccount())
    while True:
        records = exchange.GetRecords(PERIOD_M15)
        if records and records[-2].Time > preTime:
            preTime = records[-2].Time
            doTicker(records[:-1])
        Sleep(1000 *60)

       
def doTicker(records):
    #Log('onTick',exchange.GetTicker())
    M15 = pd.DataFrame(records)

    #Factor=3
    #Pd=7
    
    M15.columns = ['time','open','high','low','close','volume','OpenInterest']  
    
    #HL2
    M15['hl2']=(M15['high']+M15['low'])/2

    #ATR(PD)
    length=Pd
    M15['prev_close']=M15['close'].shift(1)
    ranges= [M15['high'] - M15['low'],M15['high']-M15['prev_close'],M15['low']-M15['prev_close']]
    M15['tr'] = pd.DataFrame(ranges).T.abs().max(axis=1)
    alpha = (1.0 / length) if length > 0 else 0.5
    M15['atr']=M15['tr'].ewm(alpha=alpha, min_periods=length).mean()


    M15['Up']=M15['hl2']-(Factor*M15['atr'])
    M15['Dn']=M15['hl2']+(Factor*M15['atr'])
    
    M15['TrendUp']=0.0
    M15['TrendDown']=0.0
    M15['Trend']=1
    M15['Tsl']=0.0
    M15['linecolor']='Homily'
    M15 = M15.fillna(0)

    for x in range(len(M15)):
        M15['TrendUp'].values[x] = max(M15['Up'].values[x],M15['TrendUp'].values[x-1]) if (M15['close'].values[x-1]>M15['TrendUp'].values[x-1]) else M15['Up'].values[x]
        M15['TrendDown'].values[x] = min(M15['Dn'].values[x],M15['TrendDown'].values[x-1]) if (M15['close'].values[x-1]<M15['TrendDown'].values[x-1]) else M15['Dn'].values[x]
        M15['Trend'].values[x] = 1 if (M15['close'].values[x] > M15['TrendDown'].values[x-1]) else ( -1 if (M15['close'].values[x]< M15['TrendUp'].values[x-1])else M15['Trend'].values[x-1] )
        M15['Tsl'].values[x] = M15['TrendUp'].values[x] if  (M15['Trend'].values[x]==1) else M15['TrendDown'].values[x]
        M15['linecolor'].values[x]= 'Long' if ( M15['Trend'].values[x]==1) else  'Short'
 

    linecolor=M15['linecolor'].values[-2]
    close=M15['close'].values[-2]
    Tsl=M15['Tsl'].values[-2] 


    if(M15['Trend'].values[-1] == 1 and M15['Trend'].values[-2] == -1):

        Log('SuperTrend V.1 Alert Long','Create Order Buy')
        Log('Tsl=',Tsl)
        position = exchange.GetPosition()
        if len(position) > 0:
            Amount=position[0]["Amount"]
            exchange.SetDirection("closesell")
            exchange.Buy(_C(exchange.GetTicker).Sell*1.01, Amount);
        
        exchange.SetDirection("buy")
        exchange.Buy(_C(exchange.GetTicker).Sell*1.01, vol);

    if(M15['Trend'].values[-1] == -1 and M15['Trend'].values[-2] == 1):
        Log('SuperTrend V.1 Alert Long','Create Order Sell')
        Log('Tsl=',Tsl)
        position = exchange.GetPosition()
        if len(position) > 0:
            Amount=position[0]["Amount"]
            exchange.SetDirection("closebuy")
            exchange.Sell(_C(exchange.GetTicker).Buy*0.99,Amount);
        exchange.SetDirection("sell")
        exchange.Sell(_C(exchange.GetTicker).Buy*0.99, vol*2);

公开策略连接https://www.fmz.com/strategy/200625

六、回测与总结

我们选取了近一年的数据进行回测。 使用okex季度合约 15分钟周期。 设定的参数是, Factor=3 Pd=45 vol=100(每次下单100张) 所得年化收益,约33%。 总体来说回撤并不是很大, 其中主要是312的大跌对系统产生了比较大的冲击, 如果没有312的话收益应该会比较好看。

img

六、写在最后

SuperTrend是一个非常不错的交易系统

SuperTrend系统的主要原理是采用ATR通道突破策略(类似于肯特通道) 但其变化的地方主要在于使用了强盗布林的收窄策略,或者说是逆向的唐奇安原理。 在行情运行中不断收窄上下通道。 以便达到通道突破转向的操作。(一旦通道突破,上下轨恢复初始值)

我在TradingView上把up dn TrendUp TrendDn 分别plot了出来 这样便于更好的理解这个策略 一目了然 img

另外github上还有一个js的版本。js我不是很懂,但从if语句看好像有点问题。 地址是https://github.com/Dodo33/gekko-supertrend-strategy/blob/master/Supertrend.js

最后我去追查了一下原版。 它发表在2013.05.29 作者是Rajandran R C++代码发表在Mt4论坛https://www.mql5.com/en/code/viewcode/10851/128437/Non_Repainting_SuperTrend.mq4 我大致看懂了C++的意思,有机会再重写一份。

希望大家可以从中学到精髓。 难搞哦。~!


Related

More

zdg4484 YYDS!

lglydz2010 如果直接使用这个策略在OK交易所交易需要怎么连接交易所,小白一个不会python,看不明白

bamsmen 这里如果312那波行情没吃到的话应该参数还有很大的调整空间,因为supertrend主要就是抓趋势单,312是不应该错过的。另外期待lz的pine翻译器早日问世

张无忌 可惜各种周期和参数,回测效果都不怎么好, 不知道其它人怎么优化的?

伍大胖 可以了,弄好了,感谢您的付出

伍大胖 用不了呢,显示这个:Traceback (most recent call last): File "<string>", line 1473, in Run File "<string>", line 8, in <module> ImportError: No module named pandas

xunfeng91 pine的翻译器,期待

tiemuer 没啥文化只能说一声 牛逼!

frank131419 “估摸着某一天写一个pine的翻译器。一切皆可python。”—— 牛,好些人看好这个!

Kmeans 回测引擎的代码是否可以开源呢,我想实现复现一下 然后用svm找出最好的参数

dsaidasi 这个系统好像也曾经是收益率前十的期货策略。长期坚持做下去是能赚钱的。

轻轻的云 你好,请教下,PD就是 ATR的长度值吧? 比如 ATR(14) ,就是 PD赋值14了吧?

小小梦

ovels 期待期待,pine真的看不太懂,教程也很少

homily 意思是缺少pandas包 你的系统可能需要pip install pandas

Ant_Sky 请问这是怎么处理的呢?万分感谢

homily 啊哈哈,谢谢老板

小小梦 一会儿,公开。

lonelyman 求JS版!

homily 恩啊。学习精髓。

轻轻的云 好的,谢谢!!! 顺手mq4也收走了,谢谢。。 o(∩_∩)o

homily 是的,完全正确

homily 赞的,!

小小梦 碰巧我也写了个JS版本的。

homily 感谢梦梦老师哈