原文:
www.backtrader.com/
期货和现货补偿
原文:
www.backtrader.com/docu/order-creation-execution/futurespot/future-vs-spot/
发布1.9.32.116
添加了对在社区中提出的一个有趣用例的支持。
-
通过未来开始交易,其中包括实物交割
-
让指标告诉你些什么。
-
如果需要,通过对现货价格进行操作来关闭头寸,有效地取消物理交割,无论是为了收到货物还是为了交付货物(并有望获利)。
未来到期日与现货价格操作在同一天发生。
这意味着:
-
平台从两种不同的资产中获取数据点。
-
平台必须以某种方式理解资产之间的关系,并且操作现货价格将关闭在期货上开放的头寸。
实际上,未来并没有关闭,只有物理交割是补偿的。
使用补偿概念,backtrader
添加了一种让用户向平台传达在一个数据源上的事情将对另一个数据源产生补偿效果的方法。使用模式
import backtrader as bt
cerebro = bt.Cerebro()
data0 = bt.feeds.MyFavouriteDataFeed(dataname='futurename')
cerebro.adddata(data0)
data1 = bt.feeds.MyFavouriteDataFeed(dataname='spotname')
data1.compensate(data0) # let the system know ops on data1 affect data0
cerebro.adddata(data1)
...
cerebro.run()
将所有内容放在一起
例子总是值得一千篇文章,所以让我们把所有的东西放在一起。
-
使用
backtrader
源中的标准示例数据之一。这将是未来的。 -
通过重复使用相同的数据源并添加一个过滤器,该过滤器将随机将价格移动到上/下几个点,以创建一个价差,来模拟类似但不同的价格。如下所示:
# The filter which changes the close price def close_changer(data, *args, **kwargs): data.close[0] += 50.0 * random.randint(-1, 1) return False # length of stream is unchanged`
-
将绘图到相同的轴上会混合默认包含的
BuyObserver
标记,因此标准观察器将被禁用,并手动重新添加以使用不同的每个数据标记进行绘图。 -
位置将随机输入,并在 10 天后退出。
这与期货到期期限不符,但这只是为了实现功能,而不是检查交易日历。
!!! 注意
A simulation including execution on the spot price on the day of
future expiration would require activating `cheat-on-close` to
make sure the orders are executed when the future expires. This is
not needed in this sample, because the expiration is being chosen
at random.
-
注意策略
-
buy
操作在data0
上执行。 -
sell
操作在data1
上执行。
class St(bt.Strategy): def __init__(self): bt.obs.BuySell(self.data0, barplot=True) # done here for BuySellArrows(self.data1, barplot=True) # different markers per data def next(self): if not self.position: if random.randint(0, 1): self.buy(data=self.data0) self.entered = len(self) else: # in the market if (len(self) - self.entered) >= 10: self.sell(data=self.data1)`
-
执行:
$ ./future-spot.py --no-comp
使用此图形输出。
它是有效的:
-
buy
操作以指向上的绿色三角形的形式发出信号,图例告诉我们它们属于data0
,如预期的那样。 -
sell
操作以向下箭头的形式发出信号,图例告诉我们它们属于data1
,如预期的那样。 -
交易正在关闭,即使它们是以
data0
开仓并以data1
平仓,也能实现期望的效果(在现实生活中,这意味着避免通过期货获得的商品的实物交割)。
如果没有补偿,人们可能会想象会发生什么。让我们来做一下:
$ ./future-spot.py --no-comp
输出结果
很明显,这是失败的:
-
逻辑期望
data0
上的仓位在data1
上的操作关闭,并且只有在市场中没有仓位时才开放data0
上的仓位。 -
但补偿已被停用,并且对
data0
的初始操作(绿色三角形)从未关闭,因此不会启动任何其他操作,而data1
上的空头仓位开始累积。
示例用法
$ ./future-spot.py --help
usage: future-spot.py [-h] [--no-comp]
Compensation example
optional arguments:
-h, --help show this help message and exit
--no-comp
示例代码
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import random
import backtrader as bt
# The filter which changes the close price
def close_changer(data, *args, **kwargs):
data.close[0] += 50.0 * random.randint(-1, 1)
return False # length of stream is unchanged
# override the standard markers
class BuySellArrows(bt.observers.BuySell):
plotlines = dict(buy=dict(marker='$\u21E7$', markersize=12.0),
sell=dict(marker='$\u21E9$', markersize=12.0))
class St(bt.Strategy):
def __init__(self):
bt.obs.BuySell(self.data0, barplot=True) # done here for
BuySellArrows(self.data1, barplot=True) # different markers per data
def next(self):
if not self.position:
if random.randint(0, 1):
self.buy(data=self.data0)
self.entered = len(self)
else: # in the market
if (len(self) - self.entered) >= 10:
self.sell(data=self.data1)
def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()
dataname = '../../datas/2006-day-001.txt' # data feed
data0 = bt.feeds.BacktraderCSVData(dataname=dataname, name='data0')
cerebro.adddata(data0)
data1 = bt.feeds.BacktraderCSVData(dataname=dataname, name='data1')
data1.addfilter(close_changer)
if not args.no_comp:
data1.compensate(data0)
data1.plotinfo.plotmaster = data0
cerebro.adddata(data1)
cerebro.addstrategy(St) # sample strategy
cerebro.addobserver(bt.obs.Broker) # removed below with stdstats=False
cerebro.addobserver(bt.obs.Trades) # removed below with stdstats=False
cerebro.broker.set_coc(True)
cerebro.run(stdstats=False) # execute
cerebro.plot(volume=False) # and plot
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description=('Compensation example'))
parser.add_argument('--no-comp', required=False, action='store_true')
return parser.parse_args(pargs)
if __name__ == '__main__':
runstrat()
StopTrail(Limit)
原文:
www.backtrader.com/docu/order-creation-execution/trail/stoptrail/
版本1.9.35.116
将StopTrail
和StopTrailLimit
订单执行类型添加到回测武器库。
注意
这仅在回测中实现,尚未针对实时经纪人实现
注意
用版本1.9.36.116
更新。交互式经纪人支持StopTrail
、StopTrailLimit
和OCO
。
-
OCO
总是将第 1 个订单作为参数oco
指定为一组 -
StopTrailLimit
:经纪人模拟和IB
经纪人具有相同的行为。指定:price
作为初始触发价格(也指定trailamount
),然后plimi
作为初始限价。两者之间的差异将确定limitoffset
(限价与停止触发价格之间的距离)
用法模式完全集成到策略实例的标准buy
、sell
和close
市场操作方法中。注意:
-
指示希望的执行类型,如
exectype=bt.Order.StopTrail
-
以及是否必须使用固定距离或百分比距离计算跟踪价格
-
固定距离:
trailamount=10
-
百分比距离:
trailpercent=0.02
(即:2%
)
-
如果通过发出buy
进入市场,这是sell
与StopTrail
和trailamount
一起的操作:
-
如果未指定
price
,则使用最新的close
价格 -
trailamount
从价格中减去以找到stop
(或触发)价格 -
下一个迭代的经纪人检查触发价格是否已达到
-
如果是:订单以
Market
执行类型的方法执行 -
如果否,则使用最新的
close
价格减去trailamount
距离重新计算stop
价格 -
如果新价格上涨,则更新
-
如果新价格将下跌(或根本不变),则将其丢弃
-
也就是说:跟踪止损价格随价格上涨而上涨,但如果价格开始下跌,则保持不变,以潜在地获利。
如果通过sell
进入市场,然后通过StopTrail
发出buy
订单只是做相反的操作,即:价格向下跟随。
一些用法模式
# For a StopTrail going downwards
# last price will be used as reference
self.buy(size=1, exectype=bt.Order.StopTrail, trailamount=0.25)
# or
self.buy(size=1, exectype=bt.Order.StopTrail, price=10.50, trailamount=0.25)
# For a StopTrail going upwards
# last price will be used as reference
self.sell(size=1, exectype=bt.Order.StopTrail, trailamount=0.25)
# or
self.sell(size=1, exectype=bt.Order.StopTrail, price=10.50, trailamount=0.25)
也可以指定trailpercent
而不是trailamount
,价格与价格的距离将被计算为价格的百分比
# For a StopTrail going downwards with 2% distance
# last price will be used as reference
self.buy(size=1, exectype=bt.Order.StopTrail, trailpercent=0.02)
# or
self.buy(size=1, exectype=bt.Order.StopTrail, price=10.50, trailpercent=0.0.02)
# For a StopTrail going upwards with 2% difference
# last price will be used as reference
self.sell(size=1, exectype=bt.Order.StopTrail, trailpercent=0.02)
# or
self.sell(size=1, exectype=bt.Order.StopTrail, price=10.50, trailpercent=0.02)
对于StopTrailLimit
-
唯一的区别在于当触发跟踪止损价格时会发生什么。
-
在这种情况下,订单以
Limit
订单的形式执行(与StopLimit
订单的行为相同,但在这种情况下,触发价格是动态的)注意:必须指定
plimit=x.x
给buy
或sell
,这将是限价注意:限价不像停止/触发价格那样动态更改
例如总是值得一看,因此通常的backtrader示例,其中
-
使用移动平均线上穿进入市场多头
-
使用跟踪止损退出市场
使用50
点固定价格距离的执行
$ ./trail.py --plot --strat trailamount=50.0
这产生了以下图表
和以下输出:
**************************************************
2005-02-14,3075.76,3025.76,3025.76
----------
2005-02-15,3086.95,3036.95,3036.95
2005-02-16,3068.55,3036.95,3018.55
2005-02-17,3067.34,3036.95,3017.34
2005-02-18,3072.04,3036.95,3022.04
2005-02-21,3063.64,3036.95,3013.64
...
...
**************************************************
2005-05-19,3051.79,3001.79,3001.79
----------
2005-05-20,3050.45,3001.79,3000.45
2005-05-23,3070.98,3020.98,3020.98
2005-05-24,3066.55,3020.98,3016.55
2005-05-25,3059.84,3020.98,3009.84
2005-05-26,3086.08,3036.08,3036.08
2005-05-27,3084.0,3036.08,3034.0
2005-05-30,3096.54,3046.54,3046.54
2005-05-31,3076.75,3046.54,3026.75
2005-06-01,3125.88,3075.88,3075.88
2005-06-02,3131.03,3081.03,3081.03
2005-06-03,3114.27,3081.03,3064.27
2005-06-06,3099.2,3081.03,3049.2
2005-06-07,3134.82,3084.82,3084.82
2005-06-08,3125.59,3084.82,3075.59
2005-06-09,3122.93,3084.82,3072.93
2005-06-10,3143.85,3093.85,3093.85
2005-06-13,3159.83,3109.83,3109.83
2005-06-14,3162.86,3112.86,3112.86
2005-06-15,3147.55,3112.86,3097.55
2005-06-16,3160.09,3112.86,3110.09
2005-06-17,3178.48,3128.48,3128.48
2005-06-20,3162.14,3128.48,3112.14
2005-06-21,3179.62,3129.62,3129.62
2005-06-22,3182.08,3132.08,3132.08
2005-06-23,3190.8,3140.8,3140.8
2005-06-24,3161.0,3140.8,3111.0
...
...
...
**************************************************
2006-12-19,4100.48,4050.48,4050.48
----------
2006-12-20,4118.54,4068.54,4068.54
2006-12-21,4112.1,4068.54,4062.1
2006-12-22,4073.5,4068.54,4023.5
2006-12-27,4134.86,4084.86,4084.86
2006-12-28,4130.66,4084.86,4080.66
2006-12-29,4119.94,4084.86,4069.94
而不是等待通常的下穿模式,系统使用跟踪止损退出市场。例如,让我们看看第一次操作。
-
进入多头时的收盘价:
3075.76
-
系统计算的跟踪止损价:
3025.76
(相距50
个单位) -
样本计算的跟踪止损价:
3025.76
(每行显示的最后价格)
在第一次计算之后:
-
收盘价上涨至
3086.95
,止损价调整为3036.95
-
以下收盘价不超过
3086.95
,触发价格不变
在其他两次操作中也可以看到相同的模式。
为了比较,仅使用30
点固定距离的执行(仅图表)
$ ./trail.py --plot --strat trailamount=30.0
和图表
最后一次执行后跟随trailpercent=0.02
$ ./trail.py --plot --strat trailpercent=0.02
相应的图表。
示例用法
$ ./trail.py --help
usage: trail.py [-h] [--data0 DATA0] [--fromdate FROMDATE] [--todate TODATE]
[--cerebro kwargs] [--broker kwargs] [--sizer kwargs]
[--strat kwargs] [--plot [kwargs]]
StopTrail Sample
optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data to read in (default:
../../datas/2005-2006-day-001.txt)
--fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
--todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
--cerebro kwargs kwargs in key=value format (default: )
--broker kwargs kwargs in key=value format (default: )
--sizer kwargs kwargs in key=value format (default: )
--strat kwargs kwargs in key=value format (default: )
--plot [kwargs] kwargs in key=value format (default: )
示例代码
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import backtrader as bt
class St(bt.Strategy):
params = dict(
ma=bt.ind.SMA,
p1=10,
p2=30,
stoptype=bt.Order.StopTrail,
trailamount=0.0,
trailpercent=0.0,
)
def __init__(self):
ma1, ma2 = self.p.ma(period=self.p.p1), self.p.ma(period=self.p.p2)
self.crup = bt.ind.CrossUp(ma1, ma2)
self.order = None
def next(self):
if not self.position:
if self.crup:
o = self.buy()
self.order = None
print('*' * 50)
elif self.order is None:
self.order = self.sell(exectype=self.p.stoptype,
trailamount=self.p.trailamount,
trailpercent=self.p.trailpercent)
if self.p.trailamount:
tcheck = self.data.close - self.p.trailamount
else:
tcheck = self.data.close * (1.0 - self.p.trailpercent)
print(','.join(
map(str, [self.datetime.date(), self.data.close[0],
self.order.created.price, tcheck])
)
)
print('-' * 10)
else:
if self.p.trailamount:
tcheck = self.data.close - self.p.trailamount
else:
tcheck = self.data.close * (1.0 - self.p.trailpercent)
print(','.join(
map(str, [self.datetime.date(), self.data.close[0],
self.order.created.price, tcheck])
)
)
def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()
# Data feed kwargs
kwargs = dict()
# Parse from/to-date
dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S'
for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']):
if a:
strpfmt = dtfmt + tmfmt * ('T' in a)
kwargs[d] = datetime.datetime.strptime(a, strpfmt)
# Data feed
data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **kwargs)
cerebro.adddata(data0)
# Broker
cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')'))
# Sizer
cerebro.addsizer(bt.sizers.FixedSize, **eval('dict(' + args.sizer + ')'))
# Strategy
cerebro.addstrategy(St, **eval('dict(' + args.strat + ')'))
# Execute
cerebro.run(**eval('dict(' + args.cerebro + ')'))
if args.plot: # Plot if requested to
cerebro.plot(**eval('dict(' + args.plot + ')'))
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description=(
'StopTrail Sample'
)
)
parser.add_argument('--data0', default='../../datas/2005-2006-day-001.txt',
required=False, help='Data to read in')
# Defaults for dates
parser.add_argument('--fromdate', required=False, default='',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')
parser.add_argument('--todate', required=False, default='',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')
parser.add_argument('--cerebro', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--broker', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--sizer', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--strat', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--plot', required=False, default='',
nargs='?', const='{}',
metavar='kwargs', help='kwargs in key=value format')
return parser.parse_args(pargs)
if __name__ == '__main__':
runstrat()
经纪人
经纪人
原文:
www.backtrader.com/docu/broker/
参考
类backtrader.brokers.BackBroker()
经纪人模拟器
该模拟支持不同的订单类型,检查提交订单的现金需求与当前现金的情况,跟踪每次cerebro
迭代的现金和价值,并在不同的数据上保持当前位置。
cash根据仪器进行每次迭代调整,如期货
which a price change implies in real brokers the addition/substracion of
cash.
支持的订单类型:
-
Market
:在下一个柱子的第 1^(st)个 tick(即open
价格)执行 -
Close
:用于一日内,在该订单以会话最后一个柱的收盘价执行 -
Limit
:在会话期间看到给定的限价时执行 -
Stop
:如果看到给定的停止价格,则执行Market
订单 -
StopLimit
:如果看到给定的停止价格,则启动Limit
订单
因为经纪人是由Cerebro
实例化的,而且应该(大多数情况下)没有理由替换经纪人,所以实例的参数不受用户控制。要更改此参数,有两种选择:
-
手动创建此类的实例,并使用
cerebro.broker = instance
将实例设置为run
执行的经纪人 -
使用
set_xxx
通过cerebro.broker.set_xxx
来设置值,其中\
xxx`代表要设置的参数的名称
注意
cerebro.broker
是Cerebro
的getbroker
和setbroker
方法支持的property
参数:
-
cash
(默认值:10000
):起始现金 -
commission
(默认值:CommInfoBase(percabs=True)
)适用于所有资产的基础佣金方案 -
checksubmit
(默认值:True
):在将订单接受到系统之前检查保证金/现金 -
eosbar
(默认值:False
):对于一日内柱,如果某个柱具有与会话结束相同的time
,则将其视为会话结束。这通常不是情况,因为一些柱(最终拍卖)会在会话结束后几分钟内由许多交易所为许多产品产生 -
eosbar
(默认值:False
):对于一日内柱,如果某个柱具有与会话结束相同的time
,则将其视为会话结束。这通常不是情况,因为一些柱(最终拍卖)会在会话结束后几分钟内由许多交易所为许多产品产生 -
filler
(默认值:None
)一个带有签名的可调用函数:
callable(order, price, ago)
-
order
:显然是执行的订单。这提供了对数据(以及ohlc和volume值)、执行类型、剩余大小(order.executed.remsize
)和其他内容的访问。请检查
Order
文档和参考,以了解Order
实例中可用的内容 -
price
:订单将在ago
柱中执行的价格 -
ago
:用于与order.data
一起使用以提取ohlc和volume价格的索引。在大多数情况下,这将是0
,但对于Close
订单的一个特殊情况,这将是-1
。为了获得柱形体积(例如):
volume = order.data.voluume[ago]
可调用函数必须返回执行大小(大于等于 0 的值)
可调用函数当然可以是一个与前述签名匹配的对象
__call__
默认值为
None
时,订单将在一次性完全执行 -
-
slip_perc
(默认值:0.0
)应该使用的百分比绝对值(和正数)使买入/卖出订单的价格上涨/下跌注意:
-
0.01
是1%
-
0.001
是0.1%
-
-
slip_fixed
(默认值:0.0
)应使用的以单位(和正数)表示的百分比来使价格上涨/下跌以进行买入/卖出订单滑点注意:如果
slip_perc
不为零,则它优先于此。 -
slip_open
(默认值:False
)是否为订单执行滑点价格,其将特定使用下一个条的开盘价格。一个例子是使用下一个可用的刻度执行的Market
订单,即:该条的开盘价。这也适用于一些其他执行,因为逻辑试图检测开盘价格是否会与移动到新条时请求的价格/执行类型匹配。
-
slip_match
(默认值:True
)如果设置为
True
,则经纪人将通过将滑点限制在高/低
价格上来提供匹配,以防超出。如果为
False
,则经纪人不会以当前价格匹配订单,并将尝试在下一次迭代期间执行 -
slip_limit
(默认值:True
)Limit
订单,给定所请求的确切匹配价格,即使slip_match
为False
,也将匹配。此选项控制该行为。
如果为
True
,则Limit
订单将通过将价格限制在限价
/高/低
价格上来匹配如果
False
且滑点超过上限,则不会匹配 -
slip_out
(默认值:False
)即使价格跌出
高
-低
范围,也要提供滑点。 -
coc
(默认值:False
)Cheat-On-Close 将其设置为
True
并使用set_coc
启用matching a `Market` order to the closing price of the bar in which the order was issued. This is actually *cheating*, because the bar is *closed* and any order should first be matched against the prices in the next bar`
-
coo
(默认值:False
)Cheat-On-Open 将其设置为
True
并使用set_coo
启用matching a `Market` order to the opening price, by for example using a timer with `cheat` set to `True`, because such a timer gets executed before the broker has evaluated`
-
int2pnl
(默认值:True
)将生成的利息(如果有)分配给减少持仓的操作的损益。可能存在这种情况:不同的策略正在竞争,利息将以不确定的方式分配给其中任何一个。
-
shortcash
(默认值:True
)如果为 True,则在对类似股票的资产进行空头操作时会增加现金,并且计算出的资产值将为负。
如果为
False
,则现金将被扣除为操作成本,并且计算出的值将为正,以保持相同数量 -
fundstartval
(默认值:100.0
)此参数控制以类似基金的方式测量绩效的起始值,即:可以添加和扣除现金以增加股份数量。绩效不是使用投资组合的净资产值来衡量的,而是使用基金的价值来衡量。
-
fundmode
(默认值:False
)如果设置为
True
,分析器如TimeReturn
可以根据基金价值而不是总净资产自动计算回报率
set_cash(cash)
设置 cash
参数(别名:setcash
)
get_cash()
返回当前现金(别名:getcash
)
get_value(datas=None, mkt=False, lever=False)
返回给定数据的投资组合价值(如果数据为 None
,则将返回总投资组合价值)(别名:getvalue
)
set_eosbar(eosbar)
设置 eosbar
参数(别名:seteosbar
)
set_checksubmit(checksubmit)
设置 checksubmit
参数
set_filler(filler)
为成交量填充执行设置一个成交量填充器
set_coc(coc)
配置“在订单栏上购买收盘价”的“Cheat-On-Close”方法
set_coo(coo)
配置“在订单栏上购买开盘价”的“Cheat-On-Open”方法
set_int2pnl(int2pnl)
配置利息分配给损益
set_fundstartval(fundstartval)
设置基金样式表现追踪器的起始值
set_slippage_perc(perc, slip_open=True, slip_limit=True, slip_match=True, slip_out=False)
配置滑点以百分比为基础
set_slippage_fixed(fixed, slip_open=True, slip_limit=True, slip_match=True, slip_out=False)
配置滑点以基于固定点进行
get_orders_open(safe=False)
返回仍然打开的订单的可迭代对象(未执行或部分执行)
返回的订单不得被更改。
如果需要订单操作,请将参数 safe
设置为 True
getcommissioninfo(data)
检索与给定 data
相关联的 CommissionInfo
方案
setcommission(commission=0.0, margin=None, mult=1.0, commtype=None, percabs=True, stocklike=False, interest=0.0, interest_long=False, leverage=1.0, automargin=False, name=None)
此方法使用参数为经纪人管理的资产设置 CommissionInfo
对象。请查阅 CommInfoBase
的参考资料
如果名称为 None
,则对于找不到其他 CommissionInfo
方案的资产,这将是默认值
addcommissioninfo(comminfo, name=None)
如果 name
为 None
,则添加一个 CommissionInfo
对象,如果 name
为 None
,则为所有资产设置默认值
getposition(data)
返回给定 data
的当前持仓状态(Position
实例)
get_fundshares()
返回基金模式中当前股份数量
get_fundvalue()
返回基金样式股票价值
add_cash(cash)
向系统添加/移除现金(使用负值来移除)
滑点
原文:
www.backtrader.com/docu/slippage/slippage/
回测无法保证真实市场条件。无论市场模拟有多好,在真实市场条件下都可能发生滑点。这意味着:
- 请求的价格可能无法匹配。
集成的回测经纪人支持滑点。以下参数可以传递给经纪人
-
slip_perc
(默认值:0.0
) 绝对百分比(正数),用于上下滑动买入/卖出订单的价格注意:
-
0.01
是1%
-
0.001
是0.1%
-
-
slip_fixed
(默认值:0.0
) 单位百分比(正数),用于上下滑动买入/卖出订单的价格注意:如果
slip_perc
不为零,则它优先于此。 -
slip_open
(默认值:False
) 是否为订单执行滑动价格,该价格特别使用下一个柱的开盘价格。一个示例是使用下一个可用刻度执行的Market
订单,即柱的开盘价格。这也适用于其他一些执行,因为逻辑试图检测开盘价格是否会在移动到新柱时匹配请求的价格/执行类型。
-
slip_match
(默认值:True
)如果为
True
,经纪人将通过在超出时将滑点限制在high/low
价格上来提供匹配。如果
False
,经纪人将不会根据当前价格匹配订单,并将在下一次迭代中尝试执行 -
slip_limit
(默认值:True
)即使
slip_match
为False
,给定请求的精确匹配价格的Limit
订单也会被匹配。此选项控制该行为。
如果为
True
,则Limit
订单将通过将价格限制在limit
/high/low
价格上来匹配如果
False
并且滑点超过上限,则不会匹配 -
slip_out
(默认值:False
)即使价格超出
high
-low
范围,也提供滑点。
工作原理
为了决定何时应用滑点,订单执行类型被考虑在内:
-
Close
- 不应用滑点此订单与
close
价格匹配,此价格是当天的最后一个价格。滑点不会发生,因为订单只能在会话的最后一个刻度发生,这是一个唯一的价格,没有容忍度。 -
Market
- 滑点被应用请检查
slip_open
异常。因为Market
订单将与下一个柱的开盘价格匹配。 -
Limit
- 滑点按照这个逻辑应用-
如果匹配价格将是开盘价格,则根据参数
slip_open
应用滑点。如果应用,则价格永远不会比请求的limit
价格更差 -
如果匹配价格不是
limit
价格,则应用滑点,并在high/low
上限制。在这种情况下,slip_mlimit
适用于决定是否在超出上限时会发生匹配 -
如果匹配价格是
limit
价格,则不会应用滑点
-
-
Stop
- 一旦订单被触发,与市价
订单相同的逻辑将适用 -
StopLimit
- 一旦订单被触发,与限价
订单相同的逻辑将适用
该方法试图在模拟和可用数据的限制内提供尽可能逼真的方法
配置滑点
经纪人已经由每个运行的智能交易引擎实例化,具有默认参数。有两种方法可以更改其行为:
-
使用方法来配置滑点
BackBroker.set_slippage_perc(perc, slip_open=True, slip_limit=True, slip_match=True, slip_out=False)
配置滑点为基于百分比的
BackBroker.set_slippage_fixed(fixed, slip_open=True, slip_limit=True, slip_match=True, slip_out=False)
配置滑点为固定点位基础
-
将经纪人替换为:
import backtrader as bt cerebro = bt.Cerebro() cerebro.broker = bt.brokers.BackBroker(slip_perc=0.005) # 0.5%`
实际示例
源代码包含一个示例,该示例使用了执行类型为市价
的订单以及使用信号的多头/空头方法。这应该能帮助理解其逻辑。
一个没有滑点并且具有初始图表供以后参考的运行:
$ ./slippage.py --plot
01 2005-03-22 23:59:59 SELL Size: -1 / Price: 3040.55
02 2005-04-11 23:59:59 BUY Size: +1 / Price: 3088.47
03 2005-04-11 23:59:59 BUY Size: +1 / Price: 3088.47
04 2005-04-19 23:59:59 SELL Size: -1 / Price: 2948.38
05 2005-04-19 23:59:59 SELL Size: -1 / Price: 2948.38
06 2005-05-19 23:59:59 BUY Size: +1 / Price: 3034.88
...
35 2006-12-19 23:59:59 BUY Size: +1 / Price: 4121.01
同样的运行使用了配置为1.5%
的滑点:
$ ./slippage.py --slip_perc 0.015
01 2005-03-22 23:59:59 SELL Size: -1 / Price: 3040.55
02 2005-04-11 23:59:59 BUY Size: +1 / Price: 3088.47
03 2005-04-11 23:59:59 BUY Size: +1 / Price: 3088.47
04 2005-04-19 23:59:59 SELL Size: -1 / Price: 2948.38
05 2005-04-19 23:59:59 SELL Size: -1 / Price: 2948.38
06 2005-05-19 23:59:59 BUY Size: +1 / Price: 3034.88
...
35 2006-12-19 23:59:59 BUY Size: +1 / Price: 4121.01
没有变动。这是该场景的预期行为。
-
执行类型:
市价
-
而
slip_open
没有被设置为True
市价
订单与下一根柱的开盘价格匹配,我们不允许open
价格被移动。
将slip_open
设置为True
的一个运行设置:
$ ./slippage.py --slip_perc 0.015 --slip_open
01 2005-03-22 23:59:59 SELL Size: -1 / Price: 3021.66
02 2005-04-11 23:59:59 BUY Size: +1 / Price: 3088.47
03 2005-04-11 23:59:59 BUY Size: +1 / Price: 3088.47
04 2005-04-19 23:59:59 SELL Size: -1 / Price: 2948.38
05 2005-04-19 23:59:59 SELL Size: -1 / Price: 2948.38
06 2005-05-19 23:59:59 BUY Size: +1 / Price: 3055.14
...
35 2006-12-19 23:59:59 BUY Size: +1 / Price: 4121.01
人们可以立即看到价格已经变动。并且分配的价格比操作 35 的最坏或相等。这不是复制粘贴错误
-
2016-12-19 的
open
和high
是相同的。价格不能被推高到
high
之上,因为那将意味着返回一个不存在的价格。
当然,backtrader允许匹配超出high
-low
范围的价格,如果愿意的话,可以激活slip_out
进行运行:
$ ./slippage.py --slip_perc 0.015 --slip_open --slip_out
01 2005-03-22 23:59:59 SELL Size: -1 / Price: 2994.94
02 2005-04-11 23:59:59 BUY Size: +1 / Price: 3134.80
03 2005-04-11 23:59:59 BUY Size: +1 / Price: 3134.80
04 2005-04-19 23:59:59 SELL Size: -1 / Price: 2904.15
05 2005-04-19 23:59:59 SELL Size: -1 / Price: 2904.15
06 2005-05-19 23:59:59 BUY Size: +1 / Price: 3080.40
...
35 2006-12-19 23:59:59 BUY Size: +1 / Price: 4182.83
用于匹配的匹配表达式将是:哦天呐!(我的天啊!)。价格明显超出了范围。只需看看操作 35,该操作在4182.83
处匹配。快速检查本文档中的图表可以看到该资产从未接近过该价格。
slip_match
的默认值为True
,这意味着backtrader提供了一个匹配,无论是带上限价还是不带上限价,如上所示。让我们将其禁用:
$ ./slippage.py --slip_perc 0.015 --slip_open --no-slip_match
01 2005-04-15 23:59:59 SELL Size: -1 / Price: 3028.10
02 2005-05-18 23:59:59 BUY Size: +1 / Price: 3029.40
03 2005-06-01 23:59:59 BUY Size: +1 / Price: 3124.03
04 2005-10-06 23:59:59 SELL Size: -1 / Price: 3365.57
05 2005-10-06 23:59:59 SELL Size: -1 / Price: 3365.57
06 2005-12-01 23:59:59 BUY Size: +1 / Price: 3499.95
07 2005-12-01 23:59:59 BUY Size: +1 / Price: 3499.95
08 2006-02-28 23:59:59 SELL Size: -1 / Price: 3782.71
09 2006-02-28 23:59:59 SELL Size: -1 / Price: 3782.71
10 2006-05-23 23:59:59 BUY Size: +1 / Price: 3594.68
11 2006-05-23 23:59:59 BUY Size: +1 / Price: 3594.68
12 2006-11-27 23:59:59 SELL Size: -1 / Price: 3984.37
13 2006-11-27 23:59:59 SELL Size: -1 / Price: 3984.37
天啊!从 35 降到了 13。原因是:
停用slip_match
将阻止匹配操作,如果滑点将匹配价格推高到high
之上或推低到柱的low
之下。看起来,随着请求的滑点为1.5%
,大约有 22 个操作无法执行。
这些示例应该已经展示了不同的滑点选项是如何共同工作的。
以开盘作弊
原文:
www.backtrader.com/docu/cerebro/cheat-on-open/cheat-on-open/
版本 1.9.44.116
增加了对 Cheat-On-Open
的支持。这似乎是对那些全情投入的人的一项需求功能,他们在柱结束后做了计算,但期望与open
价格匹配。
当开盘价格出现差距(向上或向下,取决于buy
或sell
是否生效)并且现金不足以进行全情投入操作时,此用例会失败。这迫使经纪人拒绝操作。
虽然人们可以尝试用积极的[1]
索引方法来展望未来,但这需要预加载数据,而这并不总是可用的。
模式:
cerebro = bt.Cerebro(cheat_on_open=True)
这个:
-
在系统中激活了额外的周期,该周期调用策略中的
next_open
、nextstart_open
和prenext_open
方法。决定增加一个额外的方法系列是为了清晰地区分常规方法和作弊模式之间的区别。常规方法是基于所检查的价格不再可用且未来未知的基础上运行的,而作弊模式则是另一种操作。
这也避免了对常规
next
方法进行两次调用。
当处于xxx_open
方法内部时,以下情况成立:
-
指标尚未重新计算,并保留了上一个周期中在等效的
xxx
常规方法中最后看到的值。 -
经纪人尚未评估新周期的待处理订单,可以引入新订单,如果可能的话将进行评估。
请注意:
-
Cerebro
还有一个名为broker_coo
(默认为True
)的参数,告诉cerebro
如果已经激活了cheat-on-open
,它应该尽可能地在经纪人中也激活它。仿真经纪人有一个名为:
coo
的参数和一个设置它的方法名为set_coo
尝试作弊开盘
下面的示例具有具有 2 种不同行为的策略:
-
如果cheat-on-open是True,它将只从
next_open
操作 -
如果cheat-on-open是False,它将只从
next
操作
在这两种情况下,匹配价格必须是相同的
-
如果不作弊,订单将在前一天结束时发布,并将与下一个输入价格(即
open
价格)匹配 -
如果作弊,订单将在执行当天发布。因为订单是在经纪人评估订单之前发布的,所以它也将与下一个输入价格(即
open
价格)匹配。第二种情况允许计算全情投入策略的确切赌注,因为可以直接访问当前的
open
价格。
在这两种情况下
- 当前的
open
和close
价格将从next
打印出来。
常规执行:
$ ./cheat-on-open.py --cerebro cheat_on_open=False
...
2005-04-07 next, open 3073.4 close 3090.72
2005-04-08 next, open 3092.07 close 3088.92
Strat Len 68 2005-04-08 Send Buy, fromopen False, close 3088.92
2005-04-11 Buy Executed at price 3088.47
2005-04-11 next, open 3088.47 close 3080.6
2005-04-12 next, open 3080.42 close 3065.18
...
顺序:
-
在 2005-04-08close之后发布
-
它是在 2005-04-11 执行的,以
3088.47
的open
价格执行。
作弊执行:
$ ./cheat-on-open.py --cerebro cheat_on_open=True
...
2005-04-07 next, open 3073.4 close 3090.72
2005-04-08 next, open 3092.07 close 3088.92
2005-04-11 Send Buy, fromopen True, close 3080.6
2005-04-11 Buy Executed at price 3088.47
2005-04-11 next, open 3088.47 close 3080.6
2005-04-12 next, open 3080.42 close 3065.18
...
顺序:
-
在 2005-04-11 开盘 之前发布
-
它在 2005-04-11 执行,开盘价为
3088.47
而图表上整体的结果也是相同的。
结论
在开盘前作弊允许在开盘前发布订单,例如允许精确计算全仓情况下的股份。
样本用法
$ ./cheat-on-open.py --help
usage: cheat-on-open.py [-h] [--data0 DATA0] [--fromdate FROMDATE]
[--todate TODATE] [--cerebro kwargs] [--broker kwargs]
[--sizer kwargs] [--strat kwargs] [--plot [kwargs]]
Cheat-On-Open Sample
optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data to read in (default:
../../datas/2005-2006-day-001.txt)
--fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
--todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
--cerebro kwargs kwargs in key=value format (default: )
--broker kwargs kwargs in key=value format (default: )
--sizer kwargs kwargs in key=value format (default: )
--strat kwargs kwargs in key=value format (default: )
--plot [kwargs] kwargs in key=value format (default: )
样本来源
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import backtrader as bt
class St(bt.Strategy):
params = dict(
periods=[10, 30],
matype=bt.ind.SMA,
)
def __init__(self):
self.cheating = self.cerebro.p.cheat_on_open
mas = [self.p.matype(period=x) for x in self.p.periods]
self.signal = bt.ind.CrossOver(*mas)
self.order = None
def notify_order(self, order):
if order.status != order.Completed:
return
self.order = None
print('{} {} Executed at price {}'.format(
bt.num2date(order.executed.dt).date(),
'Buy' * order.isbuy() or 'Sell', order.executed.price)
)
def operate(self, fromopen):
if self.order is not None:
return
if self.position:
if self.signal < 0:
self.order = self.close()
elif self.signal > 0:
print('{} Send Buy, fromopen {}, close {}'.format(
self.data.datetime.date(),
fromopen, self.data.close[0])
)
self.order = self.buy()
def next(self):
print('{} next, open {} close {}'.format(
self.data.datetime.date(),
self.data.open[0], self.data.close[0])
)
if self.cheating:
return
self.operate(fromopen=False)
def next_open(self):
if not self.cheating:
return
self.operate(fromopen=True)
def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()
# Data feed kwargs
kwargs = dict()
# Parse from/to-date
dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S'
for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']):
if a:
strpfmt = dtfmt + tmfmt * ('T' in a)
kwargs[d] = datetime.datetime.strptime(a, strpfmt)
# Data feed
data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **kwargs)
cerebro.adddata(data0)
# Broker
cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')'))
# Sizer
cerebro.addsizer(bt.sizers.FixedSize, **eval('dict(' + args.sizer + ')'))
# Strategy
cerebro.addstrategy(St, **eval('dict(' + args.strat + ')'))
# Execute
cerebro.run(**eval('dict(' + args.cerebro + ')'))
if args.plot: # Plot if requested to
cerebro.plot(**eval('dict(' + args.plot + ')'))
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description=(
'Cheat-On-Open Sample'
)
)
parser.add_argument('--data0', default='../../datas/2005-2006-day-001.txt',
required=False, help='Data to read in')
# Defaults for dates
parser.add_argument('--fromdate', required=False, default='',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')
parser.add_argument('--todate', required=False, default='',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')
parser.add_argument('--cerebro', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--broker', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--sizer', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--strat', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--plot', required=False, default='',
nargs='?', const='{}',
metavar='kwargs', help='kwargs in key=value format')
return parser.parse_args(pargs)
if __name__ == '__main__':
runstrat()
Fillers
原文:
www.backtrader.com/docu/filler/
当涉及使用成交量执行订单时,backtrader 经纪人模拟具有默认策略:
- 忽略成交量
这基于两个前提:
-
在足够流动的市场中进行交易,以完全吸收一次性 买入/卖出 订单
-
真实成交量匹配需要真实世界
快速示例是
Fill or Kill
订单。即使到了 tick 分辨率并且有足够的 fill 量,backtrader 经纪人也无法知道市场上有多少额外的参与者,以区分这样一个订单是否会匹配以坚持Fill
部分,或者该订单是否应该Kill
。
但是 broker 可以接受 Volume Fillers,这些填充器确定在给定时间点使用多少成交量用于 order matching。
填充器的签名
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
向经纪人添加一个 Filler
最直接的方法是使用 set_filler
:
import backtrader as bt
cerebro = Cerebro()
cerebro.broker.set_filler(bt.broker.fillers.FixedSize())
第二种选择是完全替换 broker
,虽然这可能只适用于已重写部分功能的 BrokerBack
的子类:
import backtrader as bt
cerebro = Cerebro()
filler = bt.broker.fillers.FixedSize()
newbroker = bt.broker.BrokerBack(filler=filler)
cerebro.broker = newbroker
示例
backtrader 源代码中包含一个名为 volumefilling
的示例,允许测试一些集成的 fillers
(最初全部)
参考资料
class backtrader.fillers.FixedSize()
使用 百分比 的体积返回给定订单的执行大小。
此百分比通过参数 perc
设置
参数:
-
size
(默认值:None
)要执行的最大尺寸。如果执行时的实际体积小于大小,则也是限制如果此参数的值计算为 False,则整个条的体积将用于匹配订单
class backtrader.fillers.FixedBarPerc()
使用条中体积的 百分比 返回给定订单的执行大小。
此百分比通过参数 perc
设置
参数:
-
perc
(默认值:100.0
)(有效值:0.0 - 100.0
)用于执行订单的成交量栏的百分比
class backtrader.fillers.BarPointPerc()
返回给定订单的执行大小。成交量将在 high-low 范围内均匀分布,使用 minmov
进行分区。
从给定价格的分配交易量中,将使用 perc
百分比
参数:
-
minmov
(默认值:0.01
)最小价格变动。用于将范围 高-低 分割,以在可能的价格之间按比例分配交易量
-
perc
(默认值:100.0
)(有效值:0.0 - 100.0
)分配给订单执行价格的交易量百分比,用于匹配
位置
原文:
www.backtrader.com/docu/position/
通常在策略内部检查资产的持仓情况:
-
position
(一种属性)或getposition(data=None, broker=None)
这将返回由 cerebro 提供的默认
broker
中策略的datas[0]
上的位置
一个持仓只是指:
-
资产持有
size
-
平均价格为
price
它作为一种状态,并且例如可以用来决定是否要发出订单(例如:仅在没有持仓时才进入多头持仓)
参考:Position
类 backtrader.position.Position(size=0, price=0.0)
保持并更新持仓的大小和价格。该对象与任何资产无关,仅保留大小和价格。
成员属性:
* size (int): current size of the position
* price (float): current price of the position
Position 实例可以使用 len(position) 进行测试,以查看大小是否为空
交易
原文:
www.backtrader.com/docu/trade/
交易的定义:
-
当仪器中的持仓从 0 变为大小 X 时,交易就开始了,大小可以是正的/负的,分别表示多头/空头仓位)
-
当仓位从 X 变为 0 时,交易关闭。
以下两个动作:
-
正至负
-
负至正
实际上被视为:
-
一个交易已经关闭(仓位从 X 变为 0)
-
一个新交易已经打开(仓位从 0 变为 Y)
交易仅用于信息提供,没有用户可调用的方法。
参考:交易
类 backtrader.trade.Trade(data=None, tradeid=0, historyon=False, size=0, price=0.0, value=0.0, commission=0.0)
跟踪交易的生命周期:大小、价格、佣金(和价值?)
一个交易从 0 开始,可以增加和减少,并且如果回到 0 可以被视为关闭。
交易可以是多头(正大小)或空头(负大小)
一个交易不打算被反转(逻辑上不支持)
成员属性:
-
ref
: 唯一的交易标识符 -
status
(int
): Created、Open、Closed 中的一个 -
tradeid
: 在创建订单时传递给订单的交易 id 分组,默认为 0 -
size
(int
): 交易的当前大小 -
price
(float
): 交易的当前价格 -
value
(float
): 交易的当前价值 -
commission
(float
): 当前累积佣金 -
pnl
(float
): 交易的当前利润和损失(毛利润) -
pnlcomm
(float
): 交易的当前利润和损失减去佣金(净利润) -
isclosed
(bool
): 记录最后一个更新是否关闭了交易(将大小设置为 null) -
isopen
(bool
): 记录是否有任何更新打开了交易 -
justopened
(bool
): 如果交易刚刚开启 -
baropen
(int
): 开启此交易的柱 -
dtopen
(float
): 交易开启的浮点编码日期时间- 使用方法
open_datetime
获取 Python datetime.datetime 或使用平台提供的num2date
方法
- 使用方法
-
barclose
(int
): 关闭此交易的柱 -
dtclose
(float
): 交易关闭的浮点编码日期时间- 使用方法
close_datetime
获取 Python datetime.datetime 或使用平台提供的num2date
方法
- 使用方法
-
barlen
(int
): 此交易持续的周期数 -
historyon
(bool
): 是否记录历史记录 -
history
(list
): 每个“update”事件更新的列表,包含更新后的状态和更新中使用的参数历史记录中的第一个条目是开仓事件,最后一个条目是关闭事件
佣金方案
佣金:股票与期货
原文:
www.backtrader.com/docu/commission-schemes/commission-schemes/
不可知论
在继续之前,让我们记住,backtrader
试图保持对数据表示的不可知性。 可以将不同的佣金方案应用于相同的数据集。
让我们看看如何做到这一点。
使用经纪商快捷方式
这使得最终用户远离了CommissionInfo
对象,因为可以通过单个函数调用来创建/设置佣金方案。 在常规的cerebro
创建/设置过程中,只需在broker
成员属性上添加一个对setcommission
的调用。 以下调用为使用Interactive Brokers时的Eurostoxx50期货设置了一个常规佣金方案:
cerebro.broker.setcommission(commission=2.0, margin=2000.0, mult=10.0)
由于大多数用户通常只会测试单个工具,因此这就是所有的内容。 如果您已为数据源(data feed
)指定了name
,因为在图表上同时考虑了多个工具,因此可以稍微扩展此调用,如下所示:
cerebro.broker.setcommission(commission=2.0, margin=2000.0, mult=10.0, name='Eurostoxxx50')
在这种情况下,此即时佣金方案仅适用于名称匹配为Eurostoxx50
的工具。
设置佣金参数的含义
-
commission
(默认值:0.0
)绝对或百分比单位中每个操作的货币单位成本。
在上面的示例中,每个
buy
合约的费用为 2.0 欧元,每个sell
合约再次为 2.0 欧元。这里重要的问题是何时使用绝对值或百分比值。
-
如果
margin
评估为False
(例如为 False、0 或 None),则会认为commission
表示price
乘以size
操作值的百分比 -
如果
margin
是其他值,则认为操作发生在类似期货
的工具上,commission
是每个size
合约的固定价格
-
-
margin
(默认值:None
)使用
期货
类似工具时需要的保证金。 如上所述-
如果未设置**
margin
**,则将理解commission
为以百分比表示,并应用于buy
或sell
操作的price * size
组件。 -
如果设置了
margin
,则将理解commission
为固定值,该值将乘以buy
或sell
操作的size
组件。
-
-
mult
(默认值:1.0)对于类似
期货
的工具,这确定要应用于利润和损失计算的乘数。这就是期货同时具有吸引力和风险的原因。
-
name
(默认值:无)将佣金方案的应用限制为与
name
匹配的工具这可以在数据源创建期间设置。
如果未设置,该方案将适用于系统中的任何数据。
现在有两个示例:股票 vs 期货
上述期货示例:
cerebro.broker.setcommission(commission=2.0, margin=2000.0, mult=10.0)
以股票为例:
cerebro.broker.setcommission(commission=0.005) # 0.5% of the operation value
注意
第 2 种语法不设置margin和mult,backtrader会尝试通过考虑佣金为%
来采用智能方法。
要完全指定佣金方案,需要创建CommissionInfo
的子类
创建永久委员会方案
可以通过直接使用CommissionInfo
类来创建更永久的佣金方案。用户可以选择在某处定义此定义:
import backtrader as bt
commEurostoxx50 = bt.CommissionInfo(commission=2.0, margin=2000.0, mult=10.0)
稍后在另一个 Python 模块中应用它与 addcommissioninfo
:
from mycomm import commEurostoxx50
...
cerebro.broker.addcommissioninfo(commEuroStoxx50, name='Eurostoxxx50')
CommissionInfo
是一个对象,它使用与backtrader
环境中的其他对象相同的params
声明。因此,以上内容也可以表示为:
import backtrader as bt
class CommEurostoxx50(bt.CommissionInfo):
params = dict(commission=2.0, margin=2000.0, mult=10.0)
以后:
from mycomm import CommEurostoxx50
...
cerebro.broker.addcommissioninfoCommEuroStoxx50(), name='Eurostoxxx50')
现在进行与 SMA 交叉的“真实”比较
使用简单移动平均线交叉作为入/出信号,同一数据集将使用期货
类似的佣金方案进行测试,然后使用类似于股票
的方案进行测试。
注意
期货持仓不仅可以给出进入/退出行为,还可以在每次发生时进行反转行为。但这个示例是关于比较委员会方案的。
代码(请参阅底部的完整策略)是相同的,可以在定义策略之前选择方案。
futures_like = True
if futures_like:
commission, margin, mult = 2.0, 2000.0, 10.0
else:
commission, margin, mult = 0.005, None, 1
只需将futures_like
设置为 false 即可运行类似于股票
的方案。
已添加一些记录代码以评估不同佣金方案的影响。让我们只关注前两个操作。
对于期货:
2006-03-09, BUY CREATE, 3757.59
2006-03-10, BUY EXECUTED, Price: 3754.13, Cost: 2000.00, Comm 2.00
2006-04-11, SELL CREATE, 3788.81
2006-04-12, SELL EXECUTED, Price: 3786.93, Cost: 2000.00, Comm 2.00
2006-04-12, OPERATION PROFIT, GROSS 328.00, NET 324.00
2006-04-20, BUY CREATE, 3860.00
2006-04-21, BUY EXECUTED, Price: 3863.57, Cost: 2000.00, Comm 2.00
2006-04-28, SELL CREATE, 3839.90
2006-05-02, SELL EXECUTED, Price: 3839.24, Cost: 2000.00, Comm 2.00
2006-05-02, OPERATION PROFIT, GROSS -243.30, NET -247.30
对于股票:
2006-03-09, BUY CREATE, 3757.59
2006-03-10, BUY EXECUTED, Price: 3754.13, Cost: 3754.13, Comm 18.77
2006-04-11, SELL CREATE, 3788.81
2006-04-12, SELL EXECUTED, Price: 3786.93, Cost: 3786.93, Comm 18.93
2006-04-12, OPERATION PROFIT, GROSS 32.80, NET -4.91
2006-04-20, BUY CREATE, 3860.00
2006-04-21, BUY EXECUTED, Price: 3863.57, Cost: 3863.57, Comm 19.32
2006-04-28, SELL CREATE, 3839.90
2006-05-02, SELL EXECUTED, Price: 3839.24, Cost: 3839.24, Comm 19.20
2006-05-02, OPERATION PROFIT, GROSS -24.33, NET -62.84
第一次操作的价格如下:
-
买入(执行)-> 3754.13 / 卖出(执行)-> 3786.93
-
期货利润与损失(含佣金):324.0
-
股票利润与损失(含佣金):-4.91
-
嘿!!佣金完全吃掉了股票
操作的任何利润,但对期货
操作只是造成了小小的影响。
第二次操作:
-
买入(执行)->
3863.57
/ 卖出(执行)->3389.24
-
期货利润与损失(含佣金):
-247.30
-
股票利润与损失(含佣金):
-62.84
-
对于这个负操作,期货
的影响更大。
但:
-
期货累积净利润与损失:
324.00 + (-247.30) = 76.70
-
股票累积净利润与损失:
(-4.91) + (-62.84) = -67.75
累积效果可以在下面的图表中看到,在完整年份结束时,期货产生了更大的利润,但也遭受了更大的回撤(更深的亏损)
但重要的是:无论是期货
还是股票
…… 都可以进行回测。
期货佣金
股票佣金
代码
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind
futures_like = True
if futures_like:
commission, margin, mult = 2.0, 2000.0, 10.0
else:
commission, margin, mult = 0.005, None, 1
class SMACrossOver(bt.Strategy):
def log(self, txt, dt=None):
''' Logging function fot this strategy'''
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def notify(self, order):
if order.status in [order.Submitted, order.Accepted]:
# Buy/Sell order submitted/accepted to/by broker - Nothing to do
return
# Check if an order has been completed
# Attention: broker could reject order if not enougth cash
if order.status in [order.Completed, order.Canceled, order.Margin]:
if order.isbuy():
self.log(
'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
self.buyprice = order.executed.price
self.buycomm = order.executed.comm
self.opsize = order.executed.size
else: # Sell
self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
gross_pnl = (order.executed.price - self.buyprice) * \
self.opsize
if margin:
gross_pnl *= mult
net_pnl = gross_pnl - self.buycomm - order.executed.comm
self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
(gross_pnl, net_pnl))
def __init__(self):
sma = btind.SMA(self.data)
# > 0 crossing up / < 0 crossing down
self.buysell_sig = btind.CrossOver(self.data, sma)
def next(self):
if self.buysell_sig > 0:
self.log('BUY CREATE, %.2f' % self.data.close[0])
self.buy() # keep order ref to avoid 2nd orders
elif self.position and self.buysell_sig < 0:
self.log('SELL CREATE, %.2f' % self.data.close[0])
self.sell()
if __name__ == '__main__':
# Create a cerebro entity
cerebro = bt.Cerebro()
# Add a strategy
cerebro.addstrategy(SMACrossOver)
# Create a Data Feed
datapath = ('../../datas/2006-day-001.txt')
data = bt.feeds.BacktraderCSVData(dataname=datapath)
# Add the Data Feed to Cerebro
cerebro.adddata(data)
# set commission scheme -- CHANGE HERE TO PLAY
cerebro.broker.setcommission(
commission=commission, margin=margin, mult=mult)
# Run over everything
cerebro.run()
# Plot the result
cerebro.plot()
参考
类 backtrader.CommInfoBase()
基于委员会方案的基类。
参数:
-
commission
(默认值:0.0
):以百分比或货币单位表示的基础佣金值 -
mult
(默认为1.0
):应用于资产的值/利润的乘数 -
margin
(默认值:None
):需要开设/持有操作的货币单位金额。只有当类中的最终_stocklike
属性设置为False
时才适用 -
automargin
(默认:False
):方法get_margin
使用的,用于自动计算以下策略所需的保证金/担保-
如果参数
automargin
的评估为False
,则使用参数margin
-
如果
automargin < 0
,则使用参数mult
和mult * price
-
如果
automargin > 0
,则使用参数automargin
和automargin * price
-
-
commtype
(默认:None
):支持的值为CommInfoBase.COMM_PERC
(将佣金理解为%)和CommInfoBase.COMM_FIXED
(将佣金理解为货币单位)None
的默认值是支持的值,以保持与传统的CommissionInfo
对象的兼容性。 如果commtype
设置为 None,则适用以下规则:-
margin
是None
:内部_commtype
设置为COMM_PERC
,_stocklike
设置为True
(与股票的百分比方式运作) -
如果
margin
不是None
:_commtype
设置为COMM_FIXED
,_stocklike
设置为False
(使用期货的固定来回佣金)
如果此参数设置为
None
之外的内容,则它将传递给内部的_commtype
属性,并且参数stocklike
和内部属性_stocklike
也是如此 -
-
stocklike
(默认:False
):指示工具是否类似于股票或期货(参见上文的commtype
讨论) -
percabs
(默认:False
):当commtype
设置为 COMM_PERC 时,参数commission
是否必须理解为 XX%或 0.XX如果此参数为
True
:0.XX 如果此参数为False
:XX% -
interest
(默认:0.0
)如果这不是零,则这是持有空头卖出头寸所收取的年息。 这主要是针对股票的空头卖出
公式:
days * price * abs(size) * (interest / 365)
必须用绝对值指定:0.05 -> 5%
注意
通过覆盖方法
_get_credit_interest
可以更改行为 -
interest_long
(默认:False
)某些产品,如 ETF,对空头和多头头寸收取利息。 如果这是
True
并且interest
不为零,则将在两个方向上收取利息 -
leverage
(默认:1.0
)对于所需现金的资产的杠杆倍数
- _stocklike
()
用于类 Stock-like/Futures-like 行为的最终值
- _commtype
()
PERC vs FIXED 佣金的最终值
这两个参数在内部使用,而不是声明的参数,以启用该方法
描述的兼容性检查适用于遗留的CommissionInfo
()
对象()
类 backtrader.CommissionInfo()
实际佣金方案的基类。
CommInfoBase 被创建以保持对backtrader提供的原始,不完整的支持的支持。新的佣金方案派生自这个类,这些类是CommInfoBase
的子类。
percabs
的默认值也更改为True
参数:
-
percabs
(默认:True):当commtype
设置为 COMM_PERC 时,参数commission
是否必须理解为 XX%或 0.XX如果此参数为 True:0.XX 如果此参数为 False:XX%
get_leverage()
返回此佣金方案允许的杠杆水平
getsize(price, cash)
返回在给定价格下执行现金操作所需的大小
getoperationcost(size, price)
返回操作将花费的现金金额
getvaluesize(size, price)
返回给定价格的大小值。对于类似期货的对象,它在size * margin
处固定。
getvalue(position, price)
返回给定价格的位置值。对于类似期货的对象,它在size * margin
处固定。
get_margin(price)
返回给定价格下资产单个项目所需的实际保证金/担保。默认实现具有以下策略:
-
如果参数
automargin
评估为False
,则使用参数margin
-
使用参数
mult
,即如果automargin < 0
,则为mult * price
。 -
使用参数
automargin
,即如果automargin > 0
,则为automargin * price
。
getcommission(size, price)
计算给定价格的操作的佣金
_getcommission(size, price, pseudoexec)
计算给定价格的操作的佣金
pseudoexec:如果为 True,则操作尚未执行
profitandloss(size, price, newprice)
返回位置的实际盈亏
cashadjust(size, price, newprice)
计算给定价格差异的现金调整
get_credit_interest(data, pos, dt)
计算卖空或特定产品的信用费用
_get_credit_interest(data, size, price, days, dt0, dt1)
此方法返回经纪人收取的信用利息成本。
如果size > 0
,则仅在类的参数interest_long
为True
时才调用此方法。
计算信用利率的公式为:
公式:days * price * abs(size) * (interest / 365)
参数:
* `data`: data feed for which interest is charged
* `size`: current position size. > 0 for long positions and < 0 for
short positions (this parameter will not be `0`)
* `price`: current position price
* `days`: number of days elapsed since last credit calculation
(this is (dt0 - dt1).days)
* `dt0`: (datetime.datetime) current datetime
* `dt1`: (datetime.datetime) datetime of previous calculation
dt0
和dt1
在默认实现中未被使用,并作为覆盖方法的额外输入提供
扩展佣金
原文:
www.backtrader.com/docu/extending-commissions/commission-schemes-extended/
佣金和相关功能由一个名为CommissionInfo
的单个类管理,该类大部分通过调用broker.setcommission
实例化。
该概念仅限于具有保证金和按合同固定佣金的期货,以及具有基于价格/数量百分比的股票。即使已实现其目的,也不是最灵活的方案。
GitHub 上的增强请求#29导致一些重写,以便:
-
保持
CommissionInfo
和broker.setcommission
与原始行为兼容。 -
对代码进行一些清理
-
使佣金方案灵活以支持增强请求和进一步的可能性
在进入示例之前的实际工作
class CommInfoBase(with_metaclass(MetaParams)):
COMM_PERC, COMM_FIXED = range(2)
params = (
('commission', 0.0), ('mult', 1.0), ('margin', None),
('commtype', None),
('stocklike', False),
('percabs', False),
)
引入了CommissionInfo
的基类,它将新参数添加到混合中:
-
commtype
(默认:None
)这是兼容性的关键。如果值为
None
,则CommissionInfo
对象和broker.setcommission
的行为将与以前相同。即:-
如果设置了
margin
,则佣金方案适用于具有按合同固定佣金的期货 -
如果未设置
margin
,则佣金方案适用于采用百分比方法的股票。
如果值为
COMM_PERC
或COMM_FIXED
(或从派生类中的任何其他值),这显然决定了佣金是否是固定的还是基于百分比的。 -
-
stocklike
(默认:False
)如上所述,旧的
CommissionInfo
对象中的实际行为由参数margin
确定如果
commtype
设置为None
之外的其他内容,则此值指示资产是否为类似期货的资产(将使用保证金并进行基于条形的现金调整),否则这是类似股票的资产。 -
percabs
(默认:False
)如果为
False
,则百分比必须以相对形式传递(xx%)。如果为
True
,则百分比必须作为绝对值传递(0.xx)CommissionInfo
是从CommInfoBase
继承的,将此参数的默认值更改为True
以保持兼容的行为。
所有这些参数也可以在broker.setcommission
中使用,现在看起来像这样:
def setcommission(self,
commission=0.0, margin=None, mult=1.0,
commtype=None, percabs=True, stocklike=False,
name=None):
请注意以下内容:
percabs
为True
,以保持与旧调用的兼容行为,如上所述对于CommissionInfo
对象
用于测试commissions-schemes
的旧示例已重写以支持命令行参数和新行为。用法帮助:
$ ./commission-schemes.py --help
usage: commission-schemes.py [-h] [--data DATA] [--fromdate FROMDATE]
[--todate TODATE] [--stake STAKE]
[--period PERIOD] [--cash CASH] [--comm COMM]
[--mult MULT] [--margin MARGIN]
[--commtype {none,perc,fixed}] [--stocklike]
[--percrel] [--plot] [--numfigs NUMFIGS]
Commission schemes
optional arguments:
-h, --help show this help message and exit
--data DATA, -d DATA data to add to the system (default:
../../datas/2006-day-001.txt)
--fromdate FROMDATE, -f FROMDATE
Starting date in YYYY-MM-DD format (default:
2006-01-01)
--todate TODATE, -t TODATE
Starting date in YYYY-MM-DD format (default:
2006-12-31)
--stake STAKE Stake to apply in each operation (default: 1)
--period PERIOD Period to apply to the Simple Moving Average (default:
30)
--cash CASH Starting Cash (default: 10000.0)
--comm COMM Commission factor for operation, either apercentage or
a per stake unit absolute value (default: 2.0)
--mult MULT Multiplier for operations calculation (default: 10)
--margin MARGIN Margin for futures-like operations (default: 2000.0)
--commtype {none,perc,fixed}
Commission - choose none for the old CommissionInfo
behavior (default: none)
--stocklike If the operation is for stock-like assets orfuture-
like assets (default: False)
--percrel If perc is expressed in relative xx{'const': True,
'help': u'If perc is expressed in relative xx%
ratherthan absolute value 0.xx', 'option_strings': [u'
--percrel'], 'dest': u'percrel', 'required': False,
'nargs': 0, 'choices': None, 'default': False, 'prog':
'commission-schemes.py', 'container':
<argparse._ArgumentGroup object at
0x0000000007EC9828>, 'type': None, 'metavar':
None}atherthan absolute value 0.xx (default: False)
--plot, -p Plot the read data (default: False)
--numfigs NUMFIGS, -n NUMFIGS
Plot using numfigs figures (default: 1)
让我们进行一些运行以重现原始佣金方案贴文的原始行为。
期货佣金(固定和有保证金)
执行和图表:
$ ./commission-schemes.py --comm 2.0 --margin 2000.0 --mult 10 --plot
并且输出显示固定佣金为 2.0 货币单位(默认的投注是 1):
2006-03-09, BUY CREATE, 3757.59
2006-03-10, BUY EXECUTED, Price: 3754.13, Cost: 2000.00, Comm 2.00
2006-04-11, SELL CREATE, 3788.81
2006-04-12, SELL EXECUTED, Price: 3786.93, Cost: 2000.00, Comm 2.00
2006-04-12, TRADE PROFIT, GROSS 328.00, NET 324.00
...
股票佣金(带和不带保证金的百分比)
执行和图表:
$ ./commission-schemes.py --comm 0.005 --margin 0 --mult 1 --plot
为了提高可读性,可以使用相对百分比值:
$ ./commission-schemes.py --percrel --comm 0.5 --margin 0 --mult 1 --plot
现在0.5
直接意味着0.5%
在两种情况下输出为:
2006-03-09, BUY CREATE, 3757.59
2006-03-10, BUY EXECUTED, Price: 3754.13, Cost: 3754.13, Comm 18.77
2006-04-11, SELL CREATE, 3788.81
2006-04-12, SELL EXECUTED, Price: 3786.93, Cost: 3754.13, Comm 18.93
2006-04-12, TRADE PROFIT, GROSS 32.80, NET -4.91
...
期货的佣金(百分比和有保证金)
使用新参数,期货的基于百分比的方案:
$ ./commission-schemes.py --commtype perc --percrel --comm 0.5 --margin 2000 --mult 10 --plot
改变佣金……最终结果改变也就不足为奇了。
输出显示佣金现在是可变的:
2006-03-09, BUY CREATE, 3757.59
2006-03-10, BUY EXECUTED, Price: 3754.13, Cost: 2000.00, Comm 18.77
2006-04-11, SELL CREATE, 3788.81
2006-04-12, SELL EXECUTED, Price: 3786.93, Cost: 2000.00, Comm 18.93
2006-04-12, TRADE PROFIT, GROSS 328.00, NET 290.29
...
在先前的运行中设置了 2.0 货币单位(默认投注为 1)
另一篇文章将详细介绍新类别和一种自制佣金方案的实现。
示例的代码
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind
class SMACrossOver(bt.Strategy):
params = (
('stake', 1),
('period', 30),
)
def log(self, txt, dt=None):
''' Logging function fot this strategy'''
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
# Buy/Sell order submitted/accepted to/by broker - Nothing to do
return
# Check if an order has been completed
# Attention: broker could reject order if not enougth cash
if order.status in [order.Completed, order.Canceled, order.Margin]:
if order.isbuy():
self.log(
'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
else: # Sell
self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
def notify_trade(self, trade):
if trade.isclosed:
self.log('TRADE PROFIT, GROSS %.2f, NET %.2f' %
(trade.pnl, trade.pnlcomm))
def __init__(self):
sma = btind.SMA(self.data, period=self.p.period)
# > 0 crossing up / < 0 crossing down
self.buysell_sig = btind.CrossOver(self.data, sma)
def next(self):
if self.buysell_sig > 0:
self.log('BUY CREATE, %.2f' % self.data.close[0])
self.buy(size=self.p.stake) # keep order ref to avoid 2nd orders
elif self.position and self.buysell_sig < 0:
self.log('SELL CREATE, %.2f' % self.data.close[0])
self.sell(size=self.p.stake)
def runstrategy():
args = parse_args()
# Create a cerebro
cerebro = bt.Cerebro()
# Get the dates from the args
fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
# Create the 1st data
data = btfeeds.BacktraderCSVData(
dataname=args.data,
fromdate=fromdate,
todate=todate)
# Add the 1st data to cerebro
cerebro.adddata(data)
# Add a strategy
cerebro.addstrategy(SMACrossOver, period=args.period, stake=args.stake)
# Add the commission - only stocks like a for each operation
cerebro.broker.setcash(args.cash)
commtypes = dict(
none=None,
perc=bt.CommInfoBase.COMM_PERC,
fixed=bt.CommInfoBase.COMM_FIXED)
# Add the commission - only stocks like a for each operation
cerebro.broker.setcommission(commission=args.comm,
mult=args.mult,
margin=args.margin,
percabs=not args.percrel,
commtype=commtypes[args.commtype],
stocklike=args.stocklike)
# And run it
cerebro.run()
# Plot if requested
if args.plot:
cerebro.plot(numfigs=args.numfigs, volume=False)
def parse_args():
parser = argparse.ArgumentParser(
description='Commission schemes',
formatter_class=argparse.ArgumentDefaultsHelpFormatter,)
parser.add_argument('--data', '-d',
default='../../datas/2006-day-001.txt',
help='data to add to the system')
parser.add_argument('--fromdate', '-f',
default='2006-01-01',
help='Starting date in YYYY-MM-DD format')
parser.add_argument('--todate', '-t',
default='2006-12-31',
help='Starting date in YYYY-MM-DD format')
parser.add_argument('--stake', default=1, type=int,
help='Stake to apply in each operation')
parser.add_argument('--period', default=30, type=int,
help='Period to apply to the Simple Moving Average')
parser.add_argument('--cash', default=10000.0, type=float,
help='Starting Cash')
parser.add_argument('--comm', default=2.0, type=float,
help=('Commission factor for operation, either a'
'percentage or a per stake unit absolute value'))
parser.add_argument('--mult', default=10, type=int,
help='Multiplier for operations calculation')
parser.add_argument('--margin', default=2000.0, type=float,
help='Margin for futures-like operations')
parser.add_argument('--commtype', required=False, default='none',
choices=['none', 'perc', 'fixed'],
help=('Commission - choose none for the old'
' CommissionInfo behavior'))
parser.add_argument('--stocklike', required=False, action='store_true',
help=('If the operation is for stock-like assets or'
'future-like assets'))
parser.add_argument('--percrel', required=False, action='store_true',
help=('If perc is expressed in relative xx% rather'
'than absolute value 0.xx'))
parser.add_argument('--plot', '-p', action='store_true',
help='Plot the read data')
parser.add_argument('--numfigs', '-n', default=1,
help='Plot using numfigs figures')
return parser.parse_args()
if __name__ == '__main__':
runstrategy()