本文将繼續對backtrader的order進行介紹,具體介紹StopLimit訂單的使用。
選取平安銀行(000001)2018年1月1日至2019年12月31日的日線資料進行回測。為了便于分析,回測過程中設定傭金為0,交易機關大小為100。
這篇文章寫作過程有些坎坷,因為發現backtrader在StopLimit訂單實作過程中可能存在bug,已經在backtrader的官方社群發帖提問,目前尚未收到回複。本文将結合目前StopLimit訂單的實作代碼對其功能進行說明。
執行規則
在StopLimit訂單建立時,會設定price、plimit和valid時間,如果超過valid時間訂單仍未滿足執行條件,訂單就會過期被取消。在valid時間内,訂單會按照下面描述的價格比對規則判斷訂單是否會成交。
價格比對
StopLimit訂單使用K線4個價格點(Open/High/Low/Close)以及price和plimit,來判斷訂單是否會成交。
- 首先以price作為觸發價格,使用Stop訂單價格比對規則判斷訂單是否會被觸發,參閱筆記(29)。
- 對于已觸發訂單,以plimit作為限制價格,使用Limit訂單價格比對規則判斷訂單是否會成交,參閱筆記(28)。
示例
政策:
- 買入條件:收盤價高于15日均線,價格在10日内,較收盤價向上突破0.5%作為觸發價格,較收盤價上漲0.2%作為限制價格。
- 賣出條件:收盤價低于15日均線,價格在10日内,較收盤價向下跌破0.5%作為觸發價格,較收盤價下跌0.2%作為限制價格。
# 檢查是否持倉
if self.position:
# 檢查是否達到賣出條件
if self.buysell < 0:
if self.p.valid:
valid = self.data.datetime.date(0) + \
datetime.timedelta(days=self.p.valid)
else:
valid = None
...
elif self.p.exectype == 'StopLimit':
price = self.data.close * (1.0 - self.p.perc1 / 100.0)
plimit = self.data.close * (1.0 - self.p.perc2 / 100.0)
self.sell(exectype=bt.Order.StopLimit, price=price, valid=valid, plimit = plimit)
if self.p.valid:
txt = 'SELL CREATE, exectype StopLimit, close %.2f, price %.2f, pricelimit %.2f, valid: %s'
self.log(txt % (self.data.close[0], price, plimit, valid.strftime('%Y-%m-%d')))
else:
txt = 'SELL CREATE, exectype StopLimit, close %.2f, price %.2f, pricelimit %2.f'
self.log(txt % (self.data.close[0], price, plimit))
# 不在場内且出現買入信号
elif self.buysell > 0:
if self.p.valid:
valid = self.data.datetime.date(0) + \
datetime.timedelta(days=self.p.valid)
else:
valid = None
...
elif self.p.exectype == 'StopLimit':
price = self.data.close * (1.0 + self.p.perc1 / 100.0)
plimit = self.data.close * (1.0 + self.p.perc2 / 100.0)
self.buy(exectype=bt.Order.StopLimit, price=price, valid=valid,
plimit=plimit)
if self.p.valid:
txt = ('BUY CREATE, exectype StopLimit, close %.2f, price %.2f,'
' pricelimit %.2f, valid: %s')
self.log(txt % (self.data.close[0], price, plimit, valid.strftime('%Y-%m-%d')))
else:
txt = ('BUY CREATE, exectype StopLimit, close %.2f, price %.2f,'
' pricelimit: %.2f')
self.log(txt % (self.data.close[0], price, plimit))
為了說明StopLimit訂單可能出現的成交情況,在代碼中使用p.perc1 = 0.5,p.perc2 = 0.2,來控制觸發及限制價格,使用p.valid = 10,來控制訂單的有效期。
StopLimit訂單執行邏輯的源代碼位于backtrader包中的bbroker.py,這裡以買單的代碼為例:
def _try_exec_stoplimit(self, order,
popen, phigh, plow, pclose,
pcreated, plimit):
if order.isbuy():
if popen >= pcreated:
order.triggered = True
self._try_exec_limit(order, popen, phigh, plow, plimit)
elif phigh >= pcreated:
# price penetrated upwards during the session
order.triggered = True
# can calculate execution for a few cases - datetime is fixed
if popen > pclose:
if plimit >= pcreated: # limit above stop trigger
p = self._slip_up(phigh, pcreated, lim=True)
self._execute(order, ago=0, price=p)
elif plimit >= pclose:
self._execute(order, ago=0, price=plimit)
else: # popen < pclose
if plimit >= pcreated:
p = self._slip_up(phigh, pcreated, lim=True)
self._execute(order, ago=0, price=p)
在這段源代碼中展示了StopLimit訂單價格比對的規則。在價格比對之前,會判斷訂單是否過期,相關代碼也在bbroker.py中,這裡就不做展示了。代碼中,popen、phigh、plow、pclose依次對應K線的open、high、low、close值,pcreated對應于buy方法參數中的觸發價格price值,plimit對應buy方法參數中的限制價格plimit值。
1. 對于第1種條件分支情況:popen >= pcreated
此時,開盤價大于等于觸發價格,那麼訂單就被觸發:
随後訂單将以plimit為限制價格,按照Limit訂單的邏輯被執行:
來看兩個例子:
例1:将圖表顯示時間範圍調整為2019年8月,輸出圖形如下:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiclRnblN2XjlGcjAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHLwUEVNpXQq5kMRpHW3BjMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL2ETOxITOzAjM0ITNwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
上圖中,紅色的曲線表示15日均線。
部分輸出結果為:
2019-08-08, BUY CREATE, exectype StopLimit, close 14.38, price 14.45, pricelimit 14.41, valid: 2019-08-18
2019-08-09, ORDER SUBMITTED
2019-08-09, ORDER ACCEPTED
2019-08-09, Open: 14.55, High: 14.85, Low: 14.43, Close: 14.52
2019-08-12, Open: 14.61, High: 15.12, Low: 14.60, Close: 15.12
2019-08-13, Open: 15.00, High: 15.08, Low: 14.74, Close: 14.89
2019-08-14, Open: 15.14, High: 15.22, Low: 14.80, Close: 14.97
2019-08-15, Open: 14.64, High: 14.96, Low: 14.60, Close: 14.94
2019-08-16, Open: 15.09, High: 15.14, Low: 14.78, Close: 14.90
2019-08-19, Open: 14.91, High: 14.94, Low: 14.52, Close: 14.92
2019-08-19, BUY EXPIRED
下面結合輸出列印結果及圖形進行分析。
(1) 8月8日,收盤價高于15日均線,達到了買入條件,建立StopLimit買單,當日收盤價為14.38,設定較收盤價上漲突破0.5%後的觸發價格為14.45,較收盤價上漲0.2%後的限制價格為14.41,p.valid = 10,有效期valid至8月18日。若在截止日期前能夠滿足StopLimit的價格比對條件,訂單就會成交,否則訂單就會過期。
(2) 8月9日,收到買單送出通知。(8日建立的訂單,在9日收到訂單狀态通知,是因為notify_order方法會在Strategy的next方法前被調用,即在8日的next方法中建立了買單,在9日的notify_order方法中通知訂單被送出、接受。)
(3) 8月9日,收到買單接受通知。
(4) 8月9日,開盤價popen值14.55,高于觸發價格pcreated值14.45,訂單将以plimit = 14.41為限制價格,按照Limit訂單的邏輯被執行。
(5) 8月9日至8月19日(8月18日為周末,未開市),每日的最低點均高于限制價格14.41,未達到Limit訂單的買入條件,訂單未成交。
(6) 8月19日,收到訂單過期通知。8月18日為StopLimit訂單設定的截止日期valid,由于18日為周末,是以即使19日滿足了Limit訂單的價格比對條件,訂單也不會成交,會因為逾時而過期。
在例1中,訂單達到了觸發價格,随之被按照Limit訂單的邏輯進行價格比對,最終由于到截止日仍未滿足Limit訂單的成交條件而逾時。
例2:将圖表顯示時間範圍調整為2019年11月,輸出圖形如下:
部分輸出結果為:
2019-11-01, BUY CREATE, exectype StopLimit, close 16.86, price 16.94, pricelimit 16.89, valid: 2019-11-11
2019-11-04, ORDER SUBMITTED
2019-11-04, ORDER ACCEPTED
2019-11-04, Open: 16.98, High: 17.25, Low: 16.77, Close: 16.92
2019-11-04, BUY EXECUTED, Price: 16.89, Cost: 1689.37.
下面結合輸出列印結果及圖形進行分析。
(1) 11月1日,收盤價高于15日均線,達到了買入條件,建立StopLimit買單,當日收盤價為16.86,設定較收盤價上漲突破0.5%後的觸發價格為16.94,較收盤價上漲0.2%後的限制價格為16.89,p.valid = 10,有效期valid至11月11日。若在截止日期前能夠滿足StopLimit的價格比對條件,訂單就會成交,否則訂單就會過期。
(2) 11月4日(2日和3日為周末,未開市),收到買單送出通知。
(3) 11月4日,收到買單接受通知。
(4) 11月4日,開盤價popen值16.98,高于觸發價格pcreated值16.94,訂單将以plimit = 16.89為限制價格,按照Limit訂單的邏輯被執行。11月4日,開盤價popen值16.98,大于plimit值16.89,最低點plow值16.77,小于plimit值16.89,按照Limit訂單價格比對規則,将以plimit值16.89成交。
(5) 11月4日,訂單已plimit值16.89成交。
在例2中,訂單達到了觸發價格,随之被按照Limit訂單的邏輯進行價格比對,最終滿足Limit訂單的價格比對規則而成交。
2. 對于第2種條件分支情況:popen < pcreated & phigh >= pcreated & popen > pclose & plimit < pcreated & plimit >= pclose
首先,判斷phigh >= pcreated,那麼訂單就被觸發:
随後訂單将以plimit價格成交:
來看一個例子:
例3:将圖表顯示時間範圍調整為2018年3月,輸出圖形如下:
部分輸出結果為:
2018-03-08, BUY CREATE, exectype StopLimit, close 11.92, price 11.98, pricelimit 11.94, valid: 2018-03-18
2018-03-09, ORDER SUBMITTED
2018-03-09, ORDER ACCEPTED
2018-03-09, Open: 11.96, High: 12.01, Low: 11.79, Close: 11.90
2018-03-09, BUY EXECUTED, Price: 11.94, Cost: 1194.18.
下面結合輸出列印結果及圖形進行分析。
(1) 3月8日,收盤價高于15日均線,達到了買入條件,建立StopLimit買單,當日收盤價為11.92,設定較收盤價上漲突破0.5%後的觸發價格為11.98,較收盤價上漲0.2%後的限制價格為11.94,p.valid = 10,有效期valid至3月18日。若在截止日期前能夠滿足StopLimit的價格比對條件,訂單就會成交,否則訂單就會過期。
(2) 3月9日,收到買單送出通知。
(3) 3月9日,收到買單接受通知。
(4) 3月9日,開盤價popen值11.96,低于觸發價格pcreated值11.98,但最高點phigh值12.01,高于觸發價格pcreated值11.98,訂單被觸發,進入後續價格判斷。限制價格plimit值11.94小于觸發價格pcreated值11.98,并且限制價格plimit值11.94大于收盤價pclose值11.90,訂單将以plimit值11.94成交。
(5) 3月9日,訂單已plimit值11.94成交。
在例3中,訂單由于當日最高點高于觸發價格而被觸發,随後根據源代碼中的價格判斷規則,被以plimit立即成交。
3. 對于第3種條件分支情況:popen < pcreated & phigh >= pcreated & popen > pclose & plimit < pcreated & plimit >= pclose
根據源代碼可以看出,這種分支情況是不在所列的分支邏輯中的,來看一個例子。
例4:将圖表顯示時間範圍調整為2019年5月,輸出圖形如下:
部分輸出結果為:
2019-05-28, BUY CREATE, exectype StopLimit, close 12.49, price 12.55, pricelimit 12.51, valid: 2019-06-07
2019-05-29, ORDER SUBMITTED
2019-05-29, ORDER ACCEPTED
2019-05-29, Open: 12.36, High: 12.59, Low: 12.26, Close: 12.40
2019-05-30, Open: 12.32, High: 12.38, Low: 12.11, Close: 12.22
2019-05-30, BUY EXECUTED, Price: 12.32, Cost: 1232.00.
下面結合輸出列印結果及圖形進行分析。
(1) 5月28日,收盤價高于15日均線,達到了買入條件,建立StopLimit買單,當日收盤價為12.49,設定較收盤價上漲突破0.5%後的觸發價格為12.55,較收盤價上漲0.2%後的限制價格為12.51,p.valid = 10,有效期valid至6月7日。若在截止日期前能夠滿足StopLimit的價格比對條件,訂單就會成交,否則訂單就會過期。
(2) 5月29日,收到買單送出通知。
(3) 5月29日,收到買單接受通知。
(4) 5月29日,開盤價popen值12.36,低于觸發價格pcreated值12.55,但最高點phigh值12.59,高于觸發價格pcreated值12.55,訂單被觸發,進入後續價格判斷。由于popen(12.36) < pcreated(12.55) & phigh(12.59) >= pcreated(12.55) & popen(12.36) < pclose(12.40),此時,将進入源代碼中的最後一個分支:
else: # popen < pclose
if plimit >= pcreated:
p = self._slip_up(phigh, pcreated, lim=True)
self._execute(order, ago=0, price=p)
但是此時plimit(12.51) < pcreated(12.55),代碼中沒有分支來處理這個邏輯,是以将進入下一輪循環(下一日K線判斷)。
(5)5月30日,我們先轉到bbroker.py來看一下源代碼:
def _try_exec(self, order):
...
elif (order.triggered and
order.exectype in [Order.StopLimit, Order.StopTrailLimit]):
self._try_exec_limit(order, popen, phigh, plow, plimit)
elif order.exectype in [Order.Stop, Order.StopTrail]:
self._try_exec_stop(order, popen, phigh, plow, pcreated, pclose)
elif order.exectype in [Order.StopLimit, Order.StopTrailLimit]:
self._try_exec_stoplimit(order,
popen, phigh, plow, pclose,
pcreated, plimit)
通過源代碼可以看到,在5月30日處理價格比對邏輯時,并不會進入最後一個分支執行_try_exec_stoplimit方法,由于在5月29日時,已經将order.trigered置為True,是以将會以5月30日的K線來執行_try_exec_limit方法,也就是按照Limit訂單的邏輯來處理訂單。此時,開盤價Open值12.32小于限制價格plimit值12.51,按照Limit訂單的邏輯将以開盤價12.32成交。
(6) 5月30日,訂單已開盤價12.32成交。
在例4中,5月28日滿足了買單條件,5月29日訂單被送出并觸發,5月30日訂單成交。
再來回顧一下5月29日的情況,最高點12.59,高于觸發價格12.55,買單被觸發。按照StopLimit的設計邏輯,訂單被觸發後,将會以Limit訂單的邏輯進行執行。那麼,如果在最高點之後,出現小于等于限制價格plimit的話,那麼訂單應該就會成交。5月29日,最高點12.59,收盤價12.40,而限制價格plimit值為12.51,在最高點與收盤價之間,也就是說,plimit會出現在5月29日最高點之後,收盤之前,那麼訂單理應在5月29日成交。但是如輸出結果所示,由于源代碼邏輯中未包含相應的條件分支,訂單在5月30日才成交。
由于上述問題的存在,可以看到訂單成交推遲了一天,但是不會對回測造成過大影響。該問題已經在backtrader官方社群發帖詢問,推測是backtrader的bug,或者是未明确說明的交易邏輯考慮,目前尚未收到回複。待收到回複後,再對文章做出相應調整。
上面展示了StopLimit訂單做買入的情況,賣出的情況可以反過來對應展開,這裡就不做贅述了。如有疑問,可以參考Limit訂單和Stop訂單賣出的情況。
StopLimit訂單的用法可以總結為:突破關鍵點後以更優的價格開倉,跌破關鍵點後以更優的價格平倉。與Limit訂單相同,平倉時慎用StopLimit訂單。
為了便于互相交流學習,建立了微信群,感興趣的讀者請加微信。