原文:
www.backtrader.com/
基准测试
原文:
www.backtrader.com/blog/posts/2016-07-22-benchmarking/benchmarking/
backtrader包括两种不同类型的对象,可以帮助跟踪:
-
观察者
-
分析器
问题 #89是关于添加针对资产的基准测试。这是合理的,因为一个人可能实际上有一种策略,即使是正的,也低于简单跟踪资产所能提供的回报。
在分析器领域中,已经有一个TimeReturn
对象,用于跟踪整个投资组合价值的回报演变(即:包括现金)
这显然也可以是一个观察者,因此在添加一些基准测试的同时,还进行了一些工作,以便能够将观察者和分析器组合在一起,这两者都旨在跟踪相同的内容。
注意
观察者和分析器之间的主要区别是观察者的线性质,记录每��值并使其适合绘图和实时查询。当然,这会消耗内存。
另一方面,分析器通过get_analysis
返回一组结果,实现可能直到运行结束才提供任何结果。
分析器 - 基准测试
标准TimeReturn
分析器已扩展以支持跟踪数据源。涉及的两个主要参数:
-
timeframe
(默认:None
)如果为None
,则将报告整个回测期间的完整回报传递
TimeFrame.NoTimeFrame
以考虑整个数据集而没有时间限制 -
data
(默认:None
)用于跟踪而不是投资组合价值的参考资产。
注意
这些数据必须已经通过
addata
、resampledata
或replaydata
添加到cerebro
实例中
(有关更多详细信息和参数,请参阅文档中的参考资料)
因此,可以像这样跟踪投资组合的年度回报:
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())
如果两者都要被跟踪,最好给分析器分配名称:
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())
观察者 - 基准测试
由于背景机制允许在观察者内部使用分析器,因此添加了 2 个新的观察者:
-
TimeReturn
-
基准
两者都使用bt.analyzers.TimeReturn
分析器来收集结果。
而不是像上面那样有代码片段,一个完整的示例带有一些运行来展示它们的功能。
观察 TimeReturn
执行:
$ ./observer-benchmark.py --plot --timereturn --timeframe notimeframe
输出。
注意执行选项:
-
--timereturn
:我们告诉示例就是这样做的 -
--timeframe notimeframe
:告诉分析器考虑整个数据集,而不考虑时间范围边界。
最后绘制的数值为-0.26
。
- 起始现金(从图表中明显)为
50,000
货币单位,策略最终以36,970
货币单位结束,因此价值下降了-26%
。
观察基准测试
因为基准也会显示时间回报率的结果,让我们运行相同的内容,但激活基准:
$ ./observer-benchmark.py --plot --timeframe notimeframe
输出。
嘿,嘿,嘿!!!
-
策略比资产更好:
-0.26
对-0.33
这不应该是一个值得庆祝的事情,但至少很明显策略甚至没有资产那么糟糕。
转而以年度为基础追踪事物:
$ ./observer-benchmark.py --plot --timeframe years
输出
注意!
-
策略的最后一个值从
-0.26
变化到-0.27
-
另一方面,资产显示的最后一个值是
-0.35
(与上述的-0.33
相比)
两者值如此接近的原因是,从 2005 年到 2006 年,无论是策略还是基准资产,几乎都处于 2005 年的起始水平。
转换到较低的时间范围,比如周,整个情况都会改变:
$ ./observer-benchmark.py --plot --timeframe weeks
输出
现在:
-
基准 观察者显示出更加紧张的状态。事物上下移动,因为现在每周都在跟踪组合和数据的回报率
-
因为在年底的最后一周没有交易活动,资产几乎没有变动,所以最后显示的值为 0.00(最后一周之前的最后收盘价为
25.54
,样本数据收盘价为25.55
,差异首先在第 4 位小数点上感受到)
观察基准 - 另一数据
该样本允许针对不同的数据进行基准测试。默认情况下,使用--benchdata1
时,基准测试的对象是Oracle。考虑整个数据集,使用--timeframe notimeframe
:
$ ./observer-benchmark.py --plot --timeframe notimeframe --benchdata1
输出:
现在清楚为什么上面没有值得庆祝的原因了:
-
策略的结果对于
notimeframe
没有改变,仍然为-26%
(-0.26
) -
但是当与另一数据进行基准比较时,这个数据在同一时期有
+23%
(0.23
)的增长
要么策略需要改变,要么另一种资产最好交易。
结论
现在有两种方法,使用相同的底层代码/计算,来跟踪时间回报率和基准
- 观察者(
TimeReturn
和Benchmark
)
而
- 分析器(带有
data
参数的TimeReturn
和TimeReturn
)
当然,基准 不保证盈利,只是比较。
使用样本的方法:
$ ./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()
Pyfolio 集成
原文:
www.backtrader.com/blog/posts/2016-07-17-pyfolio-integration/pyfolio-integration/
注意
2017 年 2 月
pyfolio
的 API 已更改,create_full_tear_sheet
不再具有gross_lev
作为命名参数。
因此,下面的示例不起作用
初次查看教程时,由于 zipline 和 pyfolio 之间的紧密集成,被认为很难,但是 pyfolio 提供的用于其他用途的样本测试数据实际上非常有用,可以解码幕后运行的内容,从而实现集成的奇迹。
一个名为pyfolio
的投资组合工具的集成是在Ticket #108中提出的。
大部分组件已经就位在backtrader中:
-
分析器基础设施
-
子分析器
-
一个 TimeReturn 分析器
只需要一个主要的PyFolio
分析器和 3 个简单的子分析器。再加上一个依赖于pyfolio
已经需要的pandas
的方法。
最具挑战性的部分是…“确保所有依赖项正确”。
-
更新
pandas
-
更新
numpy
-
更新
scikit-lean
-
更新
seaborn
在类 Unix 环境中使用C编译器,一切都是关于时间的。在 Windows 下,即使安装了特定的Microsoft编译器(在本例中是Python 2.7的链),事情也会失败。但是一个收集最新软件包的著名网站对Windows有所帮助。如果您需要,请访问:
www.lfd.uci.edu/~gohlke/pythonlibs/
如果没有经过测试,集成将不完整,这就是为什么通常的示例总是存在的原因。
没有 PyFolio
该示例使用random.randint
来决定何时买入/卖出,因此这只是一个检查事情是否正常运行的方法:
$ ./pyfoliotest.py --printout --no-pyfolio --plot
输出:
Len,Datetime,Open,High,Low,Close,Volume,OpenInterest
0001,2005-01-03T23:59:59,38.36,38.90,37.65,38.18,25482800.00,0.00
BUY 1000 @%23.58
0002,2005-01-04T23:59:59,38.45,38.54,36.46,36.58,26625300.00,0.00
BUY 1000 @%36.58
SELL 500 @%22.47
0003,2005-01-05T23:59:59,36.69,36.98,36.06,36.13,18469100.00,0.00
...
SELL 500 @%37.51
0502,2006-12-28T23:59:59,25.62,25.72,25.30,25.36,11908400.00,0.00
0503,2006-12-29T23:59:59,25.42,25.82,25.33,25.54,16297800.00,0.00
SELL 250 @%17.14
SELL 250 @%37.01
有 3 个数据和几个买入和卖出操作是随机选择并分散在测试运行的默认 2 年寿命中
一个 PyFolio 运行
当在Jupyter Notebook中运行时,pyfolio
的功能运行良好,包括内联绘图。这是笔记本
注意
runstrat
在此处获取[]作为参数以使用默认参数运行,并跳过由笔记本本身传递的参数
%matplotlib inline
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 = (
('printout', False),
('stake', 1000),
)
def __init__(self):
pass
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))
# Data 0
for data in self.datas:
toss = random.randint(1, 10)
curpos = self.getposition(data)
if curpos.size:
if toss > 5:
size = curpos.size // 2
self.sell(data=data, size=size)
if self.p.printout:
print('SELL {} @%{}'.format(size, data.close[0]))
elif toss < 5:
self.buy(data=data, size=self.p.stake)
if self.p.printout:
print('BUY {} @%{}'.format(self.p.stake, data.close[0]))
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.BacktraderCSVData(dataname=args.data0, **dkwargs)
cerebro.adddata(data0, name='Data0')
data1 = bt.feeds.BacktraderCSVData(dataname=args.data1, **dkwargs)
cerebro.adddata(data1, name='Data1')
data2 = bt.feeds.BacktraderCSVData(dataname=args.data2, **dkwargs)
cerebro.adddata(data2, name='Data2')
cerebro.addstrategy(St, printout=args.printout)
if not args.no_pyfolio:
cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio')
results = cerebro.run()
if not args.no_pyfolio:
strat = results[0]
pyfoliozer = strat.analyzers.getbyname('pyfolio')
returns, positions, transactions, gross_lev = pyfoliozer.get_pf_items()
if args.printout:
print('-- RETURNS')
print(returns)
print('-- POSITIONS')
print(positions)
print('-- TRANSACTIONS')
print(transactions)
print('-- GROSS LEVERAGE')
print(gross_lev)
import pyfolio as pf
pf.create_full_tear_sheet(
returns,
positions=positions,
transactions=transactions,
gross_lev=gross_lev,
live_start_date='2005-05-01',
round_trips=True)
if args.plot:
cerebro.plot(style=args.plot_style)
def parse_args(args=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Sample for pivot point and cross plotting')
parser.add_argument('--data0', required=False,
default='../../datas/yhoo-1996-2015.txt',
help='Data to be read in')
parser.add_argument('--data1', required=False,
default='../../datas/orcl-1995-2014.txt',
help='Data to be read in')
parser.add_argument('--data2', required=False,
default='../../datas/nvda-1999-2014.txt',
help='Data to be read in')
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('--plot', required=False, action='store_true',
help=('Plot the result'))
parser.add_argument('--plot-style', required=False, action='store',
default='bar', choices=['bar', 'candle', 'line'],
help=('Plot style'))
parser.add_argument('--no-pyfolio', required=False, action='store_true',
help=('Do not do pyfolio things'))
import sys
aargs = args if args is not None else sys.argv[1:]
return parser.parse_args(aargs)
runstrat([])
Entire data start date: 2005-01-03
Entire data end date: 2006-12-29
Out-of-Sample Months: 20
Backtest Months: 3
[-0.012 -0.025]
D:drobinWinPython-64bit-2.7.10.3python-2.7.10.amd64libsite-packagespyfolioplotting.py:1210: FutureWarning: .resample() is now a deferred operation
use .resample(...).mean() instead of .resample(...)
**kwargs)
<matplotlib.figure.Figure at 0x23982b70>
使用示例:
$ ./pyfoliotest.py --help
usage: pyfoliotest.py [-h] [--data0 DATA0] [--data1 DATA1] [--data2 DATA2]
[--fromdate FROMDATE] [--todate TODATE] [--printout]
[--cash CASH] [--plot] [--plot-style {bar,candle,line}]
[--no-pyfolio]
Sample for pivot point and cross plotting
optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data to be read in (default:
../../datas/yhoo-1996-2015.txt)
--data1 DATA1 Data to be read in (default:
../../datas/orcl-1995-2014.txt)
--data2 DATA2 Data to be read in (default:
../../datas/nvda-1999-2014.txt)
--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)
--plot Plot the result (default: False)
--plot-style {bar,candle,line}
Plot style (default: bar)
--no-pyfolio Do not do pyfolio things (default: False)
体积填充
原文:
www.backtrader.com/blog/posts/2016-07-14-volume-filling/volume-filling/
到目前为止,backtrader中的默认体积填充策略一直相当简单和直接:
- 忽略体积
注意
2016 年 7 月 15 日
更正了实现中的一个错误,并更新了样本以close
该位置并在休息后重复。
下面的最后一个测试运行(以及相应的图表)来自更新样本
这基于两个前提:
-
在足够流动的市场中交易,以完全吸收buy/sell订单
-
真实的成交量匹配需要真实的世界
一个快速的示例是
Fill or Kill
订单。即使是到tick分辨率并且具有足够的fill体积,backtrader经纪人也无法知道市场中有多少额外的参与者来区分订单是否会匹配以坚持Fill
部分,或者订单是否应该Kill
但是随着版本1.5.2.93
的发布,可以指定经纪人采取Volume的filler
以在执行订单时考虑Volume。此外,3 个初始填充器已经进入发布:
-
FixedSize
:每天使用固定的匹配大小(例如:1000 个单位),前提是当前柱状图至少有 1000 个单位 -
FixedBarPerc
:使用总柱状图体积的百分比尝试匹配订单 -
BarPointPerc
:在价格范围高低之间进行柱状图体积的均匀分布,并使用相应于单个价格点的体积的百分比
创建填充器
backtrader生态系统中的filler可以是符合以下签名的任何callable:
callable(order, price, ago)
其中:
-
order
是即将执行的订单此对象提供了对操作目标的
data
对象的访问权限,创建大小/价格、执行价格/大小/剩余大小以及其他详细信息 -
price
,订单将以其执行 -
ago
是在其中查找体积和价格元素的order中data
的索引在几乎所有情况下,这将是
0
(当前时间点),但在一种特殊情况下,为了涵盖Close
订单,这可能是-1
。例如,要访问柱状图体积,请执行以下操作:
barvolume = order.data.volume[ago]`
可调用对象可以是一个函数,或者例如支持__call__
方法的类的实例,如:
class MyFiller(object):
def __call__(self, order, price, ago):
pass
向经纪人添加填充器
最直接的方法是使用set_filler
:
import backtrader as bt
cerebro = Cerebro()
cerebro.broker.set_filler(bt.broker.filler.FixedSize())
第二选择是完全替换broker
,尽管这可能只适用于已经重写部分功能的BrokerBack
的子类:
import backtrader as bt
cerebro = Cerebro()
filler = bt.broker.filler.FixedSize()
newbroker = bt.broker.BrokerBack(filler=filler)
cerebro.broker = newbroker
该样本
backtrader源代码包含一个名为volumefilling
的样本,它允许测试一些集成的fillers
(最初全部)。
该样本在源文件中使用一个默认数据样本命名为:datas/2006-volume-day-001.txt
。
例如,不使用填充器运行:
$ ./volumefilling.py --stakeperc 20.0
输出:
Len,Datetime,Open,High,Low,Close,Volume,OpenInterest
0001,2006-01-02,3602.00,3624.00,3596.00,3617.00,164794.00,1511674.00
++ STAKE VOLUME: 32958.0
-- NOTIFY ORDER BEGIN
Ref: 1
...
Alive: False
-- NOTIFY ORDER END
-- ORDER REMSIZE: 0.0
++ ORDER COMPLETED at data.len: 2
0002,2006-01-03,3623.00,3665.00,3614.00,3665.00,554426.00,1501792.00
...
因为输入相当冗长,所以大部分内容都被跳过了,但总结是:
-
看到第
1
条时,将使用20%
(–stakeperc 20.0)发出买入订单 -
如输出所示,并且根据backtrader的默认行为,订单已经在一次交易中完全匹配。没有查看成交量
注意
经纪人在示例中分配了大量的现金,以确保可以应对许多测试情况
另一个运行使用FixedSize
成交量填充器和每个条的最大1000
单位:
$ ./volumefilling.py --stakeperc 20.0 --filler FixedSize --filler-args size=1000
输出:
Len,Datetime,Open,High,Low,Close,Volume,OpenInterest
0001,2006-01-02,3602.00,3624.00,3596.00,3617.00,164794.00,1511674.00
++ STAKE VOLUME: 32958.0
-- NOTIFY ORDER BEGIN
...
-- NOTIFY ORDER END
-- ORDER REMSIZE: 0.0
++ ORDER COMPLETED at data.len: 34
0034,2006-02-16,3755.00,3774.00,3738.00,3773.00,502043.00,1662302.00
...
现在:
-
所选的成交量保持不变,为
32958
-
在第
34
条完成执行,这似乎是合理的,因为从第2
条到第34
条…已经看到了33
个条。每条1000
单位匹配的情况下,显然需要33
个条来完成执行
这并不是一个伟大的成就,所以让我们来看看FixedBarPerc
:
$ ./volumefilling.py --stakeperc 20.0 --filler FixedBarPerc --filler-args perc=0.75
输出:
...
-- NOTIFY ORDER END
-- ORDER REMSIZE: 0.0
++ ORDER COMPLETED at data.len: 11
0011,2006-01-16,3635.00,3664.00,3632.00,3660.00,273296.00,1592611.00
...
这次:
-
跳过开始,仍然是
32958
单位的订单 -
执行使用了
0.75%
的条成交量来匹配请求。 -
完成需要从第
2
条到第11
条(10
条)
这更有趣,但让我们看看现在使用BarPointPerc
更动态的成交量分配会发生什么:
$ ./volumefilling.py --stakeperc 20.0 --filler BarPointPerc --filler-args minmov=1.0,perc=10.0
输出:
...
-- NOTIFY ORDER END
-- ORDER REMSIZE: 0.0
++ ORDER COMPLETED at data.len: 22
0022,2006-01-31,3697.00,3718.00,3681.00,3704.00,749740.00,1642003.00
...
发生的事情是:
-
同样的初始分配(跳过)到
32958
的订单大小 -
完全执行需要从
2
到22
(21 个条) -
filler使用了
1.0
的minmov
(资产的最小价格变动)来在高低范围内均匀分配成交量 -
10%
的成交量分配给特定价格点用于订单匹配
对于任何对如何在每个条上部分匹配订单感兴趣的人来说,检查运行的完整输出可能是值得的时间。
注意
在 1.5.3.93 中修正了错误并更新示例以在中断后close
操作
现金增加到一个更多的数量,以避免保证金调用并启用绘图:
$ ./volumefilling.py --filler FixedSize --filler-args size=10000 --stakeperc 10.0 --plot --cash 500e9
而不是查看输出,因为输出非常冗长,让我们看看图表,它已经讲述了整个故事。
使用示例:
usage: volumefilling.py [-h] [--data DATA] [--cash CASH]
[--filler {FixedSize,FixedBarPerc,BarPointPerc}]
[--filler-args FILLER_ARGS] [--stakeperc STAKEPERC]
[--opbreak OPBREAK] [--fromdate FROMDATE]
[--todate TODATE] [--plot]
Volume Filling Sample
optional arguments:
-h, --help show this help message and exit
--data DATA Data to be read in (default: ../../datas/2006-volume-
day-001.txt)
--cash CASH Starting cash (default: 500000000.0)
--filler {FixedSize,FixedBarPerc,BarPointPerc}
Apply a volume filler for the execution (default:
None)
--filler-args FILLER_ARGS
kwargs for the filler with format:
arg1=val1,arg2=val2... (default: None)
--stakeperc STAKEPERC
Percentage of 1st bar to use for stake (default: 10.0)
--opbreak OPBREAK Bars to wait for new op after completing another
(default: 10)
--fromdate FROMDATE, -f FROMDATE
Starting date in YYYY-MM-DD format (default: None)
--todate TODATE, -t TODATE
Ending date in YYYY-MM-DD format (default: None)
--plot Plot the result (default: False)
代码
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import os.path
import time
import sys
import backtrader as bt
class St(bt.Strategy):
params = (
('stakeperc', 10.0),
('opbreak', 10),
)
def notify_order(self, order):
print('-- NOTIFY ORDER BEGIN')
print(order)
print('-- NOTIFY ORDER END')
print('-- ORDER REMSIZE:', order.executed.remsize)
if order.status == order.Completed:
print('++ ORDER COMPLETED at data.len:', len(order.data))
self.doop = -self.p.opbreak
def __init__(self):
pass
def start(self):
self.callcounter = 0
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))
self.doop = 0
def next(self):
txtfields = list()
txtfields.append('%04d' % len(self))
txtfields.append(self.data0.datetime.date(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))
# Single order
if self.doop == 0:
if not self.position.size:
stakevol = (self.data0.volume[0] * self.p.stakeperc) // 100
print('++ STAKE VOLUME:', stakevol)
self.buy(size=stakevol)
else:
self.close()
self.doop += 1
FILLERS = {
'FixedSize': bt.broker.filler.FixedSize,
'FixedBarPerc': bt.broker.filler.FixedBarPerc,
'BarPointPerc': bt.broker.filler.BarPointPerc,
}
def runstrat():
args = parse_args()
datakwargs = dict()
if args.fromdate:
fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
datakwargs['fromdate'] = fromdate
if args.todate:
fromdate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
datakwargs['todate'] = todate
data = bt.feeds.BacktraderCSVData(dataname=args.data, **datakwargs)
cerebro = bt.Cerebro()
cerebro.adddata(data)
cerebro.broker.set_cash(args.cash)
if args.filler is not None:
fillerkwargs = dict()
if args.filler_args is not None:
fillerkwargs = eval('dict(' + args.filler_args + ')')
filler = FILLERSargs.filler
cerebro.broker.set_filler(filler)
cerebro.addstrategy(St, stakeperc=args.stakeperc, opbreak=args.opbreak)
cerebro.run()
if args.plot:
cerebro.plot(style='bar')
def parse_args():
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Volume Filling Sample')
parser.add_argument('--data', required=False,
default='../../datas/2006-volume-day-001.txt',
help='Data to be read in')
parser.add_argument('--cash', required=False, action='store',
default=500e6, type=float,
help=('Starting cash'))
parser.add_argument('--filler', required=False, action='store',
default=None, choices=FILLERS.keys(),
help=('Apply a volume filler for the execution'))
parser.add_argument('--filler-args', required=False, action='store',
default=None,
help=('kwargs for the filler with format:\n'
'\n'
'arg1=val1,arg2=val2...'))
parser.add_argument('--stakeperc', required=False, action='store',
type=float, default=10.0,
help=('Percentage of 1st bar to use for stake'))
parser.add_argument('--opbreak', required=False, action='store',
type=int, default=10,
help=('Bars to wait for new op after completing '
'another'))
parser.add_argument('--fromdate', '-f', required=False, default=None,
help='Starting date in YYYY-MM-DD format')
parser.add_argument('--todate', '-t', required=False, default=None,
help='Ending date in YYYY-MM-DD format')
parser.add_argument('--plot', required=False, action='store_true',
help=('Plot the result'))
return parser.parse_args()
if __name__ == '__main__':
runstrat()
以步骤方式交易一天
原文:
www.backtrader.com/blog/posts/2016-07-13-day-in-steps/day-in-steps/
看起来世界上的某个地方存在一种可以总结如下的兴趣:
- 使用每日柱状图但使用开盘价引入订单
这来自于票证交流中的对话#105 Order execution logic with current day data和#101 Dynamic stake calculation
backtrader在处理每日柱状图时尽可能保持真实,并且在使用每日柱状图时适用以下前提:
- 当评估每日柱状图时,柱状图已经结束
这是有道理的,因为所有价格(open/high/low/close)组件都是已知的。当已知close
价格时允许在open
价格上采取行动似乎是不合逻辑的。
这个问题的明显解决方案是使用日内数据,在已知开盘价时进入。但是似乎日内数据并不那么普遍。
这就是在数据源中添加过滤器可以帮助的地方。一个过滤器:
- 将每日数据转换为类似日内数据的数据
碧海蓝天!!!好奇的读者会立即指出,例如从Minutes
到Days
的上采样是合乎逻辑且有效的,但是从Days
到Minutes
的下采样是不可能的。
而且百分百正确。下面呈现的过滤器不会尝试这样做,但是一个更加谦卑和简单的目标:
-
将每日柱状图分解为 2 部分
-
一个只有开盘价而没有成交量的柱状图
-
一个是正常的每日柱状图的副本
-
这仍然可以被视为一种合乎逻辑的方法:
-
看到开盘价时,交易员可以采取行动
-
订单在一天的其余时间匹配(实际上可能匹配也可能不匹配,取决于执行类型和价格限制)
下面呈现了完整的代码。让我们看一个使用255
个每日柱状图的众所周知的数据的示例运行:
$ ./daysteps.py --data ../../datas/2006-day-001.txt
输出:
Calls,Len Strat,Len Data,Datetime,Open,High,Low,Close,Volume,OpenInterest
0001,0001,0001,2006-01-02T23:59:59,3578.73,3578.73,3578.73,3578.73,0.00,0.00
- I could issue a buy order during the Opening
0002,0001,0001,2006-01-02T23:59:59,3578.73,3605.95,3578.73,3604.33,0.00,0.00
0003,0002,0002,2006-01-03T23:59:59,3604.08,3604.08,3604.08,3604.08,0.00,0.00
- I could issue a buy order during the Opening
0004,0002,0002,2006-01-03T23:59:59,3604.08,3638.42,3601.84,3614.34,0.00,0.00
0005,0003,0003,2006-01-04T23:59:59,3615.23,3615.23,3615.23,3615.23,0.00,0.00
- I could issue a buy order during the Opening
0006,0003,0003,2006-01-04T23:59:59,3615.23,3652.46,3615.23,3652.46,0.00,0.00
...
...
0505,0253,0253,2006-12-27T23:59:59,4079.70,4079.70,4079.70,4079.70,0.00,0.00
- I could issue a buy order during the Opening
0506,0253,0253,2006-12-27T23:59:59,4079.70,4134.86,4079.70,4134.86,0.00,0.00
0507,0254,0254,2006-12-28T23:59:59,4137.44,4137.44,4137.44,4137.44,0.00,0.00
- I could issue a buy order during the Opening
0508,0254,0254,2006-12-28T23:59:59,4137.44,4142.06,4125.14,4130.66,0.00,0.00
0509,0255,0255,2006-12-29T23:59:59,4130.12,4130.12,4130.12,4130.12,0.00,0.00
- I could issue a buy order during the Opening
0510,0255,0255,2006-12-29T23:59:59,4130.12,4142.01,4119.94,4119.94,0.00,0.00
以下情况发生:
-
next
被调用:510 次
,即255 x 2
-
策略和数据的
len
总共达到了255
,这是预期的:数据只有这么多根柱状图 -
每当数据的
len
增加时,4 个价格组件具有相同的值,即open
价格这里打印出一条备注,指示在这个开盘阶段可以采取行动,例如购买。
实际上:
- 每日数据源正在使用每天 2 步重播,在
open
和其余价格组件之间提供操作选项
过滤器将在下一个版本中添加到backtrader的默认分发中。
包括过滤器的示例代码。
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
from datetime import datetime, time
import backtrader as bt
class DayStepsFilter(object):
def __init__(self, data):
self.pendingbar = None
def __call__(self, data):
# Make a copy of the new bar and remove it from stream
newbar = [data.lines[i][0] for i in range(data.size())]
data.backwards() # remove the copied bar from stream
openbar = newbar[:] # Make an open only bar
o = newbar[data.Open]
for field_idx in [data.High, data.Low, data.Close]:
openbar[field_idx] = o
# Nullify Volume/OpenInteres at the open
openbar[data.Volume] = 0.0
openbar[data.OpenInterest] = 0.0
# Overwrite the new data bar with our pending data - except start point
if self.pendingbar is not None:
data._updatebar(self.pendingbar)
self.pendingbar = newbar # update the pending bar to the new bar
data._add2stack(openbar) # Add the openbar to the stack for processing
return False # the length of the stream was not changed
def last(self, data):
'''Called when the data is no longer producing bars
Can be called multiple times. It has the chance to (for example)
produce extra bars'''
if self.pendingbar is not None:
data.backwards() # remove delivered open bar
data._add2stack(self.pendingbar) # add remaining
self.pendingbar = None # No further action
return True # something delivered
return False # nothing delivered here
class St(bt.Strategy):
params = ()
def __init__(self):
pass
def start(self):
self.callcounter = 0
txtfields = list()
txtfields.append('Calls')
txtfields.append('Len Strat')
txtfields.append('Len Data')
txtfields.append('Datetime')
txtfields.append('Open')
txtfields.append('High')
txtfields.append('Low')
txtfields.append('Close')
txtfields.append('Volume')
txtfields.append('OpenInterest')
print(','.join(txtfields))
self.lcontrol = 0
def next(self):
self.callcounter += 1
txtfields = list()
txtfields.append('%04d' % self.callcounter)
txtfields.append('%04d' % len(self))
txtfields.append('%04d' % len(self.data0))
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 len(self.data) > self.lcontrol:
print('- I could issue a buy order during the Opening')
self.lcontrol = len(self.data)
def runstrat():
args = parse_args()
cerebro = bt.Cerebro()
data = bt.feeds.BacktraderCSVData(dataname=args.data)
data.addfilter(DayStepsFilter)
cerebro.adddata(data)
cerebro.addstrategy(St)
cerebro.run(stdstats=False, runonce=False, preload=False)
if args.plot:
cerebro.plot(style='bar')
def parse_args():
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Sample for pivot point and cross plotting')
parser.add_argument('--data', required=False,
default='../../datas/2005-2006-day-001.txt',
help='Data to be read in')
parser.add_argument('--plot', required=False, action='store_true',
help=('Plot the result'))
return parser.parse_args()
if __name__ == '__main__':
runstrat()
Visual Chart 实时数据/交易
原文:
www.backtrader.com/blog/posts/2016-07-12-visualchart-feed/visualchart-feed/
从版本 1.5.1.93 开始,backtrader 支持 Visual Chart 实时数据和实时交易。
需要的东西:
-
Visual Chart 6(这个版本运行在 Windows 上)
-
comtypes
,具体来说是这个分支:github.com/mementum/comtypes
使用以下命令安装:
pip install https://github.com/mementum/comtypes/archive/master.zip
Visual Chart 的 API 基于 COM,当前的
comtypes
主分支不支持对VT_RECORD
的VT_ARRAYS
进行解包。而 Visual Chart 正是使用了这个。Pull Request #104 已经提交但尚未集成。一旦集成,就可以使用主分支了。
-
pytz
(可选但强烈建议)在许多情况下,数据提供的内部
SymbolInfo.TimeOffset
就足以返回市场时间的数据流(即使默认配置是 Visual Chart 中的LocalTime
)
如果您不知道什么是 Visual Chart 和/或其当前关联的经纪商 Esfera Capital,请访问以下网站:
-
Visual Chart
-
Esfera Capital
初始声明:
-
如往常一样,在冒险之前测试,测试,测试 和 再次测试 一千次。
从这个软件中的 bugs,到您自己的软件中的 bug 以及处理意外情况的管理:任何事情都可能出错
关于此的一些说明:
-
数据提供非常好,并支持内置重采样。好处在于无需进行重采样。
-
数据流不支持 Seconds 分辨率。这不太好,但可以通过 backtrader 的内置重采样解决。
-
内置了回填功能
-
一些国际指数市场(在交易所
096
)具有奇怪的时区和市场偏移。对此进行了一些工作,例如以预期的
US/Eastern
时区提供096.DJI
。 -
数据提供了 continuous futures,非常方便拥有大量历史数据。
因此,可以向数据传递第二个参数,指示实际的交易资产。
-
Good Til Date 订单的日期时间只能指定为 日期。时间 部分会被忽略。
-
没有直接的方法可以找到本地设备到数据服务器的偏移量,需要通过会话开始时的 实时数据点 进行启发式分析来找出这一点。
-
传递带有 时间 组件的 datetime(而不是默认的 00:00:00)似乎会在 COM API 中创建一个 时间过滤器。例如,如果您想要 Minute 数据,从 3 天前开始到 14:30,您可以这样做:
dt = datetime.now() - timedelta(days=3) dt.replace(hour=14, minute=30) vcstore.getdata(dataname='001ES', fromdate=dt)`
数据会一直跳过直到 14:30 不仅是 3 天前,而是以后每一天
因此,请只传递完整日期,即默认的时间部分不受影响。
-
经纪人 支持 Positions 的概念,但仅在它们是 open 时。 关于 Position 的最后事件(其 size 为 0)不会发送。
因此,Position 记账完全由 backtrader 完成。
-
经纪人 不报告佣金。
解决方法是在实例化经纪人时提供自己的
CommissionInfo
派生类。 请参阅 backtader 文档以创建自己的类。 这相当容易。 -
Cancelled
与Expired
订单。 此区别不存在,需要启发式方法来尝试清除这种区别。因此,只有
Cancelled
将被报告。
一些额外的说明:
-
实时 ticks 大部分情况下不被使用。 它们为 backtrader 目的产生大量不需要的信息。 在被 backtrader 完全断开连接之前,它们有两个主要目的。
-
查找符号是否存在。
-
计算到数据服务器的偏移量。
当然,价格信息是实时收集的,但是来自 DataSource 对象,它们同时提供历史数据。
-
尽可能多地记录并在通常的文档链接处提供。
- 阅读文档
从样本 vctest.pye
对 Visual Chart 和 Demo Broker 进行了一些运行。
首先:015ES
(EuroStoxx50 连续)重新采样为 1 分钟,并具有断开连接和重新连接:
$ ./vctest.py --data0 015ES --timeframe Minutes --compression 1 --fromdate 2016-07-12
输出:
--------------------------------------------------
Strategy Created
--------------------------------------------------
Datetime, Open, High, Low, Close, Volume, OpenInterest, SMA
***** DATA NOTIF: CONNECTED
***** DATA NOTIF: DELAYED
0001, 2016-07-12T08:01:00.000000, 2871.0, 2872.0, 2869.0, 2872.0, 1915.0, 0.0, nan
0002, 2016-07-12T08:02:00.000000, 2872.0, 2872.0, 2870.0, 2871.0, 479.0, 0.0, nan
0003, 2016-07-12T08:03:00.000000, 2871.0, 2871.0, 2869.0, 2870.0, 518.0, 0.0, nan
0004, 2016-07-12T08:04:00.000000, 2870.0, 2871.0, 2870.0, 2871.0, 248.0, 0.0, nan
0005, 2016-07-12T08:05:00.000000, 2870.0, 2871.0, 2870.0, 2871.0, 234.0, 0.0, 2871.0
...
...
0639, 2016-07-12T18:39:00.000000, 2932.0, 2933.0, 2932.0, 2932.0, 1108.0, 0.0, 2932.8
0640, 2016-07-12T18:40:00.000000, 2931.0, 2932.0, 2931.0, 2931.0, 65.0, 0.0, 2932.6
***** DATA NOTIF: LIVE
0641, 2016-07-12T18:41:00.000000, 2932.0, 2932.0, 2930.0, 2930.0, 2093.0, 0.0, 2931.8
***** STORE NOTIF: (u'VisualChart is Disconnected', -65520)
***** DATA NOTIF: CONNBROKEN
***** STORE NOTIF: (u'VisualChart is Connected', -65521)
***** DATA NOTIF: CONNECTED
***** DATA NOTIF: DELAYED
0642, 2016-07-12T18:42:00.000000, 2931.0, 2931.0, 2931.0, 2931.0, 137.0, 0.0, 2931.2
0643, 2016-07-12T18:43:00.000000, 2931.0, 2931.0, 2931.0, 2931.0, 432.0, 0.0, 2931.0
...
0658, 2016-07-12T18:58:00.000000, 2929.0, 2929.0, 2929.0, 2929.0, 4.0, 0.0, 2930.0
0659, 2016-07-12T18:59:00.000000, 2929.0, 2930.0, 2929.0, 2930.0, 353.0, 0.0, 2930.0
***** DATA NOTIF: LIVE
0660, 2016-07-12T19:00:00.000000, 2930.0, 2930.0, 2930.0, 2930.0, 376.0, 0.0, 2930.0
0661, 2016-07-12T19:01:00.000000, 2929.0, 2930.0, 2929.0, 2930.0, 35.0, 0.0, 2929.8
注意
执行环境安装了 pytz
。
注意
注意没有 --resample
:对于 Minutes
,重新采样是内置于 Visual Chart 中的。
最后一些交易,购买 015ES
的 2 个合约,单个 Market
订单,并将它们卖出为 1 个合约的 2 个订单。
执行:
$ ./vctest.py --data0 015ES --timeframe Minutes --compression 1 --fromdate 2016-07-12 2>&1 --broker --account accname --trade --stake 2
输出相当冗长,显示了订单执行的所有部分。 简要总结一下:
--------------------------------------------------
Strategy Created
--------------------------------------------------
Datetime, Open, High, Low, Close, Volume, OpenInterest, SMA
***** DATA NOTIF: CONNECTED
***** DATA NOTIF: DELAYED
0001, 2016-07-12T08:01:00.000000, 2871.0, 2872.0, 2869.0, 2872.0, 1915.0, 0.0, nan
...
0709, 2016-07-12T19:50:00.000000, 2929.0, 2930.0, 2929.0, 2930.0, 11.0, 0.0, 2930.4
***** DATA NOTIF: LIVE
0710, 2016-07-12T19:51:00.000000, 2930.0, 2930.0, 2929.0, 2929.0, 134.0, 0.0, 2930.0
-------------------------------------------------- ORDER BEGIN 2016-07-12 19:52:01.629000
Ref: 1
OrdType: 0
OrdType: Buy
Status: 1
Status: Submitted
Size: 2
Price: None
Price Limit: None
ExecType: 0
ExecType: Market
CommInfo: <backtrader.brokers.vcbroker.VCCommInfo object at 0x000000001100CE10>
End of Session: 736157.916655
Info: AutoOrderedDict()
Broker: <backtrader.brokers.vcbroker.VCBroker object at 0x000000000475D400>
Alive: True
-------------------------------------------------- ORDER END
-------------------------------------------------- ORDER BEGIN 2016-07-12 19:52:01.629000
Ref: 1
OrdType: 0
OrdType: Buy
Status: 2
Status: Accepted
Size: 2
Price: None
Price Limit: None
ExecType: 0
ExecType: Market
CommInfo: <backtrader.brokers.vcbroker.VCCommInfo object at 0x000000001100CE10>
End of Session: 736157.916655
Info: AutoOrderedDict()
Broker: None
Alive: True
-------------------------------------------------- ORDER END
-------------------------------------------------- ORDER BEGIN 2016-07-12 19:52:01.629000
Ref: 1
OrdType: 0
OrdType: Buy
Status: 4
Status: Completed
Size: 2
Price: None
Price Limit: None
ExecType: 0
ExecType: Market
CommInfo: <backtrader.brokers.vcbroker.VCCommInfo object at 0x000000001100CE10>
End of Session: 736157.916655
Info: AutoOrderedDict()
Broker: None
Alive: False
-------------------------------------------------- ORDER END
-------------------------------------------------- TRADE BEGIN 2016-07-12 19:52:01.629000
ref:1
data:<backtrader.feeds.vcdata.VCData object at 0x000000000475D9E8>
tradeid:0
size:2.0
price:2930.0
value:5860.0
commission:0.0
pnl:0.0
pnlcomm:0.0
justopened:True
isopen:True
isclosed:0
baropen:710
dtopen:736157.74375
barclose:0
dtclose:0.0
barlen:0
historyon:False
history:[]
status:1
-------------------------------------------------- TRADE END
...
以下发生了:
-
数据正常接收。
-
发出了一个执行类型为
Market
的BUY
,数量为2
。-
收到
Submitted
和Accepted
通知(仅显示了Submitted
)。 -
一连串的
Partial
执行(仅显示了 1 个)直到收到Completed
。
实际执行没有显示,但在
order.executed
下收到的order
实例中可用。 -
-
虽然没有显示,但发出了 2 x
Market
SELL
订单来撤销操作。屏幕截图显示了在一个晚上使用
015ES
(EuroStoxx 50)和034EURUS
(EUR.USD 外汇对)进行两次不同运行后,在 Visual Chart 中的日志。
示例可以做得更多,旨在彻底测试设施,如果可能的话,揭示任何粗糙的边缘。
使用方式:
$ ./vctest.py --help
usage: vctest.py [-h] [--exactbars EXACTBARS] [--plot] [--stopafter STOPAFTER]
[--nostore] [--qcheck QCHECK] [--no-timeoffset] --data0 DATA0
[--tradename TRADENAME] [--data1 DATA1] [--timezone TIMEZONE]
[--no-backfill_start] [--latethrough] [--historical]
[--fromdate FROMDATE] [--todate TODATE]
[--smaperiod SMAPERIOD] [--replay | --resample]
[--timeframe {Ticks,MicroSeconds,Seconds,Minutes,Days,Weeks,Months,Years}]
[--compression COMPRESSION] [--no-bar2edge] [--no-adjbartime]
[--no-rightedge] [--broker] [--account ACCOUNT] [--trade]
[--donotsell]
[--exectype {Market,Close,Limit,Stop,StopLimit}]
[--price PRICE] [--pstoplimit PSTOPLIMIT] [--stake STAKE]
[--valid VALID] [--cancel CANCEL]
Test Visual Chart 6 integration
optional arguments:
-h, --help show this help message and exit
--exactbars EXACTBARS
exactbars level, use 0/-1/-2 to enable plotting
(default: 1)
--plot Plot if possible (default: False)
--stopafter STOPAFTER
Stop after x lines of LIVE data (default: 0)
--nostore Do not Use the store pattern (default: False)
--qcheck QCHECK Timeout for periodic notification/resampling/replaying
check (default: 0.5)
--no-timeoffset Do not Use TWS/System time offset for non timestamped
prices and to align resampling (default: False)
--data0 DATA0 data 0 into the system (default: None)
--tradename TRADENAME
Actual Trading Name of the asset (default: None)
--data1 DATA1 data 1 into the system (default: None)
--timezone TIMEZONE timezone to get time output into (pytz names)
(default: None)
--historical do only historical download (default: False)
--fromdate FROMDATE Starting date for historical download with format:
YYYY-MM-DD[THH:MM:SS] (default: None)
--todate TODATE End date for historical download with format: YYYY-MM-
DD[THH:MM:SS] (default: None)
--smaperiod SMAPERIOD
Period to apply to the Simple Moving Average (default:
5)
--replay replay to chosen timeframe (default: False)
--resample resample to chosen timeframe (default: False)
--timeframe {Ticks,MicroSeconds,Seconds,Minutes,Days,Weeks,Months,Years}
TimeFrame for Resample/Replay (default: Ticks)
--compression COMPRESSION
Compression for Resample/Replay (default: 1)
--no-bar2edge no bar2edge for resample/replay (default: False)
--no-adjbartime no adjbartime for resample/replay (default: False)
--no-rightedge no rightedge for resample/replay (default: False)
--broker Use VisualChart as broker (default: False)
--account ACCOUNT Choose broker account (else first) (default: None)
--trade Do Sample Buy/Sell operations (default: False)
--donotsell Do not sell after a buy (default: False)
--exectype {Market,Close,Limit,Stop,StopLimit}
Execution to Use when opening position (default:
Market)
--price PRICE Price in Limit orders or Stop Trigger Price (default:
None)
--pstoplimit PSTOPLIMIT
Price for the limit in StopLimit (default: None)
--stake STAKE Stake to use in buy operations (default: 10)
--valid VALID Seconds or YYYY-MM-DD (default: None)
--cancel CANCEL Cancel a buy order after n bars in operation, to be
combined with orders like Limit (default: 0)
代码:
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
from backtrader.utils import flushfile # win32 quick stdout flushing
from backtrader.utils.py3 import string_types
class TestStrategy(bt.Strategy):
params = dict(
smaperiod=5,
trade=False,
stake=10,
exectype=bt.Order.Market,
stopafter=0,
valid=None,
cancel=0,
donotsell=False,
price=None,
pstoplimit=None,
)
def __init__(self):
# To control operation entries
self.orderid = list()
self.order = None
self.counttostop = 0
self.datastatus = 0
# Create SMA on 2nd data
self.sma = bt.indicators.MovAv.SMA(self.data, period=self.p.smaperiod)
print('--------------------------------------------------')
print('Strategy Created')
print('--------------------------------------------------')
def notify_data(self, data, status, *args, **kwargs):
print('*' * 5, 'DATA NOTIF:', data._getstatusname(status), *args)
if status == data.LIVE:
self.counttostop = self.p.stopafter
self.datastatus = 1
def notify_store(self, msg, *args, **kwargs):
print('*' * 5, 'STORE NOTIF:', msg)
def notify_order(self, order):
if order.status in [order.Completed, order.Cancelled, order.Rejected]:
self.order = None
print('-' * 50, 'ORDER BEGIN', datetime.datetime.now())
print(order)
print('-' * 50, 'ORDER END')
def notify_trade(self, trade):
print('-' * 50, 'TRADE BEGIN', datetime.datetime.now())
print(trade)
print('-' * 50, 'TRADE END')
def prenext(self):
self.next(frompre=True)
def next(self, frompre=False):
txt = list()
txt.append('%04d' % len(self))
dtfmt = '%Y-%m-%dT%H:%M:%S.%f'
txt.append('%s' % self.data.datetime.datetime(0).strftime(dtfmt))
txt.append('{}'.format(self.data.open[0]))
txt.append('{}'.format(self.data.high[0]))
txt.append('{}'.format(self.data.low[0]))
txt.append('{}'.format(self.data.close[0]))
txt.append('{}'.format(self.data.volume[0]))
txt.append('{}'.format(self.data.openinterest[0]))
txt.append('{}'.format(self.sma[0]))
print(', '.join(txt))
if len(self.datas) > 1:
txt = list()
txt.append('%04d' % len(self))
dtfmt = '%Y-%m-%dT%H:%M:%S.%f'
txt.append('%s' % self.data1.datetime.datetime(0).strftime(dtfmt))
txt.append('{}'.format(self.data1.open[0]))
txt.append('{}'.format(self.data1.high[0]))
txt.append('{}'.format(self.data1.low[0]))
txt.append('{}'.format(self.data1.close[0]))
txt.append('{}'.format(self.data1.volume[0]))
txt.append('{}'.format(self.data1.openinterest[0]))
txt.append('{}'.format(float('NaN')))
print(', '.join(txt))
if self.counttostop: # stop after x live lines
self.counttostop -= 1
if not self.counttostop:
self.env.runstop()
return
if not self.p.trade:
return
# if True and len(self.orderid) < 1:
if self.datastatus and not self.position and len(self.orderid) < 1:
self.order = self.buy(size=self.p.stake,
exectype=self.p.exectype,
price=self.p.price,
plimit=self.p.pstoplimit,
valid=self.p.valid)
self.orderid.append(self.order)
elif self.position.size > 0 and not self.p.donotsell:
if self.order is None:
size = self.p.stake // 2
if not size:
size = self.position.size # use the remaining
self.order = self.sell(size=size, exectype=bt.Order.Market)
elif self.order is not None and self.p.cancel:
if self.datastatus > self.p.cancel:
self.cancel(self.order)
if self.datastatus:
self.datastatus += 1
def start(self):
header = ['Datetime', 'Open', 'High', 'Low', 'Close', 'Volume',
'OpenInterest', 'SMA']
print(', '.join(header))
self.done = False
def runstrategy():
args = parse_args()
# Create a cerebro
cerebro = bt.Cerebro()
storekwargs = dict()
if not args.nostore:
vcstore = bt.stores.VCStore(**storekwargs)
if args.broker:
brokerargs = dict(account=args.account, **storekwargs)
if not args.nostore:
broker = vcstore.getbroker(**brokerargs)
else:
broker = bt.brokers.VCBroker(**brokerargs)
cerebro.setbroker(broker)
timeframe = bt.TimeFrame.TFrame(args.timeframe)
if args.resample or args.replay:
datatf = bt.TimeFrame.Ticks
datacomp = 1
else:
datatf = timeframe
datacomp = args.compression
fromdate = None
if args.fromdate:
dtformat = '%Y-%m-%d' + ('T%H:%M:%S' * ('T' in args.fromdate))
fromdate = datetime.datetime.strptime(args.fromdate, dtformat)
todate = None
if args.todate:
dtformat = '%Y-%m-%d' + ('T%H:%M:%S' * ('T' in args.todate))
todate = datetime.datetime.strptime(args.todate, dtformat)
VCDataFactory = vcstore.getdata if not args.nostore else bt.feeds.VCData
datakwargs = dict(
timeframe=datatf, compression=datacomp,
fromdate=fromdate, todate=todate,
historical=args.historical,
qcheck=args.qcheck,
tz=args.timezone
)
if args.nostore and not args.broker: # neither store nor broker
datakwargs.update(storekwargs) # pass the store args over the data
data0 = VCDataFactory(dataname=args.data0, tradename=args.tradename,
**datakwargs)
data1 = None
if args.data1 is not None:
data1 = VCDataFactory(dataname=args.data1, **datakwargs)
rekwargs = dict(
timeframe=timeframe, compression=args.compression,
bar2edge=not args.no_bar2edge,
adjbartime=not args.no_adjbartime,
rightedge=not args.no_rightedge,
)
if args.replay:
cerebro.replaydata(dataname=data0, **rekwargs)
if data1 is not None:
cerebro.replaydata(dataname=data1, **rekwargs)
elif args.resample:
cerebro.resampledata(dataname=data0, **rekwargs)
if data1 is not None:
cerebro.resampledata(dataname=data1, **rekwargs)
else:
cerebro.adddata(data0)
if data1 is not None:
cerebro.adddata(data1)
if args.valid is None:
valid = None
else:
try:
valid = float(args.valid)
except:
dtformat = '%Y-%m-%d' + ('T%H:%M:%S' * ('T' in args.valid))
valid = datetime.datetime.strptime(args.valid, dtformat)
else:
valid = datetime.timedelta(seconds=args.valid)
# Add the strategy
cerebro.addstrategy(TestStrategy,
smaperiod=args.smaperiod,
trade=args.trade,
exectype=bt.Order.ExecType(args.exectype),
stake=args.stake,
stopafter=args.stopafter,
valid=valid,
cancel=args.cancel,
donotsell=args.donotsell,
price=args.price,
pstoplimit=args.pstoplimit)
# Live data ... avoid long data accumulation by switching to "exactbars"
cerebro.run(exactbars=args.exactbars)
if args.plot and args.exactbars < 1: # plot if possible
cerebro.plot()
def parse_args():
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Test Visual Chart 6 integration')
parser.add_argument('--exactbars', default=1, type=int,
required=False, action='store',
help='exactbars level, use 0/-1/-2 to enable plotting')
parser.add_argument('--plot',
required=False, action='store_true',
help='Plot if possible')
parser.add_argument('--stopafter', default=0, type=int,
required=False, action='store',
help='Stop after x lines of LIVE data')
parser.add_argument('--nostore',
required=False, action='store_true',
help='Do not Use the store pattern')
parser.add_argument('--qcheck', default=0.5, type=float,
required=False, action='store',
help=('Timeout for periodic '
'notification/resampling/replaying check'))
parser.add_argument('--no-timeoffset',
required=False, action='store_true',
help=('Do not Use TWS/System time offset for non '
'timestamped prices and to align resampling'))
parser.add_argument('--data0', default=None,
required=True, action='store',
help='data 0 into the system')
parser.add_argument('--tradename', default=None,
required=False, action='store',
help='Actual Trading Name of the asset')
parser.add_argument('--data1', default=None,
required=False, action='store',
help='data 1 into the system')
parser.add_argument('--timezone', default=None,
required=False, action='store',
help='timezone to get time output into (pytz names)')
parser.add_argument('--historical',
required=False, action='store_true',
help='do only historical download')
parser.add_argument('--fromdate',
required=False, action='store',
help=('Starting date for historical download '
'with format: YYYY-MM-DD[THH:MM:SS]'))
parser.add_argument('--todate',
required=False, action='store',
help=('End date for historical download '
'with format: YYYY-MM-DD[THH:MM:SS]'))
parser.add_argument('--smaperiod', default=5, type=int,
required=False, action='store',
help='Period to apply to the Simple Moving Average')
pgroup = parser.add_mutually_exclusive_group(required=False)
pgroup.add_argument('--replay',
required=False, action='store_true',
help='replay to chosen timeframe')
pgroup.add_argument('--resample',
required=False, action='store_true',
help='resample to chosen timeframe')
parser.add_argument('--timeframe', default=bt.TimeFrame.Names[0],
choices=bt.TimeFrame.Names,
required=False, action='store',
help='TimeFrame for Resample/Replay')
parser.add_argument('--compression', default=1, type=int,
required=False, action='store',
help='Compression for Resample/Replay')
parser.add_argument('--no-bar2edge',
required=False, action='store_true',
help='no bar2edge for resample/replay')
parser.add_argument('--no-adjbartime',
required=False, action='store_true',
help='no adjbartime for resample/replay')
parser.add_argument('--no-rightedge',
required=False, action='store_true',
help='no rightedge for resample/replay')
parser.add_argument('--broker',
required=False, action='store_true',
help='Use VisualChart as broker')
parser.add_argument('--account', default=None,
required=False, action='store',
help='Choose broker account (else first)')
parser.add_argument('--trade',
required=False, action='store_true',
help='Do Sample Buy/Sell operations')
parser.add_argument('--donotsell',
required=False, action='store_true',
help='Do not sell after a buy')
parser.add_argument('--exectype', default=bt.Order.ExecTypes[0],
choices=bt.Order.ExecTypes,
required=False, action='store',
help='Execution to Use when opening position')
parser.add_argument('--price', default=None, type=float,
required=False, action='store',
help='Price in Limit orders or Stop Trigger Price')
parser.add_argument('--pstoplimit', default=None, type=float,
required=False, action='store',
help='Price for the limit in StopLimit')
parser.add_argument('--stake', default=10, type=int,
required=False, action='store',
help='Stake to use in buy operations')
parser.add_argument('--valid', default=None,
required=False, action='store',
help='Seconds or YYYY-MM-DD')
parser.add_argument('--cancel', default=0, type=int,
required=False, action='store',
help=('Cancel a buy order after n bars in operation,'
' to be combined with orders like Limit'))
return parser.parse_args()
if __name__ == '__main__':
runstrategy()