在最新版本中,次要編號已從 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()