首先,讓我們用兩行總結反向交易方法的工作原理:
它就像一個帶有基本構建塊 (
Cerebro
) 的建築套件,可以插入許多不同的部件基本分佈包含許多部分,如指標、分析器、觀察器、 Sizer 、過濾器、數據饋送、經紀人、佣金/資產信息方案,...
可以從頭開始或基於現有的構建塊輕鬆構建新的構建塊
基本構建塊 (
Cerebro
) 已經做了一些自動“插入”,以便更輕鬆地使用框架,而不必擔心所有細節。
因此,該框架已預先配置為提供具有默認值的行為,例如:
- 使用單個/主要數據饋送
1-day
時間框架/壓縮組合- 10,000 單位貨幣
- 股票交易
這可能並不適合所有人,但重要的是:它可以根據每個交易者/程序員的個人需求進行定制
交易股票:整數
如上所述,默認配置是用於股票交易,當交易股票時,購買/出售完整股(即:1、2 ... 50 ... 1000,而不是像1.5
或1001.7589
股那樣的數量。
這意味著當用戶在默認配置中執行以下操作時:
def next(self): # Apply 50% of the portfolio to buy the main asset self.order_target_percent(target=0.5)
會發生以下情況:
系統計算需要多少股資產,使給定資產在投資組合中的價值盡可能接近
50%
但是因為默認配置是使用共享,所以生成的共享數將是一個整數,即:整數
筆記
請注意,默認配置是使用單個/主要數據提要,這就是為什麼在對order_percent_target
的調用中未指定實際數據的原因。在使用多個數據源進行操作時,必須指定要獲取/出售哪些數據(除非是指主要數據)
交易加密貨幣:分數
很明顯,在交易加密貨幣時,即使是小數點後 20 位,也可以買到“半個比特幣”。
好消息是人們實際上可以更改與資產有關的信息。這是通過CommissionInfo
系列可插拔部件實現的。
一些文檔: Docs - Commission Schemes - https://www. backtrader .com/docu/commission-schemes/commission-schemes/
筆記
不得不承認,這個名字是不幸的,因為這些計劃不僅包含有關佣金的信息,還包含其他信息。
在分數場景中,感興趣的是該方案的這種方法: getsize(price, cash)
,它具有以下文檔字符串
Returns the needed size to meet a cash operation at a given price
這些方案與代理密切相關,通過代理API,可以將方案添加到系統中。
代理文檔位於: Docs - Broker - https://www。 backtrader .com/docu/broker/
相關的方法是: addcommissioninfo(comminfo, name=None)
。除了添加適用於所有資產的方案(當name
為None
時)之外,還可以設置僅適用於具有特定名稱的資產的方案。
實施分數方案
這可以通過擴展名為CommissionInfo
的現有基礎方案輕鬆實現。
class CommInfoFractional(bt.CommissionInfo): def getsize(self, price, cash): '''Returns fractional size for cash operation @price''' return self.p.leverage * (cash / price)
同上並完成。繼承CommissionInfo
並編寫一行方法,目的就達到了。因為原始方案定義支持leverage
,所以在計算中考慮到了這一點,以防萬一可以用槓桿購買加密貨幣(默認值為1.0
,即:無槓桿)
稍後在代碼中,將像這樣添加方案(通過命令行參數控制)
if args.fractional: # use the fractional scheme if requested cerebro.broker.addcommissioninfo(CommInfoFractional())
即:添加子類方案的實例(注意()
實例化)。如上所述, name
參數未設置,這意味著它將應用於系統中的所有資產。
測試野獸
下面提供了實現多頭/空頭頭寸的簡單移動平均線交叉的完整腳本,可以直接在 shell 中使用。測試的默認數據饋送是來自backtrader存儲庫的數據饋送之一。
整數運行:沒有分數 - 沒有樂趣
$ ./fractional-sizes.py --plot 2005-02-14,3079.93,3083.38,3065.27,3075.76,0.00 2005-02-15,3075.20,3091.64,3071.08,3086.95,0.00 ... 2005-03-21,3052.39,3059.18,3037.80,3038.14,0.00 2005-03-21,Enter Short 2005-03-22,Sell Order Completed - Size: -16 @Price: 3040.55 Value: -48648.80 Comm: 0.00 2005-03-22,Trade Opened - Size -16 @Price 3040.55 2005-03-22,3040.55,3053.18,3021.66,3050.44,0.00 ...
一個規模為16
單位的空頭交易已開啟。整個日誌(由於明顯原因未顯示)包含許多其他操作,所有操作都包含完整大小的交易。
分數運行
在對分數進行硬子類化和單線工作之後......
$ ./fractional-sizes.py --fractional --plot 2005-02-14,3079.93,3083.38,3065.27,3075.76,0.00 2005-02-15,3075.20,3091.64,3071.08,3086.95,0.00 ... 2005-03-21,3052.39,3059.18,3037.80,3038.14,0.00 2005-03-21,Enter Short 2005-03-22,Sell Order Completed - Size: -16.457437774427774 @Price: 3040.55 Value: -50039.66 Comm: 0.00 2005-03-22,Trade Opened - Size -16.457437774427774 @Price 3040.55 2005-03-22,3040.55,3053.18,3021.66,3050.44,0.00 ...
V
代表勝利。空頭交易以相同的交叉開盤,但這次的小數大小為-16.457437774427774
請注意,圖表中的最終投資組合價值不同,這是因為實際交易規模不同。
是的,反向交易者可以。使用可插入/可擴展的構建工具包方法,可以很容易地根據交易者程序員的特定需求定制行為。
#!/usr/bin/env python # -*- coding: utf-8; py-indent-offset:4 -*- ############################################################################### # Copyright (C) 2019 Daniel Rodriguez - MIT License # - https://opensource.org/licenses/MIT # - https://en.wikipedia.org/wiki/MIT_License ############################################################################### import argparse import logging import sys import backtrader as bt # This defines not only the commission info, but some other aspects # of a given data asset like the "getsize" information from below # params = dict(stocklike=True) # No margin, no multiplier class CommInfoFractional(bt.CommissionInfo): def getsize(self, price, cash): '''Returns fractional size for cash operation @price''' return self.p.leverage * (cash / price) class St(bt.Strategy): params = dict( p1=10, p2=30, # periods for crossover ma=bt.ind.SMA, # moving average to use target=0.5, # percentage of value to use ) def __init__(self): ma1, ma2 = [self.p.ma(period=p) for p in (self.p.p1, self.p.p2)] self.cross = bt.ind.CrossOver(ma1, ma2) def next(self): self.logdata() if self.cross > 0: self.loginfo('Enter Long') self.order_target_percent(target=self.p.target) elif self.cross < 0: self.loginfo('Enter Short') self.order_target_percent(target=-self.p.target) def notify_trade(self, trade): if trade.justopened: self.loginfo('Trade Opened - Size {} @Price {}', trade.size, trade.price) elif trade.isclosed: self.loginfo('Trade Closed - Profit {}', trade.pnlcomm) else: # trade updated self.loginfo('Trade Updated - Size {} @Price {}', trade.size, trade.price) def notify_order(self, order): if order.alive(): return otypetxt = 'Buy ' if order.isbuy() else 'Sell' if order.status == order.Completed: self.loginfo( ('{} Order Completed - ' 'Size: {} @Price: {} ' 'Value: {:.2f} Comm: {:.2f}'), otypetxt, order.executed.size, order.executed.price, order.executed.value, order.executed.comm ) else: self.loginfo('{} Order rejected', otypetxt) def loginfo(self, txt, *args): out = [self.datetime.date().isoformat(), txt.format(*args)] logging.info(','.join(out)) def logerror(self, txt, *args): out = [self.datetime.date().isoformat(), txt.format(*args)] logging.error(','.join(out)) def logdebug(self, txt, *args): out = [self.datetime.date().isoformat(), txt.format(*args)] logging.debug(','.join(out)) def logdata(self): txt = [] txt += ['{:.2f}'.format(self.data.open[0])] txt += ['{:.2f}'.format(self.data.high[0])] txt += ['{:.2f}'.format(self.data.low[0])] txt += ['{:.2f}'.format(self.data.close[0])] txt += ['{:.2f}'.format(self.data.volume[0])] self.loginfo(','.join(txt)) def run(args=None): args = parse_args(args) cerebro = bt.Cerebro() data = bt.feeds.BacktraderCSVData(dataname=args.data) cerebro.adddata(data) # create and add data feed cerebro.addstrategy(St) # add the strategy cerebro.broker.set_cash(args.cash) # set broker cash if args.fractional: # use the fractional scheme if requested cerebro.broker.addcommissioninfo(CommInfoFractional()) cerebro.run() # execute if args.plot: # Plot if requested to cerebro.plot(**eval('dict(' + args.plot + ')')) def logconfig(pargs): if pargs.quiet: verbose_level = logging.ERROR else: verbose_level = logging.INFO - pargs.verbose * 10 # -> DEBUG logger = logging.getLogger() for h in logger.handlers: # Remove all loggers from root logger.removeHandler(h) stream = sys.stdout if not pargs.stderr else sys.stderr # choose stream logging.basicConfig( stream=stream, format="%(message)s", # format="%(levelname)s: %(message)s", level=verbose_level, ) def parse_args(pargs=None): parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, description='Fractional Sizes with CommInfo', ) pgroup = parser.add_argument_group('Data Options') parser.add_argument('--data', default='../../datas/2005-2006-day-001.txt', help='Data to read in') pgroup = parser.add_argument_group(title='Broker Arguments') pgroup.add_argument('--cash', default=100000.0, type=float, help='Starting cash to use') pgroup.add_argument('--fractional', action='store_true', help='Use fractional commission info') pgroup = parser.add_argument_group(title='Plotting Arguments') pgroup.add_argument('--plot', default='', nargs='?', const='{}', metavar='kwargs', help='kwargs: "k1=v1,k2=v2,..."') pgroup = parser.add_argument_group('Verbosity Options') pgroup.add_argument('--stderr', action='store_true', help='Log to stderr, else to stdout') pgroup = pgroup.add_mutually_exclusive_group() pgroup.add_argument('--quiet', '-q', action='store_true', help='Silent (errors will be reported)') pgroup.add_argument('--verbose', '-v', action='store_true', help='Increase verbosity level') # Parse and process some args pargs = parser.parse_args(pargs) logconfig(pargs) # config logging return pargs if __name__ == '__main__': run()