资源加载中... loading...

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 感谢梦梦老师哈