SuperTrend V.1 超級趨勢線系統

一、故事由來

我的好朋友燃哥觀察了這個指標很久,在元旦以前推薦給我,討論是否可以轉化成量化。
可惜拖延症犯了,一直拖到現在纔來幫他完成這樣一個心願,其實也是最近對算法的領悟突飛猛進。
估摸着某一天寫一個pine的翻譯器。一切皆可python。。
好了廢話不多說,我們來介紹一下這個傳說中的超級趨勢線。。

二、系統介紹

CMC Markets 新一代智能交易系統 —— 超級趨勢線(Supertrend)
這裏有一篇文章介紹這個系統。
在這裏插入圖片描述

在CMC Markets中的新一代智能交易系統中,在技術指標中選取“超級趨勢線”調取即可使用,
如圖中所示,可以根據自身喜好對上漲的信號、下跌的信號調節“顏色和粗細”。
那麼什麼是超趨勢指標?在理解超趨勢指標公式之前,理解ATR是必要的,因爲超趨勢使用ATR值來計算指標值。

其中的主要算法下面也有一張圖來介紹
在這裏插入圖片描述

大致看一下,主要描述是HL2(k線均價)乘以n倍ATR的通道。做趨勢突破。
但文章寫得比較簡略。沒有詳細的算法。隨後我想到了最牛的社區Tradingview。
果不奇然。上面果然有。
在這裏插入圖片描述

從圖上看,還是比較切合趨勢的。但可惜的是它只是一個Alert的報警信號。

三、學習源碼

看着代碼還不算太長,那我們就翻譯過來試一下吧。!(っ•̀ω•́)っ✎⁾⁾!
在這裏插入圖片描述
完整pine代碼如上。。

四、代碼轉化

這裏我們在FMZ新建一個策略,起名SuperTrade
在這裏插入圖片描述

接着我們來設置2個參數Factor、Pd
在這裏插入圖片描述

爲了更好的簡化代碼的操作,便於理解,這樣要用到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 (Trend1) 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)

在這裏插入圖片描述
在這裏插入圖片描述

五、全部代碼

我調整了一下整體的代碼結構。
並將做多做空相關下單指令合併到策略中。
下面是完整代碼

'''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的話收益應該會比較好看。

在這裏插入圖片描述

六、寫在最後

SuperTrend是一個非常不錯的交易系統

SuperTrend系統的主要原理是採用ATR通道突破策略(類似於肯特通道)
但其變化的地方主要在於使用了強盜布林的收窄策略,或者說是逆向的唐奇安原理。
在行情運行中不斷收窄上下通道。
以便達到通道突破轉向的操作。(一旦通道突破,上下軌恢復初始值)

我在TradingView上把up dn TrendUp TrendDn 分別plot了出來
這樣便於更好的理解這個策略

一目瞭然
在這裏插入圖片描述

另外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++的意思,有機會再重寫一份。

希望大家可以從中學到精髓。
難搞哦。~!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章