工單 #89 是關於針對資產添加基準測試的。明智的是,人們實際上可能有一個策略,即使積極,也低於簡單地跟蹤資產所能提供的策略。
backtrader 包括2種不同類型的物件,可以幫助跟蹤:
-
Observers
-
Analyzers
在 Analyzers 領域已經有一個TimeReturn 對象跟蹤整個投資組合價值(即:包括現金)的回報演變。
這顯然也是一個 Observer,所以在添加一些基準測試的同時,一些工作也使得能夠將 Observer 和分析儀插入在一起,這些工作旨在跟蹤同樣的事情。
注意
Observers和Analyzers之間的主要區別在於observerslines性質,它記錄了每個值,這使得它們適合
s 繪圖和即時查詢。這當然會消耗記憶體。
另一方面,Analyzers返回一組結果,get_analysis並且實現可能直到運行結束才提供任何結果。
Analyzers - 基準測試
標準TimeReturn 分析器已擴展為支援跟蹤 data feed。輸入的2個主要參數:
-
timeframe(預設值:None)如果None屆時將報告整個回溯測試期間的完整回報通過
TimeFrame.NoTimeFrame以考慮整個數據集,沒有時間限制 -
data(預設值:None)引用要跟蹤的資產,而不是投資組合價值。
注意
此數據必須已添加到具有
addata或resampledata的cerebro實例中replaydata
更多細節和參數: Analyzers 參考
因此,可以像這樣跟蹤porftolio每年的回報
import backtrader as bt
cerebro = bt.Cerebro()
cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.Years)
... # add datas, strategies ...
results = cerebro.run()
strat0 = results[0]
# If no name has been specified, the name is the class name lowercased
tret_analyzer = strat0.analyzers.getbyname('timereturn')
print(tret_analyzer.get_analysis())
如果我們想跟蹤數據的返回
import backtrader as bt
cerebro = bt.Cerebro()
data = bt.feeds.OneOfTheFeeds(dataname='abcde', ...)
cerebro.adddata(data)
cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.Years,
data=data)
... # add strategies ...
results = cerebro.run()
strat0 = results[0]
# If no name has been specified, the name is the class name lowercased
tret_analyzer = strat0.analyzers.getbyname('timereturn')
print(tret_analyzer.get_analysis())
如果要跟蹤兩者,最好是為 analyzers
import backtrader as bt
cerebro = bt.Cerebro()
data = bt.feeds.OneOfTheFeeds(dataname='abcde', ...)
cerebro.adddata(data)
cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.Years,
data=data, _name='datareturns')
cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.Years)
_name='timereturns')
... # add strategies ...
results = cerebro.run()
strat0 = results[0]
# If no name has been specified, the name is the class name lowercased
tret_analyzer = strat0.analyzers.getbyname('timereturns')
print(tret_analyzer.get_analysis())
tdata_analyzer = strat0.analyzers.getbyname('datareturns')
print(tdata_analyzer.get_analysis())
Observers - 基準測試
由於後台機制允許在Observers內部使用Analyzers,因此增加了2個新observers:
-
TimeReturn -
Benchmark
兩者都使用bt.analyzers.TimeReturn 分析器來收集結果。
與其像上面那樣使用代碼片段,不如運行一些完整的示例來顯示其功能。
觀察時間返回
執行:
$ ./observer-benchmark.py --plot --timereturn --timeframe notimeframe
請注意執行選項:
-
--timereturn告訴樣品這樣做 -
--timeframe notimeframe告訴分析器考慮整個數據集,而不考慮時間幀邊界。
last繪製的值為-0.26。
- 起始現金(從圖表中顯而易見)是
50K貨幣單位,策略以36,970貨幣單位結束,因此-26%價值遞減。
遵守基準測試
由於基準測試還將顯示時間返回結果,因此讓我們運行相同的操作,但基準測試處於活動狀態:
$ ./observer-benchmark.py --plot --timeframe notimeframe
嘿 嘿 嘿!!!
-
策略優於資產:
-0.26vs-0.33這不應該是一個值得慶祝的問題,但至少很明顯,戰略甚至沒有資產那麼糟糕。
每年向下移動以跟蹤事物:
$ ./observer-benchmark.py --plot --timeframe years
小心!
-
策略 last 值從
-0.26到-0.27 -
另一方面,資產顯示 last 值
-0.35(與上述相比-0.33)
價值如此 close 的原因是,從2005年到2006年,戰略和基準資產幾乎都處於2005年初的起始水準。
切換到較低的時間範圍(如周),整個情況會發生變化:
$ ./observer-benchmark.py --plot --timeframe weeks .. image:: 04-benchmarking-weeks.png
現在:
-
Benchmarkobserver顯示出更加緊張的一面。事情上下移動,因為現在weekly投資組合的回報和數據都被跟蹤 -
由於在一年last周沒有交易活躍,資產幾乎沒有移動,因此last顯示的值為0.00(last周之前的last收盤價,
25.54樣本數據收盤價為25.55,差異首先在小數點后4點感覺到)
觀察基準測試 - 另一個數據
該示例允許針對不同的數據進行基準測試。默認情況下,在使用--benchdata1時針對 Oracle 進行基準測試。考慮整個數據集:--timeframe notimeframe
$ ./observer-benchmark.py --plot --timeframe notimeframe --benchdata1
現在已經很清楚為什麼沒有理由在上面慶祝:
-
策略的結果沒有改變
notimeframe,並保持在-26%(-0.26) -
但是,當與另一個資料進行基準測試時,此數據在同一
+23%時期具有(0.23)
要麼策略需要改變,要麼交易另一種資產更好。
總結
現在有兩種方法,使用相同的基礎代碼/計算,來跟蹤時間返回和基準測試
- Observers (
TimeReturn與Benchmark)
和
- 分析儀(
TimeReturn帶TimeReturndata參數)
當然,基準測試並不能保證利潤,只是比較。
範例用法:
$ ./observer-benchmark.py --help
usage: observer-benchmark.py [-h] [--data0 DATA0] [--data1 DATA1]
[--benchdata1] [--fromdate FROMDATE]
[--todate TODATE] [--printout] [--cash CASH]
[--period PERIOD] [--stake STAKE] [--timereturn]
[--timeframe {months,days,notimeframe,years,None,weeks}]
[--plot [kwargs]]
Benchmark/TimeReturn Observers Sample
optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data0 to be read in (default:
../../datas/yhoo-1996-2015.txt)
--data1 DATA1 Data1 to be read in (default:
../../datas/orcl-1995-2014.txt)
--benchdata1 Benchmark against data1 (default: False)
--fromdate FROMDATE Starting date in YYYY-MM-DD format (default:
2005-01-01)
--todate TODATE Ending date in YYYY-MM-DD format (default: 2006-12-31)
--printout Print data lines (default: False)
--cash CASH Cash to start with (default: 50000)
--period PERIOD Period for the crossover moving average (default: 30)
--stake STAKE Stake to apply for the buy operations (default: 1000)
--timereturn Use TimeReturn observer instead of Benchmark (default:
None)
--timeframe {months,days,notimeframe,years,None,weeks}
TimeFrame to apply to the Observer (default: None)
--plot [kwargs], -p [kwargs]
Plot the read data applying any kwargs passed For
example: --plot style="candle" (to plot candles)
(default: None)
代碼
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import random
import backtrader as bt
class St(bt.Strategy):
params = (
('period', 10),
('printout', False),
('stake', 1000),
)
def __init__(self):
sma = bt.indicators.SMA(self.data, period=self.p.period)
self.crossover = bt.indicators.CrossOver(self.data, sma)
def start(self):
if self.p.printout:
txtfields = list()
txtfields.append('Len')
txtfields.append('Datetime')
txtfields.append('Open')
txtfields.append('High')
txtfields.append('Low')
txtfields.append('Close')
txtfields.append('Volume')
txtfields.append('OpenInterest')
print(','.join(txtfields))
def next(self):
if self.p.printout:
# Print only 1st data ... is just a check that things are running
txtfields = list()
txtfields.append('%04d' % len(self))
txtfields.append(self.data.datetime.datetime(0).isoformat())
txtfields.append('%.2f' % self.data0.open[0])
txtfields.append('%.2f' % self.data0.high[0])
txtfields.append('%.2f' % self.data0.low[0])
txtfields.append('%.2f' % self.data0.close[0])
txtfields.append('%.2f' % self.data0.volume[0])
txtfields.append('%.2f' % self.data0.openinterest[0])
print(','.join(txtfields))
if self.position:
if self.crossover < 0.0:
if self.p.printout:
print('CLOSE {} @%{}'.format(size,
self.data.close[0]))
self.close()
else:
if self.crossover > 0.0:
self.buy(size=self.p.stake)
if self.p.printout:
print('BUY {} @%{}'.format(self.p.stake,
self.data.close[0]))
TIMEFRAMES = {
None: None,
'days': bt.TimeFrame.Days,
'weeks': bt.TimeFrame.Weeks,
'months': bt.TimeFrame.Months,
'years': bt.TimeFrame.Years,
'notimeframe': bt.TimeFrame.NoTimeFrame,
}
def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()
cerebro.broker.set_cash(args.cash)
dkwargs = dict()
if args.fromdate:
fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
dkwargs['fromdate'] = fromdate
if args.todate:
todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
dkwargs['todate'] = todate
data0 = bt.feeds.YahooFinanceCSVData(dataname=args.data0, **dkwargs)
cerebro.adddata(data0, name='Data0')
cerebro.addstrategy(St,
period=args.period,
stake=args.stake,
printout=args.printout)
if args.timereturn:
cerebro.addobserver(bt.observers.TimeReturn,
timeframe=TIMEFRAMES[args.timeframe])
else:
benchdata = data0
if args.benchdata1:
data1 = bt.feeds.YahooFinanceCSVData(dataname=args.data1, **dkwargs)
cerebro.adddata(data1, name='Data1')
benchdata = data1
cerebro.addobserver(bt.observers.Benchmark,
data=benchdata,
timeframe=TIMEFRAMES[args.timeframe])
cerebro.run()
if args.plot:
pkwargs = dict()
if args.plot is not True: # evals to True but is not True
pkwargs = eval('dict(' + args.plot + ')') # args were passed
cerebro.plot(**pkwargs)
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Benchmark/TimeReturn Observers Sample')
parser.add_argument('--data0', required=False,
default='../../datas/yhoo-1996-2015.txt',
help='Data0 to be read in')
parser.add_argument('--data1', required=False,
default='../../datas/orcl-1995-2014.txt',
help='Data1 to be read in')
parser.add_argument('--benchdata1', required=False, action='store_true',
help=('Benchmark against data1'))
parser.add_argument('--fromdate', required=False,
default='2005-01-01',
help='Starting date in YYYY-MM-DD format')
parser.add_argument('--todate', required=False,
default='2006-12-31',
help='Ending date in YYYY-MM-DD format')
parser.add_argument('--printout', required=False, action='store_true',
help=('Print data lines'))
parser.add_argument('--cash', required=False, action='store',
type=float, default=50000,
help=('Cash to start with'))
parser.add_argument('--period', required=False, action='store',
type=int, default=30,
help=('Period for the crossover moving average'))
parser.add_argument('--stake', required=False, action='store',
type=int, default=1000,
help=('Stake to apply for the buy operations'))
parser.add_argument('--timereturn', required=False, action='store_true',
default=None,
help=('Use TimeReturn observer instead of Benchmark'))
parser.add_argument('--timeframe', required=False, action='store',
default=None, choices=TIMEFRAMES.keys(),
help=('TimeFrame to apply to the Observer'))
# Plot options
parser.add_argument('--plot', '-p', nargs='?', required=False,
metavar='kwargs', const=True,
help=('Plot the read data applying any kwargs passed\n'
'\n'
'For example:\n'
'\n'
' --plot style="candle" (to plot candles)\n'))
if pargs:
return parser.parse_args(pargs)
return parser.parse_args()
if __name__ == '__main__':
runstrat()