原文:
www.backtrader.com/
基准测试
原文:
www.backtrader.com/docu/observer-benchmark/benchmarking/
票号 #89 是关于添加对资产的基准测试的。理智的做法是,即使有一种策略,即使是正的,也低于简单跟踪资产将提供的内容。
backtrader 包含两种不同类型的对象,可以帮助进行跟踪:
-
观察者
-
分析器
在 分析器 领域已经有一个 TimeReturn
对象,用于跟踪整个投资组合价值的收益演变(即:包括现金)
这也显然可以是一个 观察者,因此在添加一些 基准测试 的同时,还进行了一些工作,以便能够将 观察者 和 分析器 插接在一起,这些 观察者 和 分析器 旨在跟踪相同的事物。
注意
观察者 和 分析器 之间的主要区别是 观察者 的 线性 特性,记录每个值,这使它们适用于
用于绘图和实时查询。当然,这会消耗内存。
分析器 另一方面通过 get_analysis
返回一组结果,实现可能不会在 运行 结束之前提供任何结果。
分析器 - 基准测试
标准的 TimeReturn
分析器已被扩展以支持跟踪 数据源。其中涉及的两个主要参数:
-
timeframe
(默认值:None
)如果是None
,那么将报告整个回测期间的完整回报传递
TimeFrame.NoTimeFrame
以考虑整个数据集,没有时间约束 -
data
(默认值:None
)参考资产以跟踪,而不是投资组合价值。
注意
此数据必须已添加到一个
cerebro
实例中,使用addata
、resampledata
或replaydata
更多详细信息和参数:分析器参考
因此,可以这样跟踪投资组合的年度回报率
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())
观察者 - 基准测试
多亏了背景机制允许在 观察者 内使用 分析器,已经添加了两个新的观察者:
-
TimeReturn
-
Benchmark
两者都使用 bt.analyzers.TimeReturn
分析器来收集结果。
与上述的代码片段不同,一个完整的样本带有一些运行,以展示它们的功能。
观察 TimeReturn
执行:
$ ./observer-benchmark.py --plot --timereturn --timeframe notimeframe
注意执行选项:
-
--timereturn
告诉样本做到这一点 -
--timeframe notimeframe
告诉分析器考虑整个数据集,而不考虑时间范围边界。
最后绘制的值为 -0.26
。
- 起始现金(从图表中清楚可见)为
50K
货币单位,策略最终以36,970
货币单位结束,因此价值下降了-26%
。
观察基准测试
因为基准测试也将显示时间回报结果,让我们运行相同的事情,但激活基准测试:
$ ./observer-benchmark.py --plot --timeframe notimeframe
嘿,嘿,嘿!!!
-
策略优于资产:
-0.26
vs-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
.. image:: 04-benchmarking-weeks.png
现在:
-
Benchmark 观察者显示更加紧张的情况。事情上上下下,因为现在对组合和数据的
weekly
回报进行跟踪 -
因为在年末的最后一周没有交易活动,且资产几乎没有变动,所以上次显示的值为 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
- 观察者(
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()
观察者参考
原文:
www.backtrader.com/docu/observers-reference/
基准
类 backtrader.observers.Benchmark()
此观察者存储策略的回报和作为系统传递的数据之一的参考资产的回报。
参数:
-
timeframe
(默认:None
)如果为None
,则将报告整个回测期间的完整收益 -
compression
(默认:None
)仅用于子日时间框架,例如通过指定“TimeFrame.Minutes”和 60 作为压缩来处理小时时间框架
-
data
(默认:None
)用于跟踪的参考资产,以进行比较。
注意:此数据必须已添加到
cerebro
实例中,并使用addata
、resampledata
或replaydata
。 -
_doprenext
(默认:False
)基准将从策略启动的时间点开始进行(即:当策略的最小周期已满足时)。
将此设置为
True
将记录从数据源的起始点开始的基准值。 -
firstopen
(默认:False
)将其保持为
False
可确保价值与基准之间的第一个比较点从 0%开始,因为基准不会使用其开盘价。查看
TimeReturn
分析器参考以获取参数含义的全面解释 -
fund
(默认:None
)如果为
None
,则经纪人的实际模式(fundmode - True/False)将被自动检测以决定收益是基于总净资产值还是基金价值。请参阅经纪人文档中的set_fundmode
将其设置为
True
或False
以获取特定行为
请记住,在run
的任何时刻,可以通过查看lines的名称以索引0
来检查当前值。
经纪人
类 backtrader.observers.Broker(*args, **kwargs)
此观察者在经纪人中跟踪当前现金金额和投资组合价值(包括现金)
参数:无
经纪人 - 现金
类 backtrader.observers.Cash(*args, **kwargs)
此观察者跟踪经纪人中当前现金金额
参数:无
经纪人 - 值
类 backtrader.observers.Value(*args, **kwargs)
此观察者在经纪人中跟踪当前投资组合价值,包括现金。
参数:
-
fund
(默认:None
)如果为
None
,则经纪人的实际模式(fundmode - True/False)将被自动检测以决定收益是基于总净资产值还是基金价值。请参阅经纪人文档中的set_fundmode
将其设置为
True
或False
以获取特定行为
BuySell
类 backtrader.observers.BuySell(*args, **kwargs)
此观察者跟踪个别买入/卖出订单(单独执行),并将它们绘制在图表上,沿着执行价格水平附近的数据
参数:
* `barplot` (default: `False`) Plot buy signals below the minimum and
sell signals above the maximum.
If `False` it will plot on the average price of executions during a
bar
* `bardist` (default: `0.015` 1.5%) Distance to max/min when
`barplot` is `True
回撤
类 backtrader.observers.DrawDown()
此观察者跟踪当前回撤水平(绘制)和最大回撤(不绘制)水平
参数:
-
fund
(默认:None
)如果为
None
,则会自动检测经纪人的实际模式(基金模式 - True/False),以决定收益是基于总净资产值还是基金价值。请参阅经纪人文档中的set_fundmode
将其设置为
True
或False
以实现特定行为
时间收益
类backtrader.observers.TimeReturn()
该观察器存储策略的收益。
参数:
-
timeframe
(默认:None
)如果为None
,则将报告整个回测期间的完整收益将
TimeFrame.NoTimeFrame
传递以考虑整个数据集而没有时间约束 -
compression
(默认:None
)仅用于子日时间框架,例如通过指定“TimeFrame.Minutes”和 60 作为压缩来处理小时时间框架
-
fund
(默认:None
)如果为
None
,则会自动检测经纪人的实际模式(基金模式 - True/False),以决定收益是基于总净资产值还是基金价值。请参阅经纪人文档中的set_fundmode
将其设置为
True
或False
以实现特定行为
请记住,在run
的任何时刻,可以通过查看索引为0
的名称的行来检查当前值。
交易
类backtrader.observers.Trades()
该观察器跟踪完整交易并绘制在关闭交易时实现的 PnL 水平。
当仓位从 0(或越过 0)变为 X 时,交易处于开放状态,然后在回到 0(或以相反方向越过 0)时关闭
参数:
* `pnlcomm` (def: `True`)
Show net/profit and loss, i.e.: after commission. If set to `False`
if will show the result of trades before commission
对数收益
类backtrader.observers.LogReturns()
该观察器存储策略或 a 的对数收益
参数:
-
timeframe
(默认:None
)如果为None
,则将报告整个回测期间的完整收益将
TimeFrame.NoTimeFrame
传递以考虑整个数据集而没有时间约束 -
compression
(默认:None
)仅用于子日时间框架,例如通过指定“TimeFrame.Minutes”和 60 作为压缩来处理小时时间框架
-
fund
(默认:None
)如果为
None
,则会自动检测经纪人的实际模式(基金模式 - True/False),以决定收益是基于总净资产值还是基金价值。请参阅经纪人文档中的set_fundmode
将其设置为
True
或False
以实现特定行为
请记住,在run
的任何时刻,可以通过查看索引为0
的名称的行来检查当前值。
对数收益 2
类backtrader.observers.LogReturns2()
扩展观察器 LogReturns 以显示两个工具
基金价值
类backtrader.observers.FundValue(*args, **kwargs)
该观察器跟踪当前基金样式的值
参数:无
基金份额
类backtrader.observers.FundShares(*args, **kwargs)
该观察器跟踪当前基金样式的份额
参数:无
Sizers
Sizers
原文:
www.backtrader.com/docu/sizers/sizers/
- 智能押注
Strategy提供了交易方法,即:buy
,sell
和close
。让我们看看buy
的签名:
def buy(self, data=None,
size=None, price=None, plimit=None,
exectype=None, valid=None, tradeid=0, **kwargs):
注意,如果调用者没有指定,size
的默认值为None
。这就是Sizers发挥重要作用的地方:
size=None
请求Strategy向其Sizer请求实际押注
这显然意味着Strategies有一个Sizer:是的,确实!背景机制会为Strategy添加一个默认的sizer,如果用户没有添加一个。添加到strategy的默认Sizer是SizerFix
。定义的初始行:
class SizerFix(SizerBase):
params = (('stake', 1),)
很容易猜到这个Sizer只是使用1
单位的stake
买入/卖出(无论是股票、合约等)
使用Sizers
从Cerebro
Sizers可以通过 2 种不同的方法通过Cerebro添加:
-
addsizer(sizercls, *args, **kwargs)
添加一个将应用于添加到cerebro的任何策略的Sizer。这可以说是默认的Sizer。例如:
cerebro = bt.Cerebro() cerebro.addsizer(bt.sizers.SizerFix, stake=20) # default sizer for strategies`
-
addsizer_byidx(idx, sizercls, *args, **kwargs)
Sizer只会添加到由
idx
引用的Strategy这个
idx
可以从addstrategy
的返回值中获取。就像这样:cerebro = bt.Cerebro() cerebro.addsizer(bt.sizers.SizerFix, stake=20) # default sizer for strategies idx = cerebro.addstrategy(MyStrategy, myparam=myvalue) cerebro.addsizer_byidx(idx, bt.sizers.SizerFix, stake=5) cerebro.addstrategy(MyOtherStrategy)`
在这个例子中:
-
系统中已经添加了一个默认的Sizer。这适用于所有没有分配特定Sizer的策略
-
对于MyStrategy,在收集到其插入idx之后,会添加一个特定的调整
sizer
参数的sizer
-
第二个策略,MyOtherStrategy,被添加到系统中。没有为其添加特定的Sizer
-
这意味着:
-
MyStrategy最终将拥有一个内部特定的Sizer
-
MyOtherStrategy将获得默认的sizer
-
-
注意
default并不意味着策略共享一个单一的Sizer实例。每个strategy都会收到一个不同的默认sizer实例
要共享一个单一实例,要共享的sizer应该是一个单例类。如何定义一个超出了backtrader的范围
从Strategy
Strategy类提供了一个 API:setsizer
和getsizer
(以及一个property sizer
)来管理Sizer。签名如下:
-
def setsizer(self, sizer)
: 接受一个已实例化的Sizer -
def getsizer(self)
: 返回当前的Sizer实例 -
sizer
是可以直接get/set的属性
在这种情况下,Sizer可以是:
-
作为参数传递给策略
-
在
__init__
期间使用Sizer
或setsizer
属性设置,如:class MyStrategy(bt.Strategy): params = (('sizer', None),) def __init__(self): if self.p.sizer is not None: self.sizer = self.p.sizer`
例如,这将允许在与cerebro调用发生在同一级别的Sizer并将其作为参数传递给系统中的所有策略,从而有效地实现共享Sizer
Sizer开发
这样做很容易:
-
从
backtrader.Sizer
继承这使您可以访问
self.strategy
和self.broker
,尽管在大多数情况下不需要。可以通过broker
访问的内容-
通过
self.strategy.getposition(data)
获取数据的持仓 -
通过
self.broker.getvalue()
获取完整的投资组合价值请注意,当然也可以用
self.strategy.broker.getvalue()
来实现
其他一些东西已经作为参数列在下面了
-
-
覆盖方法
_getsizing(self, comminfo, cash, data, isbuy)
-
comminfo
: 包含有关数据委托的委员会信息的 CommissionInfo 实例,并允许计算持仓价值、操作成本和操作的委员会费用 -
cash
: 经纪人当前可用的现金 -
data
: 操作目标 -
isbuy
: 对于 买入 操作将为True
,对于 卖出 操作将为False
该方法返回 买入/卖出 操作的期望
size
返回的符号无关紧要,即:如果操作是 卖出 操作(
isbuy
将为False
),则该方法可能返回5
或-5
。 卖出 操作仅使用绝对值。Sizer
已经去到broker
并为给定的 data 请求了 委员会信息,实际的 现金 水平,并提供了对操作目标 data 的直接引用 -
让我们来看一下 FixedSize
尺寸器的定义:
import backtrader as bt
class FixedSize(bt.Sizer):
params = (('stake', 1),)
def _getsizing(self, comminfo, cash, data, isbuy):
return self.params.stake
这很简单,因为 Sizer 不做任何计算,参数就在那里。
但是这种机制应该允许构建复杂的 大小(又称 定位)系统,在进入/退出市场时管理利害关系。
另一个例子:头寸翻转者:
class FixedRerverser(bt.FixedSize):
def _getsizing(self, comminfo, cash, data, isbuy):
position = self.broker.getposition(data)
size = self.p.stake * (1 + (position.size != 0))
return size
这个构建在现有的 FixedSize
基础上,继承了 params
并覆盖了 _getsizing
以实现:
-
通过属性
broker
获取 data 的position
-
使用
position.size
来决定是否加倍固定赌注 -
返回计算出的值
这将从 策略 上卸下决定是否反向或开仓的负担,Sizer 控制着,可以随时替换而不影响逻辑。
实际 Sizer 应用
在不考虑复杂的大小算法的情况下,可以使用两个不同的 Sizer 将策略从单向变为双向。只需在 cerebro 执行中更改 Sizer,策略就会改变行为。一个非常简单的 close
穿越 SMA
算法:
class CloseSMA(bt.Strategy):
params = (('period', 15),)
def __init__(self):
sma = bt.indicators.SMA(self.data, period=self.p.period)
self.crossover = bt.indicators.CrossOver(self.data, sma)
def next(self):
if self.crossover > 0:
self.buy()
elif self.crossover < 0:
self.sell()
注意策略不考虑当前的 持仓(通过查看 self.position
)来决定是否实际执行 买入 或 卖出。只考虑 CrossOver
的 信号。 Sizers 将负责一切。
这个尺寸器将在已经开仓的情况下仅在卖出时返回 非零 大小:
class LongOnly(bt.Sizer):
params = (('stake', 1),)
def _getsizing(self, comminfo, cash, data, isbuy):
if isbuy:
return self.p.stake
# Sell situation
position = self.broker.getposition(data)
if not position.size:
return 0 # do not sell if nothing is open
return self.p.stake
将所有内容放在一起(并假设 backtrader 已经被导入并且一个 data 已经被添加到系统中):
...
cerebro.addstrategy(CloseSMA)
cerebro.addsizer(LongOnly)
...
cerebro.run()
...
图表(从源代码中包含的示例中获取)。
简单地将 Sizer 更改为上面显示的 FixedReverser
即可获得 长-短 版本:
...
cerebro.addstrategy(CloseSMA)
cerebro.addsizer(FixedReverser)
...
cerebro.run()
...
输出图表。
注意区别:
-
交易数量已经翻了一番
-
现金水平永远不会回到值,因为策略总是处于市场中
两种方法都是负面的,但这只是一个例子。
bt.Sizer 参考
类 backtrader.Sizer()
这是Sizers的基类。任何sizer都应该是这个的子类,并覆盖_getsizing
方法
成员属性:
-
strategy
:将由工作在其中的调整器的策略设置提供了策略的整个 api 访问权限,例如如果在
_getsizing
中需要实际数据位置:position = self.strategy.getposition(data)`
-
broker
:将由工作在其中的 Sizer 的策略设置提供了一些复杂调整器可能需要的信息,如投资组合价值,…
_getsizing(comminfo, cash, data, isbuy)
这个方法必须被 Sizer 的子类覆盖,以提供大小调整功能
参数:
* `comminfo`: The CommissionInfo instance that contains
information about the commission for the data and allows
calculation of position value, operation cost, commision for the
operation
* `cash`: current available cash in the *broker*
* `data`: target of the operation
* `isbuy`: will be `True` for *buy* operations and `False`
for *sell* operations
该方法必须返回要执行的实际大小(一个整数)。如果返回0
,则不会执行任何操作。
返回值的绝对值将被使用
调整器参考
www.backtrader.com/docu/sizers-reference/
原文:
FixedSize
类 backtrader.sizers.FixedSize()
这个调整器只是为任何操作返回一个固定的大小。大小可以通过系统希望使用的分期数量来控制,方法是通过指定tranches
参数来缩放到交易中。
参数:
* `stake` (default: `1`)
* `tranches` (default: `1`)
FixedReverser
类 backtrader.sizers.FixedReverser()
这个调整器返回需要的固定大小以反转开仓位置或开仓大小。
-
开仓位置:返回参数
stake
。 -
反转仓位:返回 2 *
stake
。
参数:
* `stake` (default: `1`)
PercentSizer
类 backtrader.sizers.PercentSizer()
这个调整器返回可用现金的百分比。
参数:
* `percents` (default: `20`)
AllInSizer
类 backtrader.sizers.AllInSizer()
这个调整器返回经纪人的所有可用现金。
参数:
* `percents` (default: `100`)
PercentSizerInt
类 backtrader.sizers.PercentSizerInt()
这个调整器以截断为整数的形式返回可用现金的百分比。
参数:
* `percents` (default: `20`)
AllInSizerInt
类 backtrader.sizers.AllInSizerInt()
这个调整器将经纪人的所有可用现金返回,并将大小截断为整数。
参数:
* `percents` (default: `100`)
实时交易
实时数据源和实时交易
原文:
www.backtrader.com/docu/live/live/
从版本 1.5.0 开始,backtrader
支持实时数据和实时交易。
-
交互经纪商
-
可视化图表
-
Oanda
交互式经纪人
原文:
www.backtrader.com/docu/live/ib/ib/
与交互式经纪人的集成支持两种:
-
实时数据提供
-
实时交易
注意
尽管尝试测试尽可能多的错误条件和情况,但代码可能(像任何其他软件一样)包含错误。
在进入生产之前,彻底测试任何策略都使用模拟交易帐户或 TWS 演示。
注意
使用IbPy
模块进行与交互式经纪人的交互必须事先安装。在写作时,Pypi 中没有包,但可以使用以下命令使用pip
进行安装:
pip install git+https://github.com/blampe/IbPy.git
如果您的系统中没有git
可用(Windows 安装?),则以下内容也应该有效:
pip install https://github.com/blampe/IbPy/archive/master.zip
示例代码
源代码包含完整的示例:
- samples/ibtest/ibtest.py
示例不能涵盖每种可能的用例,但它试图提供广泛的见解,并应该突出显示在使用回测模块或实时数据模块时没有真正的区别
有一件事可以确定:
-
示例在进行任何交易活动之前等待
data.LIVE
数据状态通知。这可能是在任何实时策略中考虑的事情
商店模型 vs 直接模型
与交互式经纪人的交互支持通过 2 种模式:
-
商店模型(首选)
-
与数据源类和经纪人类的直接交互
商店模型在创建经纪人和数据源时提供了清晰的分离模式。两个代码片段应该更好地作为示例。
首先是Store模型:
import backtrader as bt
ibstore = bt.stores.IBStore(host='127.0.0.1', port=7496, clientId=35)
data = ibstore.getdata(dataname='EUR.USD-CASH-IDEALPRO')
这里是参数:
host
、port
和clientId
被传递到它们所属的地方IBStore
,该地方使用这些参数打开连接。
然后使用getdata
创建一个与backtrader中所有数据源共用的数据源。
dataname
请求EUR/USD外汇对。
现在可以直接使用了:
import backtrader as bt
data = bt.feeds.IBData(dataname='EUR.USD-CASH-IDEALPRO',
host='127.0.0.1', port=7496, clientId=35)
这里:
-
用于商店的参数被传递给数据。
-
这些将用于在后台创建
IBStore
实例
缺点:
- 清晰度大大降低,因为不清楚什么属于数据,什么属于商店。
IBStore - 商店
商店是实时数据源/交易支持的关键,提供了一个在IbPy
模块和数据源和经纪人代理的需求之间的适应层。
商店是一个涵盖以下功能的概念:
-
作为一个实体的中心商店:在这种情况下,实体是 IB。
这可能需要或不需要参数
-
提供通过该方法获取经纪人实例的访问权限:
IBStore.getbroker(*args, **kwargs)
-
提供获取数据源实例的访问权限
IBStore.getdata(*args, **kwargs)
在这种情况下,许多
**kwargs
与数据源相同,如dataname
、fromdate
、todate
、sessionstart
、sessionend
、timeframe
、compression
数据可能提供其他参数。请查看下面的参考资料。
IBStore
提供:
-
连接目标(
host
和port
参数) -
标识(
clientId
参数) -
重新连接控制(
reconnect
和timeout
参数) -
时间偏移检查(
timeoffset
参数,请参见下文) -
通知和调试
notifyall
(默认:False
):在这种情况下,IB 发送的任何错误消息(许多只是信息性的)都将被转发给Cerebro/Strategy_debug
(默认:False
):在这种情况下,从 TWS 收到的每条消息都将打印到标准输出
IBData 数据源
数据选项
无论是直接还是通过 getdata
,IBData
数据源支持以下数据选项:
-
历史下载请求
如果持续时间超过 IB 对于给定时间框架/压缩组合施加的限制,这些将分成多个请求
-
3 种实时数据
tickPrice
事件(通过 IBreqMktData
)
用于CASH产品(至少 TWS API 9.70 的实验表明不支持其他类型)
通过查看
BID
价格接收tick价格事件,根据非官方互联网文献,这似乎是跟踪CASH
市场价格的方法。时间戳是在系统中本地生成的。如果用户希望,可以使用与 IB 服务器时间的偏移量(从 IB
reqCurrentTime
计算)tickString
事件(又名RTVolume
(通过 IBreqMktData
))
大约每 250 毫秒从 IB 接收一个OHLC/Volume快照(如果没有发生交易,则间隔可能更长)
RealTimeBars
事件(通过 IBreqRealTimeBars
)
每 5 秒接收历史数据条(由 IB 固定持续时间)
如果所选的时间框架/组合低于秒/5级别,此功能将自动禁用。
!!! 注意
`RealTimeBars` do not work with the TWS Demo`
默认行为是在大多数情况下使用:
tickString
,除非用户明确希望使用RealTimeBars
-
Backfilling
除非用户要求只进行历史下载,否则数据源将自动进行回填:
-
在开始时:使用最大可能的持续时间。例如:对于天/1(时间框架/压缩)组合,IB 的最大默认持续时间是1 年,这是将进行回填的时间量
-
在数据断开连接后:在这种情况下,用于回填操作的数据量将通过查看断开连接前接收的最新数据来减少。
-
注意
请注意,最终考虑的时间框架/压缩组合可能不是在数据源创建期间指定的,而是在系统中插入期间指定的。请参见以下示例:
data = ibstore.getdata(dataname='EUR.USD-CASH-IDEALPRO',
timeframe=bt.TimeFrame.Seconds, compression=5)
cerebro.resampledata(data, timeframe=bt.TimeFrame.Minutes, compression=2)
如现在应该清楚的是,最终考虑的时间框架/压缩组合是分钟/2
数据合同检查
在启动阶段,数据源将尝试下载指定合同的详细信息(查看如何指定的参考资料)。如果找不到这样的合同或找到多个匹配项,则数据将拒绝继续并将其通知系统。一些例子。
简单但明确的合同规范:
data = ibstore.getdata(dataname='TWTR') # Twitter
只会找到一个实例(2016-06),因为对于默认类型STK
、交易所SMART
和货币(默认为空)的单一合同交易将被找到。
使用AAPL
的类似方法会失败:
data = ibstore.getdata(dataname='AAPL') # Error -> multiple contracts
因为SMART
可以在几个真实交易所找到合同,并且AAPL
在其中一些交易所以不同的货币交易。以下是可以的:
data = ibstore.getdata(dataname='AAPL-STK-SMART-USD') # 1 contract found
数据通知
数据源将通过以下一种或多种方式报告当前状态(查看Cerebro和Strategy参考资料)
-
Cerebro.notify_data
(如果重写) -
使用
Cerebro.adddatacb
添加回调 -
Strategy.notify_data
(如果重写)
在strategy内部的一个例子:
class IBStrategy(bt.Strategy):
def notify_data(self, data, status, *args, **kwargs):
if status == data.LIVE: # the data has switched to live data
# do something
pass
系统发生更改后,将发送以下通知:
-
CONNECTED
成功初始连接时发送
-
DISCONNECTED
在这种情况下,不再能够检索数据,并且数据将指示系统无法执行任何操作。可能的条件:
-
指定的合同有误
-
在历史下载期间中断
-
尝试重新连接到 TWS 的次数已超过限制
-
-
CONNBROKEN
与 TWS 或数据中心的连接已断开。数据源将尝试(通过存储)重新连接和回溯填充,必要时,并恢复操作
-
NOTSUBSCRIBED
合同和连接都正常,但由于权限不足,无法检索数据。
数据将向系统指示无法检索数据
-
DELAYED
表示正在进行历史/回溯操作,并且由策略处理的数据不是实时数据
-
LIVE
表示从此处开始由strategy处理的数据是实时数据
策略的开发者应该考虑在发生断开连接或接收延迟数据等情况时要采取哪些行动。
数据时间框架和压缩
backtrader生态系统中的数据源,在创建时支持timeframe
和compression
参数。这些参数也可以通过data._timeframe
和data._compression
属性访问
timeframe/compression组合的重要性在将数据通过resampledata
或replaydata
传递给cerebro
实例时具有特定目的,以便内部重新采样器/重播器对象了解预期目标是什么。当重新采样/重播时,._timeframe
和._compression
将在数据中被覆盖。
但在另一方面,对于实时数据源,这些信息可能起重要作用。请参阅以下示例:
data = ibstore.getdata(dataname='EUR.USD-CASH-IDEALPRO',
timeframe=bt.TimeFrame.Ticks,
compression=1, # 1 is the default
rtbar=True, # use RealTimeBars
)
cerebro.adddata(data)
用户正在请求tick数据,这很重要,因为:
-
不会进行回溯填充(IB 支持的最小单位是Seconds/1)
-
即使请求和支持
dataname
的RealTimeBars
,也不会使用,因为RealTimeBar
的最小分辨率是Seconds/5
无论如何,除非使用Ticks/1的分辨率,否则数据必须进行重新采样/重播。上述情况下与实时条和工作:
data = ibstore.getdata(dataname='TWTR-STK-SMART', rtbar=True)
cerebro.resampledata(data, timeframe=bt.TimeFrame.Seconds, compression=20)
在这种情况下,并且如上所述,在resampledata
期间将覆盖数据的._timeframe
和._compression
属性。这是会发生的事情:
-
将会发生回溯填充,请求分辨率为Seconds/20
-
RealTimeBars
将用于实时数据,因为分辨率等于/大于Seconds/5且数据支持(不是CASH产品) -
TWS 发送给系统的事件最多每 5 秒发生一次。这可能不重要,因为系统每 20 秒只向策略发送一个条。
没有RealTimeBars
的情况下相同:
data = ibstore.getdata(dataname='TWTR-STK-SMART')
cerebro.resampledata(data, timeframe=bt.TimeFrame.Seconds, compression=20)
在这种情况下:
-
将会发生回溯填充,请求分辨率为Seconds/20
-
tickString
将用于实时数据,因为(不是CASH产品) -
TWS 发送给系统的事件最多每 250 毫秒发生一次。这可能不重要,因为系统每 20 秒只向策略发送一个条。
最后,对于CASH产品和最多 20 秒:
data = ibstore.getdata(dataname='EUR.USD-CASH-IDEALPRO')
cerebro.resampledata(data, timeframe=bt.TimeFrame.Seconds, compression=20)
在这种情况下:
-
将会发生回溯填充,请求分辨率为Seconds/20
-
tickPrice
将用于实时数据,因为这是现金产品即使添加了
rtbar=True
-
TWS 发送给系统的事件最多每 250 毫秒发生一次。这可能不重要,因为系统每 20 秒只向策略发送一个条。
时间管理
数据源将自动从TWS报告的ContractDetails
对象中确定时区。
注意
这要求安装pytz
。如果未安装,则用户应为数据源的tz
参数提供与所需输出时区兼容的tzinfo
实例
注意
如果安装了pytz
并且用户认为自动时区确定不起作用,则tz
参数可以包含一个时区名称的字符串。backtrader
将尝试使用给定名称实例化一个pytz.timezone
报告的datetime
将是与产品相关的时区的时间。一些示例:
-
产品: 欧洲证券交易所的 EuroStoxxx 50(股票代码:ESTX50-YYYYMM-DTB)
时区将是
CET
(中欧时间),又名Europe/Berlin
-
产品: ES-Mini(股票代码:ES-YYYYMM-GLOBEX)
时区将是
EST5EDT
,又名EST
,又名US/Eastern
-
产品: EUR.JPY 外汇对(股票代码EUR.JPY-CASH-IDEALPRO)
时区将是
EST5EDT
,又名EST
,又名US/Eastern
实际上这是一个交互式经纪商的设置,因为外汇交易几乎连续 24 小时进行,因此对于它们不会有真正的时区。
这种行为确保交易保持一致,无论交易者的实际位置如何,因为计算机很可能具有实际位置的时区,而不是交易场所的时区。
请阅读手册的时间管理部分。
注意
TWS Demo 在没有数据下载权限的资产的时区报告方面并不准确(欧洲斯托克 50 期货就是这种情况的一个例子)
实时数据源和重采样/重播
关于何时为实时数据源交付条的设计决策是:
- 尽可能实时地交付它们
这可能看起来很明显,对于Ticks
的时间框架来说是这样,但如果重采样/重播起作用,延迟可能会发生。用例:
-
重采样配置为Seconds/5,具有:
cerebro.resampledata(data, timeframe=bt.TimeFrame.Seconds, compression=5)`
-
一个时间为
23:05:27.325000
的 tick 被交付 -
在市场上交易速度很慢,下一个 tick 将在
23:05:59.025000
时到达
也许并不明显,但backtrader并不知道交易速度非常慢,下一个 tick 大约会在32
秒后到来。如果没有适当的措施,一个时间为23:05:30.000000
的重采样条可能会被延迟约29 秒
。
这就是为什么实时数据源每隔x
秒(float值)唤醒一次重采样器/重播器并让它知道没有新数据输入。这是在创建实时数据源时通过参数qcheck
(默认值:0.5
秒)来控制的。
这意味着重采样器每隔qcheck
秒就有机会交付一个条,如果本地时钟显示,重采样周期已经结束。这样一来,上述情景(23:05:30.000000
)的重采样条最多会在报告时间后qcheck
秒交付。
因为默认值是0.5
,所以最晚时间是:23:05:30.500000
。几乎比以前早了 29 秒。
缺点:
- 有些 tick 可能会对已经交付的重采样/重播的条造成延迟
如果在交付后,TWS 从服务器收到一个时间戳为23:05:29.995\
000的延迟消息,这对于已经向系统报告的时间
23:05.30.000000`来说就太晚了
这通常发生在以下情况下:
timeoffset
在IBStore\
中被禁用(设置为False
),IB报告的时间与本地时钟的时间差很大。
避免大部分延迟样本的最佳方法是:
-
增加
qcheck
值,以考虑延迟消息:data = ibstore.getdata('TWTR', qcheck=2.0, ...)`
这应该增加额外的空间,即使延迟了重采样/重播条的交付
注意
当然,对于Seconds/5的重采样来说,2.0 秒的延迟意义不同于Minutes/10的重采样
如果由于某种原因,最终用户希望禁用timeoffset
并且不通过qcheck
进行管理,则仍然可以接受延迟样本:
-
在
getdata
/IBData
的参数中设置_latethrough
为True
:data = ibstore.getdata('TWTR', _latethrough=True, ...)`
-
在重采样/重播时设置
takelate
为True
:cerebro.resampledata(data, takelate=True)`
IBBroker - 实时交易
注意
在backtrader中的经纪人模拟中实现了tradeid
功能。这允许正确地跟踪在同一资产上并行执行的交易,并将佣金正确地分配给适当的tradeid
此概念在此实时经纪人中不受支持,因为佣金是由经纪人报告的,而在某些情况下,不可能将其分离为不同的tradeid
值。
tradeid
仍然可以指定,但不再有意义。
使用经纪人
要使用IB Broker,必须替换由cerebro创建的标准经纪人模拟实例。
使用Store模型(首选):
import backtrader as bt
cerebro = bt.Cerebro()
ibstore = bt.stores.IBStore(host='127.0.0.1', port=7496, clientId=35)
cerebro.broker = ibstore.getbroker() # or cerebro.setbroker(...)
使用直接方法:
import backtrader as bt
cerebro = bt.Cerebro()
cerebro.broker = bt.brokers.IBBroker(host='127.0.0.1', port=7496, clientId=35)
经纪人参数
不管是直接还是通过getbroker
,IBBroker
经纪人都不支持任何参数。这是因为经纪人只是一个真实经纪人的代理。真实经纪人给予的,不应被剥夺。
一些限制
现金和价值报告
当内部的backtrader经纪人模拟在调用策略的next
方法之前对value
(净清算价值)和cash
进行计算时,无法保证与实时经纪人相同。
-
如果请求了值,则可能会延迟
next
的执行,直到回答到达 -
经纪人可能尚未计算出这些值
backtrader告诉 TWS 在它们更改时提供更新的值(backtrader订阅accounUpdate
消息),但它不知道消息何时到达。
IBBroker
的getcash
和getvalue
方法报告的值始终是从 IB 接收到的最新值。
注意
进一步的限制是,即使有更多货币的值可用,这些值也以帐户的基本货币报告。这是一个设计选择。
位置
backtrader使用 TWS 报告的资产的Position
(价格和大小)。在order execution和order status消息后,内部计算可以被使用,但是如果其中一些消息被错过(套接字有时会丢失数据包),则计算将无法进行。
当然,如果连接到 TWS 时将执行交易的资产已经有一个开放的头寸,由于初始偏移,策略所做的Trades
的计算将不会像通常一样工作
与其进行交易
就标准用法而言,没有变化。只需使用策略中可用的方法(有关完整说明,请参阅Strategy
参考资料)
-
buy
-
sell
-
close
-
cancel
返回的订单对象
- 与 backtrader 的
Order
对象兼容(在同一层次结构中的子类)
订单执行类型
IB 支持各种执行类型,其中一些由 IB 模拟,一些由交易所本身支持。最初支持哪些订单执行类型的决定有一个动机:
-
与backtrader中可用的经纪人模拟兼容性
这是因为经过回测的内容将会投入生产。
因此,订单执行类型仅限于broker simulation中可用的类型:
-
Order.Market
-
Order.Close
-
Order.Limit
-
Order.Stop
(当Stop触发时,Market订单跟随) -
Order.StopLimit
(当Stop触发时,Limit订单跟随)
注意
停止触发是根据不同的策略由 IB 执行的。backtrader不修改默认设置,即为0
:
0 - the default value. The "double bid/ask" method will be used for
orders for OTC stocks and US options. All other orders will use the
"last" method.
如果用户希望修改此项,可以根据 IB 文档提供的额外**kwargs
向buy
和sell
提供。例如,在策略的next
方法中:
def next(self):
# some logic before
self.buy(data, m_triggerMethod=2)
这已更改策略为2
(“last”方法,其中停止订单基于最后价格触发)
请参阅 IB API 文档以获取有关停止触发的进一步澄清
订单有效期
在回测期间可用的相同有效性概念(使用valid
来buy
和sell
)也可用,并具有相同的含义。因此,对于以下IB 订单的valid
参数翻译如下:
-
None -> GTC
(Good Til Cancelled)因为没有指定有效期,所以理解为订单必须有效直到取消
-
datetime/date
翻译为GTD
(Good Til Date)传递
datetime.datetime/datetime.date
实例表示订单必须有效直到给定时间点。 -
timedelta(x)
翻译为GTD
(这里timedelta(x) != timedelta()
)这被解释为指示订单从
now
+timedelta(x)
开始有效 -
float
翻译为GTD
如果该值来自backtrader使用的原始float日期时间存储,则订单必须有效直到由该float指示的日期时间
-
timedelta() or 0
翻译为DAY
已有一个值(而不是
None
),但是为空,被解释为当前day(session)有效的订单
通知
标准的Order
状态将通过方法notify_order
(如果已重写)通知给strategy
-
Submitted
- 订单已发送到 TWS -
Accepted
- 订单已下达 -
Rejected
- 订单放置失败或在其生命周期内被系统取消 -
Partial
- 已经部分执行 -
Completed
- 订单已完全执行 -
Canceled
(或Cancelled
)这在 IB 下有几个意思:
-
手动用户取消
-
服务器/交易所取消了订单
-
订单有效期已过期
将应用启发式方法,如果已从 TWS 接收到带有
orderState
指示为PendingCancel
或Canceled
的openOrder
消息,则订单将被标记为已过期
-
-
已过期
- 请参阅上文的解释
参考
IBStore
类 backtrader.stores.IBStore()
封装一个 ibpy ibConnection 实例的单例类。
参数也可以在使用此存储的类中指定,如IBData
和IBBroker
参数:
-
host
(默认:127.0.0.1
):IB TWS 或 IB Gateway 实际运行的位置。尽管这通常是本地主机,但不应该是 -
port
(默认值:7496
):连接的端口。演示系统使用7497
-
clientId
(默认值:None
):要用于连接到 TWS 的客户端 ID。None
:生成 1 到 65535 之间的随机 ID。一个整数
:将作为要使用的值传递。 -
notifyall
(默认值:False
)如果为
False
,则只会将error
消息发送到Cerebro
和Strategy
的notify_store
方法。如果为
True
,则会通知从 TWS 接收到的每条消息 -
_debug
(默认值:False
)打印从 TWS 接收到的所有消息到标准输出
-
reconnect
(默认值:3
)在第 1 次连接尝试失败后,尝试重新连接的次数。
将其设置为
-1
值以永远保持重新连接 -
timeout
(默认值:3.0
)重新连接尝试之间的秒数
-
timeoffset
(默认值:True
)如果为 True,则将从
reqCurrentTime
(IB 服务器时间)获得的时间用于计算到本地时间的偏移量,并且此偏移量将用于价格通知(例如用于 CASH 市场的 tickPrice 事件)以修改本地计算的时间戳。时间偏移将传播到
backtrader
生态系统的其他部分,例如重新采样,以使用计算出的偏移量对齐重新采样时间戳。 -
timerefresh
(默认值:60.0
)秒数:时间偏移量必须刷新的频率
-
indcash
(默认值:True
)将 IND 代码视为现金进行价格检索
IBBroker
类backtrader.brokers.IBBroker(**kwargs)
用于 Interactive Brokers 的经纪实现。
此类将 Interactive Brokers 的订单/持仓映射到backtrader
的内部 API。
注意
-
实际上不支持
tradeid
,因为利润和损失直接来自 IB。因为(如预期的那样)以 FIFO 方式计算,所以对于tradeid
,利润和损失并不准确。 -
仓位
如果在操作开始时有资产的持仓或通过其他方式给出的订单改变了持仓,那么在cerebro
中计算的交易将不反映现实。
为了避免这种情况,该经纪商将不得不进行自己的持仓管理,这也将允许使用多个 ID 进行交易(利润和损失也将在本地计算),但可能被认为是与实时经纪商合作的目的相悖。
IBData
类backtrader.feeds.IBData(**kwargs)
Interactive Brokers 数据源。
支持参数dataname
中的以下合同规格:
-
TICKER # 股票类型和 SMART 交易所
-
TICKER-STK # 股票和 SMART 交易所
-
TICKER-STK-EXCHANGE # 股票
-
TICKER-STK-EXCHANGE-CURRENCY # 股票
-
TICKER-CFD # 差价合约和 SMART 交易所
-
TICKER-CFD-EXCHANGE # 差价合约
-
TICKER-CDF-EXCHANGE-CURRENCY # 股票
-
TICKER-IND-EXCHANGE # 指数
-
TICKER-IND-EXCHANGE-CURRENCY # 指数
-
TICKER-YYYYMM-EXCHANGE # 期货
-
TICKER-YYYYMM-EXCHANGE-CURRENCY # 期货
-
TICKER-YYYYMM-EXCHANGE-CURRENCY-MULT # 期货
-
TICKER-FUT-EXCHANGE-CURRENCY-YYYYMM-MULT # 期货
-
TICKER-YYYYMM-EXCHANGE-CURRENCY-STRIKE-RIGHT # 期权
-
TICKER-YYYYMM-EXCHANGE-CURRENCY-STRIKE-RIGHT-MULT # 期权
-
TICKER-FOP-EXCHANGE-CURRENCY-YYYYMM-STRIKE-RIGHT # 期权组合
-
TICKER-FOP-EXCHANGE-CURRENCY-YYYYMM-STRIKE-RIGHT-MULT # 期权组合
-
CUR1.CUR2-CASH-IDEALPRO # 外汇
-
TICKER-YYYYMMDD-EXCHANGE-CURRENCY-STRIKE-RIGHT # 期权
-
TICKER-YYYYMMDD-EXCHANGE-CURRENCY-STRIKE-RIGHT-MULT # 期权
-
TICKER-OPT-EXCHANGE-CURRENCY-YYYYMMDD-STRIKE-RIGHT # 期权
-
TICKER-OPT-EXCHANGE-CURRENCY-YYYYMMDD-STRIKE-RIGHT-MULT # 期权
Params:
-
sectype
(默认:STK
)如果在
dataname
规范中未提供证券类型,则应用的默认值 -
exchange
(默认:SMART
)如果在
dataname
规范中未提供交易所,则应用的默认值 -
currency
(默认:''
)如果在
dataname
规范中未提供货币,则应用的默认值 -
historical
(默认:False
)如果设置为
True
,数据源将在第一次下载数据后停止。将使用标准数据源参数
fromdate
和todate
作为参考。如果请求的持续时间大于由 IB 给定的允许的数据时间段/压缩,则数据源将发出多个请求。
-
what
(默认:None
)如果为
None
,则历史数据请求将使用不同资产类型的默认值:-
对于 CASH 资产,为‘BID’
-
对于任何其他交易
如果希望使用另一个值,请查看 IB API 文档
-
-
rtbar
(默认:False
)如果为
True
,则将使用由 Interactive Brokers 提供的5 秒实时数据条
作为最小刻度。根据文档,它们对应于实时值(一旦被 IB 整理和筛选)如果为
False
,则将使用基于接收到的刻度的RTVolume
价格。对于CASH
资产(例如 EUR.JPY),将始终使用RTVolume
,并从中获取bid
价格(根据互联网上零散的文献,这是 IB 的行业事实标准)即使设置为
True
,如果数据被重新采样/保留到低于秒/5 的时间段/压缩,也不会使用实时数据,因为 IB 不会在该级别以下提供它们 -
qcheck
(默认:0.5
)如果未收到数据,等待的时间(秒)以便适当地对数据包进行重新采样/重播并将通知传递给链上
-
backfill_start
(默认:True
)在开始时执行回填。将在单个请求中获取尽可能多的历史数据。
-
backfill
(默认:True
)在断开连接/重新连接周期后执行回填。间隙持续时间将用于下载尽可能少的数据
-
backfill_from
(默认:None
)可以传递附加数据源来进行初始回填。一旦数据源用尽,并且如果需要,将从 IB 进行回填。理想情况下,这意味着从已存储的源(如磁盘上的文件)进行回填,但不限于此。
-
latethrough
(默认:False
)如果数据源被重采样/重播,一些 ticks 可能来得太晚,已经交付的重采样/重播 bar 了。如果设置为
True
,那些 ticks 将无论如何通过。检查重采样文档以了解如何考虑这些 ticks。
这种情况可能特别发生在
IBStore
实例中timeoffset
设置为False
,且 TWS 服务器时间与本地计算机时间不同步时 -
tradename
(默认:None
)对于某些特定情况很有用,比如CFD
,其中价格由一种资产提供,交易发生在另一种资产上。-
SPY-STK-SMART-USD -> 标普 500 ETF(将被指定为
dataname
) -
SPY-CFD-SMART-USD -> 对应的 CFD 提供的不是价格跟踪,而是交易资产(指定为
tradename
)
-
参数中的默认值是允许类似 \
TICKER这样的东西,其中参数
sectype(默认:
STK)和
exchange(默认:
SMART`)被应用。
一些资产如 AAPL
需要完整的规范,包括 currency
(默认:‘’),而其他资产如 TWTR
可以直接传递。
-
AAPL-STK-SMART-USD
将是 dataname 的完整规范或者:
IBData
作为IBData(dataname='AAPL', currency='USD')
,它使用默认值(STK
和SMART
),并覆盖货币为USD
Oanda
原文:
www.backtrader.com/docu/live/oanda/oanda/
与 Oanda 的集成支持:
-
实时数据馈送
-
实时交易
要求
-
oandapy
使用以下命令安装:
pip install git+https://github.com/oanda/oandapy.git
-
pytz
(可选且不推荐)鉴于外汇市场的全球性和 24 小时运作的特点,选择使用
UTC
时间。如果愿意,您仍然可以使用您期望的输出时区。
示例代码
源代码包含完整示例:
samples/oandatest/oandatest.py
Oanda - 存储
存储是实时数据提要/交易支持的关键,提供了Oanda API 与数据提要和经纪人代理的需求之间的适配层。
-
提供访问使用方法获取经纪人实例:
OandaStore.getbroker(*args, **kwargs)
-
提供访问数据提要实例的方法
OandaStore.getedata(\*args, **kwargs)
在这种情况下,许多
**kwargs
与数据提要(例如dataname
、fromdate
、todate
、sessionstart
、sessionend
、timeframe
、compression
)是共同的数据可能提供其他参数。请查看下面的参考资料。
强制性参数
为了成功连接到Oanda,以下参数是强制性的:
-
token
(默认值:None
):API 访问令牌 -
account
(默认值:None
):账户 ID
这些由Oanda提供
是否连接到测试服务器或真实服务器,请使用:
practice
(默认值:False
):使用测试环境
必须定期检查账户以获取现金和价值。刷新周期可以通过以下方式控制:
account_tmout
(默认值:10.0
):帐户价值/现金刷新周期
Oanda 提要
实例化数据:
-
根据 Oanda 的指南传递符号
- 根据 Oanda 的指南,EUR/USDD必须指定为
EUR_USD
。实例化如下:
data = oandastore.getdata(dataname='EUR_USD', ...)`
- 根据 Oanda 的指南,EUR/USDD必须指定为
时间管理
除非将tz
参数(pytz 兼容对象)传递给数据提要,否则所有时间输出均为UTC
格式,如上所述。
回填
backtrader对Oanda没有特殊要求。对于小时间框架,在测试服务器上由Oanda返回的回填长度为500
条
OandaBroker - 实时交易
使用经纪人
要使用OandaBroker,必须替换由cerebro创建的标准经纪人模拟实例。
使用Store模型(首选):
import backtrader as bt
cerebro = bt.Cerebro()
oandastore = bt.stores.OandaStore()
cerebro.broker = oandastore.getbroker() # or cerebro.setbroker(...)
经纪人 - 初始持仓
经纪人支持一个参数:
-
use_positions
(默认值:True
):连接到经纪人提供商时,使用现有持仓来启动经纪人。在实例化时设置为
False
,以忽略任何现有持仓
操作
关于标准用法没有变化。只需使用策略中可用的方法(详见Strategy
参考资料以获取完整解释)
-
buy
-
sell
-
close
-
cancel
订单执行类型
Oanda几乎支持backtrader所需的所有订单执行类型,但不包括Close。
因此,订单执行类型受到限制:
-
Order.Market
-
Order.Limit
-
Order.Stop
-
Order.StopLimit
(使用 Stop 和 upperBound / lowerBound 价格) -
Order.StopTrail
-
Bracket 订单受到支持,使用
takeprofit
和stoploss
订单成员并在内部创建模拟订单。
订单有效性
在回测期间(使用 valid
为 buy
和 sell
)可用的相同的有效性概念可用,并且具有相同的含义。因此,对于以下值,Oanda Orders 的 valid
参数将如下翻译:
-
None
转换为 Good Til Cancelled因为未指定有效性,所以理解为订单必须有效直至取消
-
datetime/date
转换为 Good Til Date -
timedelta(x)
转换为 Good Til Date(这里timedelta(x) != timedelta()
)这被解释为信号,要求订单从
now
+timedelta(x)
开始有效。 -
timedelta() 或 0
转换为 Session已传递一个值(而不是
None
)但为 Null,并被解释为当前 day(会话)有效的订单
通知
标准的 Order
状态将通过 notify_order
方法(如果已重写)通知到策略。
-
Submitted
- 订单已发送到 TWS -
Accepted
- 订单已下达 -
Rejected
- 用于实际拒绝和在订单创建期间未知其他状态时使用 -
Partial
- 部分执行已经发生 -
Completed
- 订单已完全执行 -
Canceled
(或Cancelled
) -
Expired
- 当订单因到期而取消时
参考
OandaStore
class backtrader.stores.OandaStore()
单例类,用于控制与 Oanda 的连接。
参数:
-
token
(默认值:None
):API 访问令牌 -
account
(默认值:None
):账户 ID -
practice
(默认值:False
):使用测试环境 -
account_tmout
(默认值:10.0
):账户价值/现金刷新的刷新周期
OandaBroker
class backtrader.brokers.OandaBroker(**kwargs)
Oanda 的经纪人实现。
此类将来自 Oanda 的订单/持仓映射到 backtrader
的内部 API。
参数:
-
use_positions
(默认值:True
):连接到经纪人提供者时,使用现有仓位启动经纪人。在实例化期间设置为
False
以忽略任何现有仓位
OandaData
class backtrader.feeds.OandaData(**kwargs)
Oanda 数据源。
参数:
-
qcheck
(默认值:0.5
)如果未收到数据,则在苏醒的秒数内将给出重新采样/重播数据包的机会,并将通知传递给链上
-
historical
(默认值:False
)如果设置为
True
,数据源将在第一次下载数据后停止。将使用标准数据源参数
fromdate
和todate
作为参考。如果请求的持续时间大于 IB 允许的时间跨度/压缩所选择的数据的持续时间,数据源将进行多次请求。
-
backfill_start
(默认值:True
)在开始时执行回填。将通过单个请求获取最大可能的历史数据。
-
backfill
(默认:True
)在断开/重新连接周期后执行回填。间隙持续时间将用于下载可能的最小数据量
-
backfill_from
(默认:None
)可以传递额外的数据源来进行初始的回填层。一旦数据源用尽并且如果请求,将从 IB 进行回填。理想情况下,这是为了从已存储的源(如磁盘上的文件)进行回填,但不限于此。
-
bidask
(默认:True
)如果为
True
,则历史/回填请求将从服务器请求bid/ask价格如果为
False
,则将请求midpoint -
useask
(默认:False
)如果为
True
,则将使用bidask价格的ask部分,而不是默认的bid使用方式 -
includeFirst
(默认:True
)通过直接设置参数到 Oanda API 调用来影响历史/回填请求的第一个柱条的交付
-
reconnect
(默认:True
)当网络连接断开时重新连接
-
reconnections
(默认:-1
)重新连接尝试的次数:
-1
表示永远 -
reconntimeout
(默认:5.0
)在重新连接尝试之间等待的时间(秒)
此数据源仅支持timeframe
和compression
的以下映射,这些映射符合 OANDA API 开发人员指南中的定义:
(TimeFrame.Seconds, 5): 'S5',
(TimeFrame.Seconds, 10): 'S10',
(TimeFrame.Seconds, 15): 'S15',
(TimeFrame.Seconds, 30): 'S30',
(TimeFrame.Minutes, 1): 'M1',
(TimeFrame.Minutes, 2): 'M3',
(TimeFrame.Minutes, 3): 'M3',
(TimeFrame.Minutes, 4): 'M4',
(TimeFrame.Minutes, 5): 'M5',
(TimeFrame.Minutes, 10): 'M10',
(TimeFrame.Minutes, 15): 'M15',
(TimeFrame.Minutes, 30): 'M30',
(TimeFrame.Minutes, 60): 'H1',
(TimeFrame.Minutes, 120): 'H2',
(TimeFrame.Minutes, 180): 'H3',
(TimeFrame.Minutes, 240): 'H4',
(TimeFrame.Minutes, 360): 'H6',
(TimeFrame.Minutes, 480): 'H8',
(TimeFrame.Days, 1): 'D',
(TimeFrame.Weeks, 1): 'W',
(TimeFrame.Months, 1): 'M',
任何其他组合都将被拒绝