因為世界上沒有任何事物是孤立存在的,購買資產的觸發因素很可能實際上是另一種資產。
使用不同的分析技術,可能已經在兩個不同的數據之間發現了相關性。
backtrader支持同時使用不同的數據源,因此它可以在大多數情況下用於此目的。
假設在以下公司之間發現了相關性:
Oracle
Yahoo
可以想像,當雅虎生意好的時候,它會從甲骨文購買更多的服務器、更多的數據庫和更專業的服務,從而推高股價。
因此,經過深入分析,制定了一項策略:
如果
Yahoo
的收盤價超過簡單移動平均線(第 15 期)購買
Oracle
退出倉位:
- 使用收盤價的向下交叉
訂單執行類型:
- 市場
總之,使用backtrader
進行設置需要什麼:
創建一個
cerebro
加載數據源 1 (Oracle) 並將其添加到cerebro
加載數據源 2 (Yahoo) 並將其添加到cerebro
加載我們設計的策略
策略細節:
在數據源 2 (Yahoo) 上創建一個簡單的移動平均線
使用 Yahoo 的收盤價和移動平均線創建 CrossOver 指標
然後如上所述在數據源 1 (Oracle) 上執行買/賣訂單。
下面的腳本使用以下默認值:
甲骨文(數據源 1)
雅虎(數據源 2)
現金:10000(系統默認)
股份:10股
佣金:每輪0.5%(表示為0.005)
期限:15個交易日
期間:2003、2004 和 2005
該腳本可以接受參數來修改上述設置,如幫助文本中所示:
$ ./multidata-strategy.py --help usage: multidata-strategy.py [-h] [--data0 DATA0] [--data1 DATA1] [--fromdate FROMDATE] [--todate TODATE] [--period PERIOD] [--cash CASH] [--commperc COMMPERC] [--stake STAKE] [--plot] [--numfigs NUMFIGS] MultiData Strategy optional arguments: -h, --help show this help message and exit --data0 DATA0, -d0 DATA0 1st data into the system --data1 DATA1, -d1 DATA1 2nd data into the system --fromdate FROMDATE, -f FROMDATE Starting date in YYYY-MM-DD format --todate TODATE, -t TODATE Starting date in YYYY-MM-DD format --period PERIOD Period to apply to the Simple Moving Average --cash CASH Starting Cash --commperc COMMPERC Percentage commission for operation (0.005 is 0.5% --stake STAKE Stake to apply in each operation --plot, -p Plot the read data --numfigs NUMFIGS, -n NUMFIGS Plot using numfigs figures
標準執行的結果:
$ ./multidata-strategy.py 2003-02-11T23:59:59+00:00, BUY CREATE , 9.14 2003-02-12T23:59:59+00:00, BUY COMPLETE, 11.14 2003-02-12T23:59:59+00:00, SELL CREATE , 9.09 2003-02-13T23:59:59+00:00, SELL COMPLETE, 10.90 2003-02-14T23:59:59+00:00, BUY CREATE , 9.45 2003-02-18T23:59:59+00:00, BUY COMPLETE, 11.22 2003-03-06T23:59:59+00:00, SELL CREATE , 9.72 2003-03-07T23:59:59+00:00, SELL COMPLETE, 10.32 ... ... 2005-12-22T23:59:59+00:00, BUY CREATE , 40.83 2005-12-23T23:59:59+00:00, BUY COMPLETE, 11.68 2005-12-23T23:59:59+00:00, SELL CREATE , 40.63 2005-12-27T23:59:59+00:00, SELL COMPLETE, 11.63 ================================================== Starting Value - 100000.00 Ending Value - 99959.26 ==================================================
經過兩年完整的戰略執行後:
- 損失了 40.74 個貨幣單位
雅虎和甲骨文之間的相關性就這麼多
視覺輸出(添加--plot
以生成圖表)
以及腳本(已添加到samples/multidata-strategy
目錄下的backtrader
的源代碼分發中。
from __future__ import (absolute_import, division, print_function, unicode_literals) import argparse import datetime # The above could be sent to an independent module import backtrader as bt import backtrader.feeds as btfeeds import backtrader.indicators as btind class MultiDataStrategy(bt.Strategy): ''' This strategy operates on 2 datas. The expectation is that the 2 datas are correlated and the 2nd data is used to generate signals on the 1st - Buy/Sell Operationss will be executed on the 1st data - The signals are generated using a Simple Moving Average on the 2nd data when the close price crosses upwwards/downwards The strategy is a long-only strategy ''' params = dict( period=15, stake=10, printout=True, ) def log(self, txt, dt=None): if self.p.printout: dt = dt or self.data.datetime[0] dt = bt.num2date(dt) print('%s, %s' % (dt.isoformat(), txt)) def notify_order(self, order): if order.status in [bt.Order.Submitted, bt.Order.Accepted]: return # Await further notifications if order.status == order.Completed: if order.isbuy(): buytxt = 'BUY COMPLETE, %.2f' % order.executed.price self.log(buytxt, order.executed.dt) else: selltxt = 'SELL COMPLETE, %.2f' % order.executed.price self.log(selltxt, order.executed.dt) elif order.status in [order.Expired, order.Canceled, order.Margin]: self.log('%s ,' % order.Status[order.status]) pass # Simply log # Allow new orders self.orderid = None def __init__(self): # To control operation entries self.orderid = None # Create SMA on 2nd data sma = btind.MovAv.SMA(self.data1, period=self.p.period) # Create a CrossOver Signal from close an moving average self.signal = btind.CrossOver(self.data1.close, sma) def next(self): if self.orderid: return # if an order is active, no new orders are allowed if not self.position: # not yet in market if self.signal > 0.0: # cross upwards self.log('BUY CREATE , %.2f' % self.data1.close[0]) self.buy(size=self.p.stake) else: # in the market if self.signal < 0.0: # crosss downwards self.log('SELL CREATE , %.2f' % self.data1.close[0]) self.sell(size=self.p.stake) def stop(self): print('==================================================') print('Starting Value - %.2f' % self.broker.startingcash) print('Ending Value - %.2f' % self.broker.getvalue()) print('==================================================') def runstrategy(): args = parse_args() # Create a cerebro cerebro = bt.Cerebro() # Get the dates from the args fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d') todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d') # Create the 1st data data0 = btfeeds.YahooFinanceCSVData( dataname=args.data0, fromdate=fromdate, todate=todate) # Add the 1st data to cerebro cerebro.adddata(data0) # Create the 2nd data data1 = btfeeds.YahooFinanceCSVData( dataname=args.data1, fromdate=fromdate, todate=todate) # Add the 2nd data to cerebro cerebro.adddata(data1) # Add the strategy cerebro.addstrategy(MultiDataStrategy, period=args.period, stake=args.stake) # Add the commission - only stocks like a for each operation cerebro.broker.setcash(args.cash) # Add the commission - only stocks like a for each operation cerebro.broker.setcommission(commission=args.commperc) # And run it cerebro.run() # Plot if requested if args.plot: cerebro.plot(numfigs=args.numfigs, volume=False, zdown=False) def parse_args(): parser = argparse.ArgumentParser(description='MultiData Strategy') parser.add_argument('--data0', '-d0', default='../../datas/orcl-1995-2014.txt', help='1st data into the system') parser.add_argument('--data1', '-d1', default='../../datas/yhoo-1996-2014.txt', help='2nd data into the system') parser.add_argument('--fromdate', '-f', default='2003-01-01', help='Starting date in YYYY-MM-DD format') parser.add_argument('--todate', '-t', default='2005-12-31', help='Starting date in YYYY-MM-DD format') parser.add_argument('--period', default=15, type=int, help='Period to apply to the Simple Moving Average') parser.add_argument('--cash', default=100000, type=int, help='Starting Cash') parser.add_argument('--commperc', default=0.005, type=float, help='Percentage commission for operation (0.005 is 0.5%%') parser.add_argument('--stake', default=10, type=int, help='Stake to apply in each operation') parser.add_argument('--plot', '-p', action='store_true', help='Plot the read data') parser.add_argument('--numfigs', '-n', default=1, help='Plot using numfigs figures') return parser.parse_args() if __name__ == '__main__': runstrategy()