在最新版本中,次要编号已从 8 移至 9,以指示即使已考虑兼容性,也可能会对行为产生一些影响。
在 1.9.0.99 版中,使用 datetime 同步多个数据的整个机制已经重新设计(适用于下一个和一次模式)。
注意
所有标准测试用例都可以从nosetests
中获得很好的OK,但是复杂的用例可能会发现未涵盖的角落案例。
之前的行为在工单 #39、 #76、 #115 和 #129 中讨论过,这是弃用旧行为的基础。
现在,检查传入价格的日期时间时间戳以对齐数据并提供新功能(首先是旧柱)。好处:
-
现在可以使用非时间对齐数据。
-
在即时源中,行为得到改善,因为重新同步会自动
让我们回想一下,旧行为使用系统中引入的第 1个 数据作为时间同步的主数据,没有其他数据可以更快。现在,在系统中引入数据的顺序不起作用。
部分返工解决了绘图问题,这天真地假设所有数据最终具有相同的长度,这是拥有时间母版的结果。新的绘图代码允许不同长度的数据。
注意
旧行为仍可通过使用:
cerebro = bt.Cerebro(oldsync=True)
或:
cerebro.run(oldsync=True)
用样品看
该multidata-strategy
范例已用作范例的基础 multidata-strategy-unaligned
(位于同一文件夹中)。已手动更改了两个数据样本以删除一些柱。两者都有 756
柱线,并且 753
被限制在两个不同的时间点
-
2004年底、2005年初
YHOO
-
2005年底
ORCL
与往常一样,一次运行胜过千言万语。
首先是旧行为
运行:
$ ./multidata-strategy-unaligned.py --oldsync --plot
从输出来看,重要的部分就在最后:
... Self len: 753 Data0 len: 753 Data1 len: 750 Data0 len == Data1 len: False Data0 dt: 2005-12-27 23:59:59 Data1 dt: 2005-12-27 23:59:59 ...
要注意:
-
该策略的长度为
753
-
第1个 数据(时间主站)也有
753
-
第2个 数据(时间从站)具有
750
从输出中看不出来,YHOO
但档包含的数据直到 2005-12-30
,系统未对其进行处理。
可视化图表
新行为
运行:
$ ./multidata-strategy-unaligned.py --plot
从输出来看,重要的部分就在最后:
... Self len: 756 Data0 len: 753 Data1 len: 753 Data0 len == Data1 len: True Data0 dt: 2005-12-27 23:59:59 Data1 dt: 2005-12-30 23:59:59 ...
行为得到了改善:
-
该策略将
756
每个数据的长度转换为完整的753
数据点。 -
由于删除的数据点不重叠,因此策略最终成为
3
比数据更长的单位。 -
2005-12-30
已到达data1
(它是删除data0
的数据点之一),因此所有数据都已处理到最后
可视化图表
虽然图表没有表现出重大差异,但它们实际上是在幕后不同的。
另一个检查
对于感兴趣的用户,data-multitimeframe
示例已更新为也支持 --oldsync
参数。由于现在正在绘制不同长度的数据,因此较大时间范围的视觉方面更好。
使用新的同步模型运行
使用旧的同步模型运行
示例用法
$ ./multidata-strategy-unaligned.py --help usage: multidata-strategy-unaligned.py [-h] [--data0 DATA0] [--data1 DATA1] [--fromdate FROMDATE] [--todate TODATE] [--period PERIOD] [--cash CASH] [--runnext] [--nopreload] [--oldsync] [--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 --runnext Use next by next instead of runonce --nopreload Do not preload the data --oldsync Use old data synchronization method --commperc COMMPERC Percentage commission (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
示例代码
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 self.p.printout: print('Self len:', len(self)) print('Data0 len:', len(self.data0)) print('Data1 len:', len(self.data1)) print('Data0 len == Data1 len:', len(self.data0) == len(self.data1)) print('Data0 dt:', self.data0.datetime.datetime()) print('Data1 dt:', self.data1.datetime.datetime()) 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(runonce=not args.runnext, preload=not args.nopreload, oldsync=args.oldsync) # 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-2003-2005.txt', help='1st data into the system') parser.add_argument('--data1', '-d1', default='../../datas/yhoo-2003-2005.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('--runnext', action='store_true', help='Use next by next instead of runonce') parser.add_argument('--nopreload', action='store_true', help='Do not preload the data') parser.add_argument('--oldsync', action='store_true', help='Use old data synchronization method') parser.add_argument('--commperc', default=0.005, type=float, help='Percentage commission (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()