查看賬戶情況:
class TestStrategy(bt.Strategy):def next(self):print('當前可用資金', self.broker.getcash())print('當前總資產', self.broker.getvalue())print('當前持倉量', self.broker.getposition(self.data).size)print('當前持倉成本', self.broker.getposition(self.data).price)# 也可以直接獲取持倉print('當前持倉量', self.getposition(self.data).size)print('當前持倉成本', self.getposition(self.data).price)# 注:getposition() 需要指定具體的標的數據集
滑點設置:
# 方式1:通過 BackBroker 類中的 slip_perc 參數設置百分比滑點
cerebro.broker = bt.brokers.BackBroker(slip_perc=0.0001)
# 方式2:通過調用 brokers 的 set_slippage_perc 方法設置百分比滑點
cerebro.broker.set_slippage_perc(perc=0.0001)# 方式1:通過 BackBroker 類中的 slip_fixed 參數設置固定滑點
cerebro.broker = bt.brokers.BackBroker(slip_fixed=0.001)
# 方式2:通過調用 brokers 的 set_slippage_fixed 方法設置固定滑點
cerebro.broker = cerebro.broker.set_slippage_fixed(fixed=0.001)
參數說明:
有關滑點的其他設置
slip_open:是否對開盤價做滑點處理,該參數在 BackBroker() 類中默認為 False,在 set_slippage_perc 和set_slippage_fixed 方法中默認為 True;
slip_match:是否將滑點處理后的新成交價與成交當天的價格區間 low ~ high 做匹配,如果為 True,則根據新成交價重新匹配調整價格區間,確保訂單能被執行;如果為 False,則不會與價格區間做匹配,訂單不會執行,但會在下一日執行一個空訂單;默認取值為 True;
slip_out:如果新成交價高于最高價或低于最高價,是否以超出的價格成交,如果為 True,則允許以超出的價格成交;如果為 False,實際成交價將被限制在價格區間內 ?low ~ high;默認取值為 False;
slip_limit:是否對限價單執行滑點,如果為 True,即使 slip_match 為Fasle,也會對價格做匹配,確保訂單被執行;如果為 False,則不做價格匹配;默認取值為 True。
# 情況1:
set_slippage_fixed(fixed=0.35,slip_open=False,slip_match=True,slip_out=False)
# 由于 slip_open=False ,不會對開盤價做滑點處理,所以仍然以原始開盤價 32.63307367 成交# 情況2:
set_slippage_fixed(fixed=0.35,slip_open=True,slip_match=True,slip_out=False)# 情況3:
set_slippage_fixed(fixed=0.35,slip_open=True,slip_match=True,slip_out=True)
# 滑點調整的新成交價為 32.63307367+0.35 = 32.98307367,超出了當天最高價 32.94151482
# 允許做價格匹配 slip_match=True, 而且運行以超出價格區間的新成交價執行 slip_out=True
# 最終以新成交價 32.98307367 成交# 情況4:
set_slippage_fixed(fixed=0.35,slip_open=True,slip_match=False,slip_out=True)
# 滑點調整的新成交價為 32.63307367+0.35 = 32.98307367,超出了當天最高價 32.94151482
# 由于不進行價格匹配 slip_match=False,新成交價超出價格區間無法成交
# 2019-01-17 這一天訂單不會執行,但會在下一日 2019-01-18 執行一個空訂單
# 再往后的 2019-07-02,也未執行訂單,下一日 2019-07-03 執行空訂單
# 即使 2019-07-03的 open 39.96627412+0.35 < high 42.0866713 滿足成交條件,也不會補充成交
交易稅費管理
股票:目前 A 股的交易費用分為 2 部分:傭金和印花稅,
其中傭金雙邊征收,不同證券公司收取的傭金各不相同,一般在 0.02%-0.03% 左右,單筆傭金不少于 5 元;
印花稅只在賣出時收取,稅率為 0.1%。
期貨:期貨交易費用包括交易所收取手續費和期貨公司收取傭金 2 部分,交易所手續費較為固定,
不同期貨公司傭金不一致,而且不同期貨品種的收取方式不相同,有的按照固定費用收取,有的按成交金額的固定百分比收取:
合約現價*合約乘數*手續費費率。除了交易費用外,期貨交易時還需上交一定比例的保證金 。
Backtrader 也提供了多種交易費設置方式,既可以簡單的通過參數進行設置,也可以結合交易條件自定義費用函數:
根據交易品種的不同,Backtrader 將交易費用分為 股票 Stock-like 模式和期貨 Futures-like 種模式;
根據計算方式的不同,Backtrader 將交易費用分為 PERC 百分比費用模式 和 FIXED 固定費用模式 ;
Stock-like 模式與 PERC 百分比費用模式對應,期貨 Futures-like 與 FIXED 固定費用模式對應;
在設置交易費用時,最常涉及如下 3 個參數:
commission:手續費 / 傭金;
mult:乘數;
margin:保證金 / 保證金比率 。
雙邊征收:買入和賣出操作都要收取相同的交易費用 。
cerebro.broker.setcommission(# 交易手續費,根據margin取值情況區分是百分比手續費還是固定手續費commission=0.0,# 期貨保證金,決定著交易費用的類型,只有在stocklike=False時起作用margin=None,# 乘數,盈虧會按該乘數進行放大mult=1.0,# 交易費用計算方式,取值有:# 1.CommInfoBase.COMM_PERC 百分比費用# 2.CommInfoBase.COMM_FIXED 固定費用# 3.None 根據 margin 取值來確定類型commtype=None,# 當交易費用處于百分比模式下時,commission 是否為 % 形式# True,表示不以 % 為單位,0.XX 形式;False,表示以 % 為單位,XX% 形式percabs=True,# 是否為股票模式,該模式通常由margin和commtype參數決定# margin=None或COMM_PERC模式時,就會stocklike=True,對應股票手續費;# margin設置了取值或COMM_FIXED模式時,就會stocklike=False,對應期貨手續費stocklike=False,# 計算持有的空頭頭寸的年化利息# days * price * abs(size) * (interest / 365)interest=0.0,# 計算持有的多頭頭寸的年化利息interest_long=False,# 杠桿比率,交易時按該杠桿調整所需現金leverage=1.0,# 自動計算保證金# 如果False,則通過margin參數確定保證金# 如果automargin<0,通過mult*price確定保證金# 如果automargin>0,如果automargin*price確定保證金automargin=False,# 交易費用設置作用的數據集(也就是作用的標的)# 如果取值為None,則默認作用于所有數據集(也就是作用于所有assets)name=None)
從上述各參數的含義和作用可知,margin 、commtype、stocklike 存在 2 種默認的配置規則:股票百分比費用、期貨固定費用,具體如下:
第 1 條規則:未設置 margin(即 margin 為 0 / None / False)→ commtype 會指向 COMM_PERC 百分比費用 → 底層的 _stocklike 屬性會設置為 True → 對應的是“股票百分比費用”。
所以如果想為股票設置交易費用,就令 margin = 0 / None / False,或者令 stocklike=True;
第 2 條規則:為 margin 設置了取值 → ? commtype 會指向 COMM_FIXED 固定費用 → 底層的 _stocklike 屬性會設置為 False → 對應的是“期貨固定費用”,因為只有期貨才會涉及保證金。
所以如果想為期貨設置交易費用,就需要設置 margin,此外還需令 stocklike=True,margin 參數才會起作用 。
自定義交易費用的例子
# 自定義期貨百分比費用
class CommInfo_Fut_Perc_Mult(bt.CommInfoBase):params = (('stocklike', False), # 指定為期貨模式('commtype', bt.CommInfoBase.COMM_PERC), # 使用百分比費用('percabs', False), # commission 以 % 為單位)def _getcommission(self, size, price, pseudoexec):# 計算交易費用return (abs(size) * price) * (self.p.commission/100) * self.p.mult# pseudoexec 用于提示當前是否在真實統計交易費用# 如果只是試算費用,pseudoexec=False# 如果是真實的統計費用,pseudoexec=Truecomminfo = CommInfo_Fut_Perc_Mult(commission=0.1, # 0.1%mult=10,margin=2000) # 實例化
cerebro.broker.addcommissioninfo(comminfo)# 上述自定義函數,也可以通過 setcommission 來實現
cerebro.broker.setcommission(commission=0.1, #0.1%mult=10,margin=2000,percabs=False,commtype=bt.CommInfoBase.COMM_PERC,stocklike=False)
下面是考慮傭金和印花稅的股票百分比費用:
class StockCommission(bt.CommInfoBase):params = (('stocklike', True), # 指定為期貨模式('commtype', bt.CommInfoBase.COMM_PERC), # 使用百分比費用模式('percabs', True), # commission 不以 % 為單位('stamp_duty', 0.001),) # 印花稅默認為 0.1%# 自定義費用計算公式def _getcommission(self, size, price, pseudoexec):if size > 0: # 買入時,只考慮傭金return abs(size) * price * self.p.commissionelif size < 0: # 賣出時,同時考慮傭金和印花稅return abs(size) * price * (self.p.commission + self.p.stamp_duty)else:return 0
成交量限制管理
形式1:bt.broker.fillers.FixedSize(size)?
通過 FixedSize() 方法設置最大的固定成交量:size,該種模式下的成交量限制規則如下:
訂單實際成交量的確定規則:取(size、訂單執行那天的 volume 、訂單中要求的成交數量)中的最小者;
訂單執行那天,如果訂單中要求的成交數量無法全部滿足,則只成交部分數量。第二天不會補單。
# 通過 BackBroker() 類直接設置
cerebro = Cerebro()
filler = bt.broker.fillers.FixedSize(size=xxx)
newbroker = bt.broker.BrokerBack(filler=filler)
cerebro.broker = newbroker# 通過 set_filler 方法設置
cerebro = Cerebro()
cerebro.broker.set_filler(bt.broker.fillers.FixedSize(size=xxx))# self.order = self.buy(size=2000) # 每次買入 2000 股
# cerebro.broker.set_filler(bt.broker.fillers.FixedSize(size=3000)) # 固定最大成交量
形式2:bt.broker.fillers.FixedBarPerc(perc)
通過 FixedBarPerc(perc) 將 訂單執行當天 bar 的總成交量 volume 的 perc % 設置為最大的固定成交量,該模式的成交量限制規則如下:
訂單實際成交量的確定規則:取 (volume * perc /100、訂單中要求的成交數量) 的 最小者;
訂單執行那天,如果訂單中要求的成交數量無法全部滿足,則只成交部分數量。
# 通過 BackBroker() 類直接設置
cerebro = Cerebro()
filler = bt.broker.fillers.FixedBarPerc(perc=xxx)
newbroker = bt.broker.BrokerBack(filler=filler)
cerebro.broker = newbroker# 通過 set_filler 方法設置
cerebro = Cerebro()
cerebro.broker.set_filler(bt.broker.fillers.FixedBarPerc(perc=xxx))
# perc 以 % 為單位,取值范圍為[0.0,100.0]# self.order = self.buy(size=2000) # 以下一日開盤價買入2000股
# cerebro.broker.set_filler(bt.broker.fillers.FixedBarPerc(perc=50))
形式3:bt.broker.fillers.BarPointPerc(minmov=0.01,perc=100.0)
BarPointPerc() 在考慮了價格區間的基礎上確定成交量,在訂單執行當天,成交量確定規則為:
通過 minmov 將 當天 bar 的價格區間 low ~ high 進行均勻劃分,得到劃分的份數:
part = ?(high -low +minmov) ?// minmov ?(向下取整)
再對當天 bar 的總成交量 volume 也劃分成相同的份數 part ,這樣就能得到每份的平均成交量:
volume_per = volume // part?
最終,volume_per * (perc / 100)就是允許的最大成交量,實際成交時,對比訂單中要求的成交量,就可以得到最終實際成交量
實際成交量 = min ( volume_per * (perc / 100), 訂單中要求的成交數量 )
# 通過 BackBroker() 類直接設置
cerebro = Cerebro()
filler = bt.broker.fillers.BarPointPerc(minmov=0.01,perc=100.0)
newbroker = bt.broker.BrokerBack(filler=filler)
cerebro.broker = newbroker# 通過 set_filler 方法設置
cerebro = Cerebro()
cerebro.broker.set_filler(bt.broker.fillers.BarPointPerc(minmov=0.01,perc=100.0))
# perc 以 % 為單位,取值范圍為[0.0,100.0]# self.order = self.buy(size=2000) # 以下一日開盤價買入2000股# cerebro.broker.set_filler(bt.broker.fillers.BarPointPerc(minmov=0.1, perc=50)) # 表示 50%
交易時機管理
對于交易訂單生成和執行時間,Backtrader 默認是 “當日收盤后下單,次日以開盤價成交”,這種模式在回測過程中能有效避免使用未來數據。
但對于一些特殊的交易場景,比如“all_in”情況下,當日所下訂單中的數量是用當日收盤價計算的(總資金 / 當日收盤價),次日以開盤價執行訂單時,
如果開盤價比昨天的收盤價提高了,就會出現可用資金不足的情況。
為了應對一些特殊交易場景,Backtrader 還提供了一些 cheating 式的交易時機模式:Cheat-On-Open 和 Cheat-On-Close。
Cheat-On-Open
Cheat-On-Open 是“當日下單,當日以開盤價成交”模式,在該模式下,Strategy 中的交易邏輯不再寫在 next() 方法里,而是寫在特定的 next_open()、nextstart_open() 、prenext_open() 函數中,具體設置可參考如下案例:
方式1:bt.Cerebro(cheat_on_open=True);
方式2:cerebro.broker.set_coo(True);
方式3:BackBroker(coo=True)。
Cheat-On-Close
Cheat-On-Close 是“當日下單,當日以收盤價成交”模式,在該模式下,Strategy 中的交易邏輯仍寫在 next() 中,具體設置如下:
方式1:cerebro.broker.set_coc(True);
方式2:BackBroker(coc=True)
class TestStrategy(bt.Strategy):......def next(self):# 取消之前未執行的訂單if self.order:self.cancel(self.order)# 檢查是否有持倉if not self.position:# 10日均線上穿5日均線,買入if self.crossover > 0:print('{} Send Buy, open {}'.format(self.data.datetime.date(),self.data.open[0]))self.order = self.buy(size=100) # 以下一日開盤價買入100股# # 10日均線下穿5日均線,賣出elif self.crossover < 0:self.order = self.close() # 平倉,以下一日開盤價賣出......# 實例化大腦
cerebro= bt.Cerebro()
.......
# 當日下單,當日收盤價成交
cerebro.broker.set_coc(True)