在開始文章閱讀時,有些交易名詞需要先了解一下:
頭寸:指的是銀行當前所有可以運用的資金的總和。
candle:與條形圖一樣,該圖表顯示高低價以及開盤價和收盤價,蠟燭的形狀反映了這些價格之間的關係。
RSI:其可識別走勢將要達到當前方向盡頭的時間以及超賣和超買市場條件。
既要開始使用演算法交易又要獲利,趨勢跟蹤通常是最容易的。在上一篇文章中,我寫了一個趨勢跟蹤策略,甚至透過使用更長的時間框架來確定趨勢來提高其效能。但這是一次簡單的一次進入和一次退出的策略。
本教程的重點是讓您開始使用Jesse的一些功能,這些功能有助於您編寫大多數趨勢跟蹤策略。這些功能中很少有:
1. 在兩個點退出交易,一個是固定價格,另一個是動態價格。
2. 將停止損失更新為退出一半位置後盈虧平衡。
3. 過濾盈虧比不好的交易。
4. 使用事件掛鉤交易的生命週期。
首先我使用make strategy命令建立一個新策略:
jessemake-strategyTrendFollowingStrategy
然後我編輯routes.py檔案以使用此策略,並將時間範圍設定為4h:
#tradingroutesroutes=[('Bitfinex','BTCUSD','4h','TrendFollowingStrategy'),]#incaseyourstrategyrequiredextracandles,timeframes,...extra_candles=[]
然後開啟新建立的策略檔案,如下所示:
fromjesse.strategiesimportStrategyimportjesse.indicatorsastafromjesseimportutilsclassTrendFollowingStrategy(Strategy):defshould_long(self)->bool:returnFalsedefshould_short(self)->bool:returnFalsedefshould_cancel(self)->bool:returnFalsedefgo_long(self):passdefgo_short(self):pass
進入規則
我想在以下情況走多長時間:
1. 我們處於上升趨勢(空頭交易反之亦然)
2. 收盤價(當前價)觸及50 EMA線
defshould_long(self)->bool:returnself.current_candle_touches_long_emaandself.trend==1defshould_short(self)->bool:returnself.current_candle_touches_long_emaandself.trend==-1
他們說一張圖片值1000個字說明,所以這裡有一張圖片顯示了在這種情況下我認為是上升趨勢:
這是我所說的,當前candle碰到50 EMA(橙色線是50 EMA):
現在讓我們為current_candle_touch_long_ema和trend編寫程式碼:
@propertydeftrend(self):short_ema=ta.ema(self.candles,50)long_ema=ta.ema(self.candles,100)longer_ema=ta.ema(self.candles,200)ifshort_ema>long_ema>longer_ema:return1elifshort_ema<long_ema<longer_ema:return-1else:return0@propertydefcurrent_candle_touches_long_ema(self):long_ema=ta.ema(self.candles,50)returnself.high>=long_ema>=self.low
我使用了3條EMA線來確定趨勢方向。我返回1代表上升趨勢,返回-1代表下降趨勢。current_candle_touches_long_ema非常簡單,我只需要確保當前candle的高價大於long_ema線(週期為50的EMA),並且當前candle的低價低於long_ema線。
設定入場價格和頭寸規模
進入價格將是目前長期交易的最高點。止損價格將是當前ATR的3倍,而不是我的進場價格。
在這種策略中,我想一次全部進入交易,但要在兩個點退出。我將在趨勢的前一個高點退出頭寸。這就是我所說的上升趨勢的最高點(藍線是我的目標):
要對此進行編碼,我首先選擇最近20條candle的高價。然後簡單地返回它們的最大值。
對於頭寸規模,我希望每次交易的風險為總資本的5%。要計算qty,我使用risk_to_qty實用程式。
當然,做空交易則相反。這是程式碼:
defgo_long(self):entry=self.highstop=entry-ta.atr(self.candles)*3qty=utils.risk_to_qty(self.capital,5,entry,stop)#highestpriceofthelast20barslast_20_highs=self.candles[-20:,3]previous_high=np.max(last_20_highs)self.buy=qty,entryself.stop_loss=qty,stopself.take_profit=qty/2,previous_highdefgo_short(self):entry=self.lowstop=entry+ta.atr(self.candles)*3qty=utils.risk_to_qty(self.capital,5,entry,stop)#lowestpriceofthelast20barslast_20_lows=self.candles[-20:,4]previous_low=np.min(last_20_lows)self.sell=qty,entryself.stop_loss=qty,stopself.take_profit=qty/2,previous_low
使止損達到收支平衡
如您所見,我僅以獲利價格退出一半的頭寸規模。換句話說,在降低頭寸之後,我想將止損價調整為收支平衡。要在Jesse中為其編寫程式碼,我將使用內建的on_reduced_position方法。
我只需要更新self.stop_loss即可告訴jesse更新我的止損訂單。Jesse會自動選擇它,取消上一個停止訂單,然後提交新的停止訂單。再簡單不過了!
defon_reduced_position(self):self.stop_loss=self.position.qty,self.position.entry_price
動態退出交易的後半部分
對於這種策略,我打算在動態情況下退出交易我的剩餘的頭寸。這種情況背後的想法是,在價格高度超買並即將進行嚴重調整時退出。用quant的語言來說,我想在RSI指標達到80以上時退出。
首先我將使用內建的update_position()方法寫入我的邏輯。只有在我們有未平倉合約時,此方法才會在每次新增新candle後執行。因此它用於更新位置。這意味著我們不需要檢查倉位是否開啟。
在此要考慮的下一件事情是,我只想退出頭寸的剩餘半部分。換句話說,如果未結頭寸已減少,我想將其平倉。檢查我的位置是否已降低的最簡單方法是使用內建的is_reduced屬性和liquidate()方法。
defupdate_position(self):#theRSIlogicisintendedforthesecondhalfofthetradeifself.is_reduced:rsi=ta.rsi(self.candles)ifself.is_longandrsi>80:#self.liquidate()closesthepositionwithamarketorderself.liquidate()elifself.is_shortandrsi<20:self.liquidate()
使用過濾器(filter)
到目前為止,我的策略看起來不錯,讓我們進行一次回溯測試,看看其進展如何:
jessebacktest2019-01-012020-05-01
經過約4%的回測後,我得到一個錯誤:
UncaughtException:InvalidStrategy:take-profit(3601.6)mustbebelowentry-price(3601.6)inashortposition
這個錯誤試圖告訴我們,在某種程度上,我們策略的止盈和入場價相等($ 3601.6),這是不可接受的。這是一個棘手的除錯問題,但是您在編寫了最初的幾條策略後就會熟練使用Jesse進行除錯。
defgo_long(self):#entry:thehighofthecurrentcandleentry=self.highstop=entry-ta.atr(self.candles)*3qty=utils.risk_to_qty(self.capital,5,entry,stop)last_20_highs=self.candles[-20:,3]previous_high=np.max(last_20_highs)self.buy=qty,entryself.stop_loss=qty,stop#(first)take-profit:thehighofthelast20candlesself.take_profit=qty/2,previous_high
這個錯誤告訴我們,在某些情況下,入場和獲利在某些時候是相同的。也就是說,在那一點上,當前蠟燭的高位是最近20個candle中的最高點。我們考慮的不是這種策略的交易型別。
我們可以透過在should_long方法中使用一些骯髒的if-else語句或使用針對此類情況設計的過濾器(filter)來防止這種情況的發生。
過濾器(filter)只是一個返回布林值的函式。透過返回True值來傳遞過濾器(filter),反之亦然。我定義一個過濾器並將其命名為reward_to_risk_filter。名稱可以是任何東西,但是通常最好的做法是使用單詞filter來開始或結束過濾方法的名稱。該篩選器(filter)的工作是確保我們嘗試輸入的交易值得。
defreward_to_risk_filter(self):profit=abs(self.average_entry_price-self.average_take_profit)loss=abs(self.average_entry_price-self.average_stop_loss)win_loss_ratio=profit/lossreturnwin_loss_ratio>1
目前Jesse仍然不知道reward_to_risk_filter()是一個過濾器(filter)。為了使其能夠識別我的過濾器(filter),我需要將其新增到filters()方法中,該方法是一個返回Python列表的內建方法:
deffilters(self):return[]
現在,我將在返回列表中新增reward_to_risk_filter作為變數。這意味著它的結尾不能有括號:
deffilters(self):return[self.reward_to_risk_filter,]
現在讓我們再執行一次回測:
jessebacktest2019-01-012020-05-01
這次一切進展順利。
結 論
保持策略越簡單,隨著時間的推移除錯策略甚至改進策略就越容易。
使用Jesse編寫策略就像手動交易策略一樣簡單。因此下次您找到交易手冊或交易專家介紹的策略時,只需為其編寫程式碼,然後對其進行回測。
------------------------------------------------------------------------------
原文作者:Saleh
原文連結:https://towardsdatascience.com/how-to-write-an-advanced-trend-following-strategy-to-algotrade-bitcoin-a38e47443ddc
譯者:鏈三豐
------------------------------------------------------------------------------
相關文章閱讀:
區塊鏈研究實驗室|如何利用Jesse協議編寫比特幣交易獲利策略
區塊鏈研究實驗室|如何在交易策略中使用多個時間框架