数据同步返工
原文: https://www.backtrader.com/blog/posts/2016-09-17-data-synchronization/data-synchronization/
在最新版本中,次要编号已从 8 移到 9,以表示可能会产生某些行为影响的更改,即使考虑到兼容性。
在 1.9.0.99 版中,使用日期时间同步多个数据的整个机制已经重新设计(对于下一个和一次模式)。
笔记
所有标准测试用例都从nosetests得到了很好的确认,但复杂的用例可能会发现未涵盖的角落用例。
之前的行为在罚单【39】【76】【115】和【129】中进行了讨论,这是反对旧行为的基础。
现在,检查传入价格的日期时间时间戳,以对齐数据并交付新内容(首先是旧条)。好处:
- 
现在可以使用非时间对齐的数据。 
- 
在 live Feed 中,由于自动重新同步,行为得到改善 
让我们回忆一下,旧的行为使用系统中引入的 1st数据作为时间同步的主数据,没有其他数据可以更快。系统中引入数据的顺序现在不起作用。
部分返工处理了绘图问题,天真地认为所有数据最终都具有相同的长度,这是拥有时间主机的结果。新的绘图代码允许不同长度的数据。
笔记
通过使用以下命令,旧行为仍然可用:
cerebro = bt.Cerebro(oldsync=True) 
或:
cerebro.run(oldsync=True) 
看一个样本
multidata-strategy样本已作为multidata-strategy-unaligned样本的基础(在同一文件夹中)。两个数据样本已手动更改,以删除一些条形图。两者都有756条,并在两个不同的时间点被限制为753
- 
2004 年底,2005 年初 YHOO
- 
ORCL的 2005 年底
一如往常,一次处决抵得上千言万语。
首先是旧的行为
执行:
$ ./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
- 
1st数据(时间主机)也有 753
- 
2nd数据(时间从机)有 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() 





