原文:
www.backtrader.com/blog/posts/2017-06-26-renko-bricks/renko-bricks/
砖形图是一种以价格比时间更重要的方式展示价格演变的方法。这已经在backtrader的1.9.54.122
版本中作为过滤器引入了。
Stockcharts对砖形图有一个很好的参考。请参阅StockCharts 上的砖形图
注意
size=35
和align=10.0
参数适用于backtrader存储库中的示例数据。这些值必须针对每个数据资产进行微调。
首先,让我们将砖形图单独放在一个图表上:
$ ./renko.py --renko size=35,align=10.0 --plot
输出
人们可以看到图表立即显示出一些支撑/阻力区域,这是砖形图的主要优势之一。显然,X 轴上的时间演变不再是恒定的,而是被拉伸或压缩,取决于价格行动在期间是更静态还是移动了几个砖块。
为了更好地看到和欣赏效果,让我们将普通的价格条和砖形图放在一起在一个图表上:
$ ./renko.py --renko size=35,align=10.0 --plot --dual
输出
时间周期的拉伸和压缩现在更加明显。另一个要考虑的因素是价格行动的关注如何影响应用的指标,正如图表上显示的两个RSI
指标所示。
$ ./renko.py --help
usage: renko.py [-h] [--data0 DATA0] [--fromdate FROMDATE] [--todate TODATE]
[--cerebro kwargs] [--broker kwargs] [--sizer kwargs]
[--strat kwargs] [--plot [kwargs]] [--renko kwargs] [--dual]
Renko bricks 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: )
--renko kwargs kwargs in key=value format (default: )
--dual put the filter on a second version of the data
(default: False)
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import backtrader as bt
class St(bt.Strategy):
params = dict(
)
def __init__(self):
for d in self.datas:
bt.ind.RSI(d)
def next(self):
pass
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)
data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **kwargs)
fkwargs = dict()
fkwargs.update(**eval('dict(' + args.renko + ')'))
if not args.dual:
data0.addfilter(bt.filters.Renko, **fkwargs)
cerebro.adddata(data0)
else:
cerebro.adddata(data0)
data1 = data0.clone()
data1.addfilter(bt.filters.Renko, **fkwargs)
cerebro.adddata(data1)
# 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
kwargs = dict(stdstats=False)
kwargs.update(**eval('dict(' + args.cerebro + ')'))
cerebro.run(**kwargs)
if args.plot: # Plot if requested to
kwargs = dict(style='candle')
kwargs.update(**eval('dict(' + args.plot + ')'))
cerebro.plot(**kwargs)
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description=(
'Renko bricks 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')
parser.add_argument('--renko', required=False, default='',
metavar='kwargs', help='kwargs in key=value format')
parser.add_argument('--dual', required=False, action='store_true',
help='put the filter on a second version of the data')
return parser.parse_args(pargs)
if __name__ == '__main__':
runstrat()
原文:
www.backtrader.com/blog/posts/2017-06-19-fund-tracking/fund-tracking/
backtrader已经有一段时间被使用,可以说是专业使用,除了一些银行和交易公司之外,还有Backtrader 基金。
一群志同道合且相互认识已久的人决定走上开设(避险)基金的道路,并以backtrader作为交易理念的基石。有一件事情是不可或缺的:它必须是 100%受监管的(但不在开曼群岛或类似地方)
位置、传统和网络在欧盟首先,然后在西班牙放置重点,西班牙(以及其他一些地方)的立法允许伞形基金托管子基金,这使得可以用较少的资金和参与者数量创建一个完全受监管的基金。
还有……基金已被西班牙的监管机构CNMV(西班牙国家证券市场委员会)批准,ISIN为ES0131444038
。链接:
对于能阅读西班牙语的人,backtrader的使用已在官方基金说明书中记录。
对于那些可能在某个时候决定走这条路的人,最重要的事情是:
backtrader是交易理念的基础,并且它已经找到了一个新的应用领域:报告。自定义的分析器和指标可以帮助控制风险/波动性,从而减轻了行政负担。
可能是因为我们年纪大了(而且是老派的),我们仍然更喜欢手动执行(自动执行将在未来某个时候接管)
下面描述的功能是为了辅助管理基金和回测资金进出以及绩效不再是追踪净资产价值的问题。
在版本1.9.52.121
中,backtrader中的经纪人不仅在现金/价值方面跟踪会计,而且像在基金中一样进行,即:
通过这样做,我们可以模拟现金存款和提款,同时仍然跟踪实际表现,而常规会计会被现金流入/流出所扭曲。
除了经纪人的变化外,分析器和观察者也已经适应(那些与净资产价值有关的)以支持fund
参数,以决定实际应该追踪什么。例如TimeReturn
:
...
cerebro.addanalyzer(bt.analyzers.TimeReturn) # auto-detect broker mode
cerebro.addanalyzer(bt.analyzers.TimeReturn, fund=True) # track fund value
cerebro.addanalyzer(bt.analyzers.TimeReturn, fund=False) # track net asset value
...
想象一下使用案例(稍后在示例中):某人从 1000 货币单位开始,在每个月的 15 日添加 100 货币单位。12 个月后,账户中的总额为 2200。根据最初的持仓计算的回报率为
以通常的方式计算回报,这意味着在没有执行任何操作的情况下,年度回报率为120%
。当然,这是不正确的。
为了缓解这个问题,不管账户的初始值如何,基金份额(fundvalue)的价值被设定为100.0
。并且根据此值和起始净资产价值(1000
货币单位),计算基金份额数量如下:
fundshares = 净资产价值 / 基金价值
在这种情况下,实际上是1000 / 100.0 = 10 份股份
每次加入现金后,我们都会增加股份数量:
new_fund_shares = 加入现金 / 基金价值
因为我们每次添加100.0
货币单位且没有执行任何操作:
- ``100.0 / 100.0 = 1 share```
```py
Notice that the *fundvalue* remains unchanged. Quickly forwarding to the end of the year we have the following:
* Starting net-asset-value: `1000`
* Final net-asset-value: `2200`
* Starting fundvalue = `100`
* Final fundvalue = `100`
* Starting number of shares: `10`
* Final number of shares: `22`
Now if we calculate the returns using the starting and ending *fundvalues* and because they are the same we have a: `0%` which matches the reality. because cash additions have not changed
## Using *fund tracking* in backtrader
### Adding cash
First, the broker has gained a method to canonically add cash to the system:
add_cash(cash)
Using it inside the strategy for example:
`def next(self):
如果条件成立:
self.broker.add_cash(1000.0)`
This method **MUST** be used to track the entry and exit of cash into the system and properly track the fund value.
### Automatic
Activate it in the *broker*:
`…
cerebro.broker.set_fundmode(True)
…`
changing at the same time the default fund start value:
`…
cerebro.broker.set_fundmode(True, 10.0) # 默认为 100
…`
or in independent calls:
`…
cerebro.broker.set_fundmode(True)
cerebro.broker.set_fundstartval(10.0) # 默认为 100
…`
After activation of the default mode and coming back to the `TimeReturn` analyzer examples from above:
`…
cerebro.addanalyzer(bt.analyzers.TimeReturn) # 自动检测经纪人模式
cerebro.addanalyzer(bt.analyzers.TimeReturn, fund=True) # 追踪基金价值
cerebro.addanalyzer(bt.analyzers.TimeReturn, fund=False) # 追踪净资产价值
…`
`1` and `2` are equivalent. But one should go for `1`. If one wishes to compare, one can always force the `TimeReturn` analyzer to not use the *fundvalue* and rather track the *net-asset-value*
An example is worth a thousand words. In the sample, we’ll be using doing as described above but with some extra cash (the asset has a per-share value over `3000`). The initial cash level will be `10000`, the default in *backtrader* and on the *15^(th)* of each month, `1000` extra monetary units will be added (using a recurring `Timer`). It will be 24 months (which is the size of the standard data sample used in *backtrader*)
#### Without any operations
$ ./fund-tracker.py --broker fundmode=True --strat cash2add=1000 --cerebro writer=True --plot
The graphical view
![image](https://gitcode.net/OpenDocCN/flygon-quant-docs-zh/-/raw/master/docs/backtrader/img/0dc175c0aaa817528eafd4a73718a16d.png)
And the text output (capped for readability):
`- timereturn:
...
- 基金:无
…
- 参数:
...
- 基金:真
…
...
- 基金:假
There are 3 `TimeReturn` analyzers getting added.
* The 1^(st) has `fund=None` (default) which means to track the actual `fundmode` set in the broker (in this case `True`)
It says that the yearly returns were `0.0` and `0.0`. Since we made no operations: **Ok**
* The 2^(nd) has `fund=True` which means to use the *fundvalue* always
It says that the yearly returns were `0.0` and `0.0`. Since we made no operations: **Ok**
* The 3^(rd) has `fund=False` which means to use the *net-asset-value* always
It says that the yearly returns were `1.2` (*120%*) and `0.54` (*54%*). Since we made no operations: **This is clearly wrong**
The plot contains also the 2 new *Observers* (`FundValue` and `FundShares`) which allow to see how even if the *net-asset-value* grows with the addition of cash every month, the *fundvalue* remains constant as `100.0`. At the same times the shares grow with each cash addition.
#### Let’s trade
The same as above but with some trading using a standard moving average crossover
$ ./fund-tracker.py --broker fundmode=True --strat cash2add=1000,trade=True --cerebro writer=True --plot
The graphical view
![image](https://gitcode.net/OpenDocCN/flygon-quant-docs-zh/-/raw/master/docs/backtrader/img/03a9b4204180b3201aa5cfece1a37a3d.png)
And the text output (capped for readability):
`- timereturn:
...
- 基金:无
- 分析:
- 2005-12-31:-0.00642229824537
- 2006-12-31:7.78998679263e-05
.......................................................................
- timereturn1:
...
- 基金:真
…
...
- 基金:假
The same three `TimeReturn` analyzers from before. The ones with `fund=None` and `fund=True` give reasonable results whereas the one using `fund=False` is clearly off the chart again with `119%` and `54%`, which is clearly not the return offered by the moving average crossover.
### Manual
In this case (which is the default in the broker and even if the broker is tracking the value of the fund, only those analyzers with `fund=True` will use the value.
A quick run with only the textual ouput:
$ ./fund-tracker.py --strat cash2add=1000,trade=True --cerebro writer=True
Output:
`- timereturn:
...
- 基金:无
…
...
- 基金:真
…
...
- 基金:假
Now only the `TimeReturn` with `fund=True` delivers sensible results.
## Conclusion
The new `fundmode` implemented in the broker and which can be used (automatically/manually) in the analyzers, allows to use *backtrader* to model the inner workings of a real fund or use cases like constant investment of money at given intervals.
## Sample Usage
`$ ./fund-tracker.py --help
用法:fund-tracker.py [-h] [–data0 DATA0] [–fromdate FROMDATE]
[--todate TODATE] [--cerebro kwargs] [--broker kwargs]
[--sizer kwargs] [--strat kwargs] [--plot [kwargs]]
基金跟踪示例
可选参数:
-h,–help 显示此帮助消息并退出
–data0 DATA0 要读取的数据(默认值:
../../datas/2005-2006-day-001.txt)
–fromdate FROMDATE 日期[时间]的格式为 YYYY-MM-DD[THH:MM:SS](默认值:
–todate TODATE 日期[时间]的格式为 YYYY-MM-DD[THH:MM:SS](默认值:
–cerebro kwargs 以键=值格式的 kwargs(默认值:)
–broker kwargs 以键=值格式的 kwargs(默认值:)
–sizer kwargs 以键=值格式的 kwargs(默认值:)
–strat kwargs 以键=值格式的 kwargs(默认值:)
–plot [kwargs] 以键=值格式的 kwargs(默认值:)`
## Sample Code
`from future import (absolute_import, division, print_function,
unicode_literals)
导入 argparse
导入 datetime
导入 backtrader as bt
类 St(bt.SignalStrategy):
参数 = 字典(
cash2add=None,
cashonday=15,
pfast=10,
pslow=30,
trade=False,
)
def __init__(self):
self.add_timer(when=bt.Timer.SESSION_END, monthdays=[self.p.cashonday])
sma1 = bt.ind.SMA(period=self.p.pfast)
sma2 = bt.ind.SMA(period=self.p.pslow)
signal = bt.ind.CrossOver(sma1, sma2)
if self.p.trade:
self.signal_add(bt.SIGNAL_LONGSHORT, signal)
def notify_timer(self, timer, when, *args, **kwargs):
# 无需检查计时器,只有一个
if self.p.cash2add is not None:
self.broker.add_cash(self.p.cash2add)
def next(self):
pass
def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()
# 数据源关键字参数
kwargs = dict()
# 从/到日期解析
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)
data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **kwargs)
cerebro.adddata(data0)
# 经纪人
cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')'))
# 大小规模
cerebro.addsizer(bt.sizers.PercentSizer,
**eval('dict(' + args.sizer + ')'))
# 策略
cerebro.addstrategy(St, **eval('dict(' + args.strat + ')'))
cerebro.addobserver(bt.observers.FundValue)
cerebro.addobserver(bt.observers.FundShares)
ankwargs = dict(timeframe=bt.TimeFrame.Years)
cerebro.addanalyzer(bt.analyzers.TimeReturn, **ankwargs)
cerebro.addanalyzer(bt.analyzers.TimeReturn, fund=True, **ankwargs)
cerebro.addanalyzer(bt.analyzers.TimeReturn, fund=False, **ankwargs)
# 执行
cerebro.run(**eval('dict(' + args.cerebro + ')'))
if args.plot: # 如果要求绘图,则绘制
cerebro.plot(**eval('dict(' + args.plot + ')'))
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
描述=(
'基金跟踪示例'
)
)
parser.add_argument('--data0', default='../../datas/2005-2006-day-001.txt',
required=False, help='要读取的数据')
# 日期的默认值
parser.add_argument('--fromdate', required=False, default='',
help='以 YYYY-MM-DD[THH:MM:SS] 格式的日期[时间]')
parser.add_argument('--todate', required=False, default='',
help='以 YYYY-MM-DD[THH:MM:SS] 格式的日期[时间]')
parser.add_argument('--cerebro', required=False, default='',
metavar='kwargs', help='以键=值格式的关键字参数')
parser.add_argument('--broker', required=False, default='',
metavar='kwargs', help='以键=值格式的关键字参数')
parser.add_argument('--sizer', required=False, default='',
metavar='kwargs', help='以键=值格式的关键字参数')
parser.add_argument('--strat', required=False, default='',
metavar='kwargs', help='以键=值格式的关键字参数')
parser.add_argument('--plot', required=False, default='',
nargs='?', const='{}',
metavar='kwargs', help='以键=值格式的关键字参数')
return parser.parse_args(pargs)
if name == ‘main’:
runstrat()`
# 发布版本 1.9.51.121
> 原文:[`www.backtrader.com/blog/posts/2017-06-12-release-1.9.51.121/release-1.9.51.121/`](https://www.backtrader.com/blog/posts/2017-06-12-release-1.9.51.121/release-1.9.51.121/)
即使是一个次要版本,也有一些有趣的东西,可能值得为它们撰写专门的博客文章。
## `linealias`
[Pull-Request #320](https://github.com/mementum/backtrader/pull/320) 包括指标`相对动量指数`(或`RMI`),根据文献,这是`RSI`的演变,它:
+ 考虑*up*和*down*周期,回溯期大于`1`
因此,与其让一个指标重复`RSI`的大部分内容,做两件事似乎更有用:
1. 扩展`RSI`(以及子指标如`UpDay`和`DownDay`)以支持大于*1*的回溯期。`RMI`可以作为一个简单具有一些不同默认值的子类来实现。
1. `RMI`指标的逻辑名称是`rmi`,但`RSI`已经决定了名称为`rsi`。通过添加一个名为`linealias`的新功能来解决这个问题
`RMI`的实现看起来是这样的:
```py
class RelativeMomentumIndex(RSI):
alias = ('RMI', )
linealias = (('rsi', 'rmi',),) # add an alias for this class rmi -> rsi
plotlines = dict(rsi=dict(_name='rmi')) # change line plotting name
为基类添加了rsi
线的别名,名称为rmi
。如果有人想要创建一个子类并使用名称rmi
,现在是可能的。
此外,rsi
线的绘图名称也更改为rmi
。还有一种替代实现方式:
class RelativeMomentumIndex(RSI):
alias = ('RMI', )
linesoverrride = True # allow redefinition of the lines hierarcy
lines = ('rmi',) # define the line
linealias = (('rmi', 'rsi',),) # add an alias for base class rsi -> rmi
在这里,不再考虑RSI
的现有层次结构,而是使用lines
来定义唯一的名为rmi
的线。不需要定义绘图名称,因为唯一的线现在具有预期的名称。
但基类将无法填充值,因为它期望有一个名为rsi
的线。因此添加了一个反向别名,让它找到该线。
使用交互式经纪人作为优化数据源的实时连接并未被预见。然而,有用户尝试了这种方法,导致了出现了速度违规。原因在于交互式经纪人数据源标记为live
数据源,允许系统绕过某些事情,比如数据预加载。
没有预加载,每次优化实例都会尝试重新从交互式经纪人下载相同的历史数据。考虑到这一点,很明显数据源可以查看用户是否只请求历史下载,在这种情况下不报告自己为live
,允许平台预加载数据并在优化实例之间共享。
查看社区帖子。使用 IBStore 进行优化会导致冗余连接/下载
这个其他社区帖子试图开发平滑蜡烛图作为指标:Develop Heikinashi Indicators,面临着一些问题,因为需要一个种子值,这可以在指标的prenext
阶段完成。
作为传统蜡烛图的一个有趣的显示替代方案,这已经被实现为一个过滤器,允许修改数据源以真正提供平滑蜡烛图。就像这样:
data0 = MyDataFeed(dataname='xxx', timeframe=bt.TimeFrame.Days, compression=1)
data0.addfilter(bt.filters.HeikinAshi)
cerebro.adddata(data0)
任何人都可以通过这段代码快速比较蜡烛图:
data0 = MyDataFeed(dataname='xxx', timeframe=bt.TimeFrame.Days, compression=1)
cerebro.adddata(data0)
data1 = data0.clone()
data1.addfilter(bt.filters.HeikinAshi)
cerebro.adddata(data1)
要绘制蜡烛图,请记住执行:
cerebro.plot(style='candle')
使用样本每日数据来自 2005 年和 2006 年的来源。
并稍微放大以更好地欣赏差异
数据源的轴始终将主数据源用作比例所有者,因为数据始终是视图中最重要的部分。例如,考虑一下布林带
,可能会导致顶部带远离数据的最大值,并且允许此带重新调整图表,将减少数据在图表中占用的空间,这是不希望的。
现在可以使用plotylimited
来控制行为,例如:
...
data0 = MyDataFeed(dataname='xxx', timeframe=bt.TimeFrame.Days, compression=1)
data0.plotinfo.plotlog = False # allow other actors to resize the axis
...
在下图中,底部的数据源使用了plotylimited=False
进行绘制。布林带不会超出图表,因为它们会影响缩放,并且一切都适合图表中。
这在社区中也有所评论。How max - min plot boundaries are set?
现在可以使用半对数刻度(y 轴刻度)绘制单个轴。例如:
...
data0 = MyDataFeed(dataname='xxx', timeframe=bt.TimeFrame.Days, compression=1)
data0.plotinfo.plotlog = True
data0.plotinfo.plotylimited = True
cerebro.adddata(data0)
...
这意味着由此数据源控制的轴将使用对数刻度,但其他轴不会,因此
注意
请注意,使用了plotylimited=True
。这是为了让matplotlib
正确地计算对数图表的限制(因为刻度是 10 的幂)以适应图表中的内容。
简单地比较Yahoo数据的长期期间的示例。
plotmaster
指向自身在同一轴上绘制多个数据源已经是可能的,但是一个小麻烦不允许设置plotinfo.plotmaster
值的干净循环。之前必须执行以下操作:
mydatas = []
data = MyDataFeed(dataname=mytickers[0], timeframe=..., compression=...)
mydatafeeds.append(data)
for ticker in mytickers[1:]
data = MyDataFeed(dataname=ticker, timeframe=..., compression=...)
mydatafeeds.append(data)
data.plotinfo.plotmaster = mydatas[0]
现在可以使用更清晰的循环:
mydatas = []
for ticker in mytickers:
data = MyDataFeed(dataname=ticker, timeframe=..., compression=...)
mydatafeeds.append(data)
data.plotinfo.plotmaster = mydatas[0]
dnames
已经被记录了引用数据源的名称已经可用,但它被忽略了,没有出现在文档中,因此它是一个隐藏的宝藏。策略中的 dnames
属性支持点表示法和*[]*表示法(实际上是一个 dict
子类)。如果我们首先添加一些数据源:
mytickers = ['YHOO', 'IBM', 'AAPL']
for t in mytickers:
d = bt.feeds.YahooFinanceData(dataname=t, fromdate=..., name=t.lower())
在策略中稍后可以执行以下操作:
def __init__(self):
yhoosma = bt.ind.SMA(self.dnames.yhoo, period=20)
aaplsma = bt.ind.SMA(self.dnames['aapl'], period=30)
# or even go over the keys/items/values like in a regular dict
# for example with a dictionary comprehension
stocs = {name: bt.ind.Stochastic(data) for name, data in self.dnames.items()}
一个包含小改动的小版本发布,增加了一些巧妙的功能。
原文:
www.backtrader.com/blog/posts/2017-05-16-stsel-revisited/stsel-revisited/
最初的策略选择方法使用了两种策略,它们是手动注册的,以及一个简单的 [0, 1]
列表来决定哪个是策略的目标。
由于 Python 提供了许多元类的反射可能性,可以实际上自动化这种方法。让我们使用 装饰器
方法来做这件事,在这种情况下可能是最不侵入性的方法(不需要为策略定义 元类)
工厂现在:
_STRATS
类属性(之前它包含要返回的策略)
register
类方法,将用作装饰器,并接受一个参数,该参数将添加到 _STRATS
COUNT
类方法,将返回一个迭代器(实际上是一个 range
),其中包含要优化的可用策略的计数
__new__
,它仍然使用 idx
参数来返回 _STRATS
类属性中给定索引处的任何内容
class StFetcher(object):
_STRATS = []
@classmethod
def register(cls, target):
cls._STRATS.append(target)
@classmethod
def COUNT(cls):
return range(len(cls._STRATS))
def __new__(cls, *args, **kwargs):
idx = kwargs.pop('idx')
obj = cls._STRATSidx
return obj
如下:
StFetcher
策略工厂不再包含任何硬编码的策略示例中的策略不需要重新设计。使用 StFetcher
的 register
方法进行装饰就足以将它们添加到选择池中。
@StFetcher.register
class St0(bt.SignalStrategy):
和
@StFetcher.register
class St1(bt.SignalStrategy):
COUNT
在将策略工厂添加到系统中并使用 optstrategy
时,过去的手动 [0, 1]
列表可以完全替换为对 StFetcher.COUNT()
的透明调用。硬编码已经结束。
cerebro.optstrategy(StFetcher, idx=StFetcher.COUNT())
$ ./stselection-revisited.py --optreturn
Strat 0 Name OptReturn:
- analyzer: OrderedDict([(u'rtot', 0.04847392369449283), (u'ravg', 9.467563221580632e-05), (u'rnorm', 0.02414514457151587), (u'rnorm100', 2.414514457151587)])
Strat 1 Name OptReturn:
- analyzer: OrderedDict([(u'rtot', 0.05124714332260593), (u'ravg', 0.00010009207680196471), (u'rnorm', 0.025543999840699633), (u'rnorm100', 2.5543999840699634)])
我们的两种策略已经运行并且(如预期)产生了不同的结果。
注意
该示例很简单,但已经在所有可用的 CPU 上运行。使用 --maxpcpus=1
执行将更快。对于更复杂的情况,使用所有 CPU 将很有用。
选择已经完全自动化。与之前一样,可以想象类似于查询数据库以获取可用策略数量,然后逐个获取策略的方法。
$ ./stselection-revisited.py --help
usage: strategy-selection.py [-h] [--data DATA] [--maxcpus MAXCPUS]
[--optreturn]
Sample for strategy selection
optional arguments:
-h, --help show this help message and exit
--data DATA Data to be read in (default:
../../datas/2005-2006-day-001.txt)
--maxcpus MAXCPUS Limit the numer of CPUs to use (default: None)
--optreturn Return reduced/mocked strategy object (default: False)
已经包含在 backtrader 的源代码中
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import backtrader as bt
from backtrader.utils.py3 import range
class StFetcher(object):
_STRATS = []
@classmethod
def register(cls, target):
cls._STRATS.append(target)
@classmethod
def COUNT(cls):
return range(len(cls._STRATS))
def __new__(cls, *args, **kwargs):
idx = kwargs.pop('idx')
obj = cls._STRATSidx
return obj
@StFetcher.register
class St0(bt.SignalStrategy):
def __init__(self):
sma1, sma2 = bt.ind.SMA(period=10), bt.ind.SMA(period=30)
crossover = bt.ind.CrossOver(sma1, sma2)
self.signal_add(bt.SIGNAL_LONG, crossover)
@StFetcher.register
class St1(bt.SignalStrategy):
def __init__(self):
sma1 = bt.ind.SMA(period=10)
crossover = bt.ind.CrossOver(self.data.close, sma1)
self.signal_add(bt.SIGNAL_LONG, crossover)
def runstrat(pargs=None):
args = parse_args(pargs)
cerebro = bt.Cerebro()
data = bt.feeds.BacktraderCSVData(dataname=args.data)
cerebro.adddata(data)
cerebro.addanalyzer(bt.analyzers.Returns)
cerebro.optstrategy(StFetcher, idx=StFetcher.COUNT())
results = cerebro.run(maxcpus=args.maxcpus, optreturn=args.optreturn)
strats = [x[0] for x in results] # flatten the result
for i, strat in enumerate(strats):
rets = strat.analyzers.returns.get_analysis()
print('Strat {} Name {}:\n - analyzer: {}\n'.format(
i, strat.__class__.__name__, rets))
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Sample for strategy selection')
parser.add_argument('--data', required=False,
default='../../datas/2005-2006-day-001.txt',
help='Data to be read in')
parser.add_argument('--maxcpus', required=False, action='store',
default=None, type=int,
help='Limit the numer of CPUs to use')
parser.add_argument('--optreturn', required=False, action='store_true',
help='Return reduced/mocked strategy object')
return parser.parse_args(pargs)
if __name__ == '__main__':
runstrat()
发布 1.9.44.116
在 backtrader 可用工具中添加了 timers 功能。此功能允许在指定时间点收到对 notify_timer
的回调(在 Cerebro
和 Strategy
中可用),并且有细粒度的最终用户控制。
注意
在 1.9.46.116
中进行了一些更正
在 Cerebro
和 Strategy
子类中,定时器回调将在以下方法中收到。
def notify_timer(self, timer, when, *args, **kwargs):
'''Receives a timer notification where ``timer`` is the timer which was
returned by ``add_timer``, and ``when`` is the calling time. ``args``
and ``kwargs`` are any additional arguments passed to ``add_timer``
The actual ``when`` time can be later, but the system may have not be
able to call the timer before. This value is the timer value and not the
system time.
'''
使用该方法完成
def add_timer(self, when,
offset=datetime.timedelta(), repeat=datetime.timedelta(),
weekdays=[], weekcarry=False,
monthdays=[], monthcarry=True,
allow=None,
tzdata=None, cheat=False,
*args, **kwargs):
'''
返回创建的 Timer
实例。
有关参数解释,请参见下文。
使用相同的方法完成,只需添加参数 strats
。如果设置为 True
,则定时器不仅将通知给 cerebro,还将通知给系统中运行的所有策略。
def add_timer(self, when,
offset=datetime.timedelta(), repeat=datetime.timedelta(),
weekdays=[], weekcarry=False,
monthdays=[], monthcarry=True,
allow=None,
tzdata=None, cheat=False, strats=False,
*args, **kwargs):
'''
返回创建的 Timer
实例。
cheat=False
这是默认设置。在这种情况下,定时器将被调用:
next
方法被调用之前
cheat=True
在这种情况下,将调用定时器:
next
方法被调用之前
这允许例如以下场景与每日柱形图:
next
时可能已设置了标志)
open
的良好指示。
示例 scheduled.py
默认使用 backtrader 发行版中提供的标准每日柱形图运行。策略的参数
class St(bt.Strategy):
params = dict(
when=bt.timer.SESSION_START,
timer=True,
cheat=False,
offset=datetime.timedelta(),
repeat=datetime.timedelta(),
weekdays=[],
)
数据具有以下会话时间:
仅使用时间运行
$ ./scheduled.py --strat when='datetime.time(15,30)'
strategy notify_timer with tid 0, when 2005-01-03 15:30:00 cheat False
1, 2005-01-03 17:30:00, Week 1, Day 1, O 2952.29, H 2989.61, L 2946.8, C 2970.02
strategy notify_timer with tid 0, when 2005-01-04 15:30:00 cheat False
2, 2005-01-04 17:30:00, Week 1, Day 2, O 2969.78, H 2979.88, L 2961.14, C 2971.12
strategy notify_timer with tid 0, when 2005-01-05 15:30:00 cheat False
3, 2005-01-05 17:30:00, Week 1, Day 3, O 2969.0, H 2969.0, L 2942.69, C 2947.19
strategy notify_timer with tid 0, when 2005-01-06 15:30:00 cheat False
...
如指定的,定时器在 15:30
滴答。没有意外。让我们添加一个偏移量为 30 分钟。
$ ./scheduled.py --strat when='datetime.time(15,30)',offset='datetime.timedelta(minutes=30)'
strategy notify_timer with tid 0, when 2005-01-03 16:00:00 cheat False
1, 2005-01-03 17:30:00, Week 1, Day 1, O 2952.29, H 2989.61, L 2946.8, C 2970.02
strategy notify_timer with tid 0, when 2005-01-04 16:00:00 cheat False
2, 2005-01-04 17:30:00, Week 1, Day 2, O 2969.78, H 2979.88, L 2961.14, C 2971.12
strategy notify_timer with tid 0, when 2005-01-05 16:00:00 cheat False
...
时间从15:30
更改为16:00
。没有什么惊喜。让我们做同样的事情,但是引用会话开始。
$ ./scheduled.py --strat when='bt.timer.SESSION_START',offset='datetime.timedelta(minutes=30)'
strategy notify_timer with tid 0, when 2005-01-03 09:30:00 cheat False
1, 2005-01-03 17:30:00, Week 1, Day 1, O 2952.29, H 2989.61, L 2946.8, C 2970.02
strategy notify_timer with tid 0, when 2005-01-04 09:30:00 cheat False
2, 2005-01-04 17:30:00, Week 1, Day 2, O 2969.78, H 2979.88, L 2961.14, C 2971.12
...
Et voilá!回调调用的时间是09:30
。并且会话开始,见上文,是09:00
。这使得可以简单地说希望在会话开始后30 分钟执行某个动作。
让我们添加一个重复:
$ ./scheduled.py --strat when='bt.timer.SESSION_START',offset='datetime.timedelta(minutes=30)',repeat='datetime.timedelta(minutes=30)'
strategy notify_timer with tid 0, when 2005-01-03 09:30:00 cheat False
1, 2005-01-03 17:30:00, Week 1, Day 1, O 2952.29, H 2989.61, L 2946.8, C 2970.02
strategy notify_timer with tid 0, when 2005-01-04 09:30:00 cheat False
2, 2005-01-04 17:30:00, Week 1, Day 2, O 2969.78, H 2979.88, L 2961.14, C 2971.12
strategy notify_timer with tid 0, when 2005-01-05 09:30:00 cheat False
...
没有重复。原因是价格的分辨率是每日的。计时器像前一个示例一样在09:30
首次调用。但是当系统获取下一批价格时,它们发生在下一天。显然,计时器只能被调用一次。需要更低的分辨率。
但在转向更低的分辨率之前,让我们通过在会话结束之前调用计时器来作弊。
$ ./scheduled.py --strat when='bt.timer.SESSION_START',cheat=True
strategy notify_timer with tid 1, when 2005-01-03 09:00:00 cheat True
-- 2005-01-03 Create buy order
strategy notify_timer with tid 0, when 2005-01-03 09:00:00 cheat False
1, 2005-01-03 17:30:00, Week 1, Day 1, O 2952.29, H 2989.61, L 2946.8, C 2970.02
strategy notify_timer with tid 1, when 2005-01-04 09:00:00 cheat True
strategy notify_timer with tid 0, when 2005-01-04 09:00:00 cheat False
-- 2005-01-04 Buy Exec @ 2969.78
2, 2005-01-04 17:30:00, Week 1, Day 2, O 2969.78, H 2979.88, L 2961.14, C 2971.12
strategy notify_timer with tid 1, when 2005-01-05 09:00:00 cheat True
strategy notify_timer with tid 0, when 2005-01-05 09:00:00 cheat False
...
策略添加了一个带有cheat=True
的第 2 个计时器。这是添加的第 2 个,因此将接收到第 2 个tid
(计时器 id),即1
(请参见上述示例,分配的tid
为0
)
并且1
在0
之前被调用,因为该计时器正在作弊,并且在系统中的许多事件发生之前被调用(请参见上文的解释)
由于价格的每日分辨率,这并没有太大的区别,除了:
经纪人的coo=True
相同。
$ ./scheduled.py --strat when='bt.timer.SESSION_START',cheat=True --broker coo=True
strategy notify_timer with tid 1, when 2005-01-03 09:00:00 cheat True
-- 2005-01-03 Create buy order
strategy notify_timer with tid 0, when 2005-01-03 09:00:00 cheat False
-- 2005-01-03 Buy Exec @ 2952.29
1, 2005-01-03 17:30:00, Week 1, Day 1, O 2952.29, H 2989.61, L 2946.8, C 2970.02
strategy notify_timer with tid 1, when 2005-01-04 09:00:00 cheat True
strategy notify_timer with tid 0, when 2005-01-04 09:00:00 cheat False
2, 2005-01-04 17:30:00, Week 1, Day 2, O 2969.78, H 2979.88, L 2961.14, C 2971.12
strategy notify_timer with tid 1, when 2005-01-05 09:00:00 cheat True
strategy notify_timer with tid 0, when 2005-01-05 09:00:00 cheat False
...
有些事情发生了变化。
2005-01-03
发出
2005-01-03
以开盘价执行
实际上就像在市场真正开盘前几秒钟就已经根据开盘竞价行动了一样。
示例scheduled-min.py
默认使用backtrader发行的标准 5 分钟条形图运行。策略的参数被扩展以包括monthdays
和carry选项
class St(bt.Strategy):
params = dict(
when=bt.timer.SESSION_START,
timer=True,
cheat=False,
offset=datetime.timedelta(),
repeat=datetime.timedelta(),
weekdays=[],
weekcarry=False,
monthdays=[],
monthcarry=True,
)
数据具有相同的交易时间:
让我们进行一些实验。首先是一个单一的计时器。
$ ./scheduled-min.py --strat when='datetime.time(15, 30)'
1, 2006-01-02 09:05:00, Week 1, Day 1, O 3578.73, H 3587.88, L 3578.73, C 3582.99
2, 2006-01-02 09:10:00, Week 1, Day 1, O 3583.01, H 3588.4, L 3583.01, C 3588.03
...
77, 2006-01-02 15:25:00, Week 1, Day 1, O 3599.07, H 3599.68, L 3598.47, C 3599.68
strategy notify_timer with tid 0, when 2006-01-02 15:30:00 cheat False
78, 2006-01-02 15:30:00, Week 1, Day 1, O 3599.64, H 3599.73, L 3599.0, C 3599.67
...
179, 2006-01-03 15:25:00, Week 1, Day 2, O 3634.72, H 3635.0, L 3634.06, C 3634.87
strategy notify_timer with tid 0, when 2006-01-03 15:30:00 cheat False
180, 2006-01-03 15:30:00, Week 1, Day 2, O 3634.81, H 3634.89, L 3634.04, C 3634.23
...
计时器按要求在15:30
开始工作。日志显示了它在前两天如何工作。
将15 分钟
的repeat
添加到混合中
$ ./scheduled-min.py --strat when='datetime.time(15, 30)',repeat='datetime.timedelta(minutes=15)'
...
74, 2006-01-02 15:10:00, Week 1, Day 1, O 3596.12, H 3596.63, L 3595.92, C 3596.63
75, 2006-01-02 15:15:00, Week 1, Day 1, O 3596.36, H 3596.65, L 3596.19, C 3596.65
76, 2006-01-02 15:20:00, Week 1, Day 1, O 3596.53, H 3599.13, L 3596.12, C 3598.9
77, 2006-01-02 15:25:00, Week 1, Day 1, O 3599.07, H 3599.68, L 3598.47, C 3599.68
strategy notify_timer with tid 0, when 2006-01-02 15:30:00 cheat False
78, 2006-01-02 15:30:00, Week 1, Day 1, O 3599.64, H 3599.73, L 3599.0, C 3599.67
79, 2006-01-02 15:35:00, Week 1, Day 1, O 3599.61, H 3600.29, L 3599.52, C 3599.92
80, 2006-01-02 15:40:00, Week 1, Day 1, O 3599.96, H 3602.06, L 3599.76, C 3602.05
strategy notify_timer with tid 0, when 2006-01-02 15:45:00 cheat False
81, 2006-01-02 15:45:00, Week 1, Day 1, O 3601.97, H 3602.07, L 3601.45, C 3601.83
82, 2006-01-02 15:50:00, Week 1, Day 1, O 3601.74, H 3602.8, L 3601.63, C 3602.8
83, 2006-01-02 15:55:00, Week 1, Day 1, O 3602.53, H 3602.74, L 3602.33, C 3602.61
strategy notify_timer with tid 0, when 2006-01-02 16:00:00 cheat False
84, 2006-01-02 16:00:00, Week 1, Day 1, O 3602.58, H 3602.75, L 3601.81, C 3602.14
85, 2006-01-02 16:05:00, Week 1, Day 1, O 3602.16, H 3602.16, L 3600.86, C 3600.96
86, 2006-01-02 16:10:00, Week 1, Day 1, O 3601.2, H 3601.49, L 3600.94, C 3601.27
...
strategy notify_timer with tid 0, when 2006-01-02 17:15:00 cheat False
99, 2006-01-02 17:15:00, Week 1, Day 1, O 3603.96, H 3603.96, L 3602.89, C 3603.79
100, 2006-01-02 17:20:00, Week 1, Day 1, O 3603.94, H 3605.95, L 3603.87, C 3603.91
101, 2006-01-02 17:25:00, Week 1, Day 1, O 3604.0, H 3604.76, L 3603.85, C 3604.64
strategy notify_timer with tid 0, when 2006-01-02 17:30:00 cheat False
102, 2006-01-02 17:30:00, Week 1, Day 1, O 3604.06, H 3604.41, L 3603.95, C 3604.33
103, 2006-01-03 09:05:00, Week 1, Day 2, O 3604.08, H 3609.6, L 3604.08, C 3609.6
104, 2006-01-03 09:10:00, Week 1, Day 2, O 3610.34, H 3617.31, L 3610.34, C 3617.31
105, 2006-01-03 09:15:00, Week 1, Day 2, O 3617.61, H 3617.87, L 3616.03, C 3617.51
106, 2006-01-03 09:20:00, Week 1, Day 2, O 3617.24, H 3618.86, L 3616.09, C 3618.42
...
179, 2006-01-03 15:25:00, Week 1, Day 2, O 3634.72, H 3635.0, L 3634.06, C 3634.87
strategy notify_timer with tid 0, when 2006-01-03 15:30:00 cheat False
180, 2006-01-03 15:30:00, Week 1, Day 2, O 3634.81, H 3634.89, L 3634.04, C 3634.23
...
预期的是第 1 次调用在15:30
触发,然后每隔 15 分钟重复一次,直到17:30
会话结束。新会话开始时,计时器再次重置为15:30
。
现在在会话开始之前作弊
$ ./scheduled-min.py --strat when='bt.timer.SESSION_START',cheat=True
strategy notify_timer with tid 1, when 2006-01-02 09:00:00 cheat True
-- 2006-01-02 09:05:00 Create buy order
strategy notify_timer with tid 0, when 2006-01-02 09:00:00 cheat False
1, 2006-01-02 09:05:00, Week 1, Day 1, O 3578.73, H 3587.88, L 3578.73, C 3582.99
-- 2006-01-02 09:10:00 Buy Exec @ 3583.01
2, 2006-01-02 09:10:00, Week 1, Day 1, O 3583.01, H 3588.4, L 3583.01, C 3588.03
...
订单创建时间为09:05:00
,执行时间为09:10:00
,因为经纪人不处于欺骗开盘模式。让我们设置它……
$ ./scheduled-min.py --strat when='bt.timer.SESSION_START',cheat=True --broker coo=True
strategy notify_timer with tid 1, when 2006-01-02 09:00:00 cheat True
-- 2006-01-02 09:05:00 Create buy order
strategy notify_timer with tid 0, when 2006-01-02 09:00:00 cheat False
-- 2006-01-02 09:05:00 Buy Exec @ 3578.73
1, 2006-01-02 09:05:00, Week 1, Day 1, O 3578.73, H 3587.88, L 3578.73, C 3582.99
2, 2006-01-02 09:10:00, Week 1, Day 1, O 3583.01, H 3588.4, L 3583.01, C 3588.03
...
发行时间和执行时间为09:05:00
,执行价格为09:05:00
的开盘价。
计时器允许通过传递一个日期列表(按照 iso 规范,其中 Mon=1 且 Sun=7 的整数)来指定它们必须执行的日期,如
weekdays=[5]
,这将要求计时器仅在星期五有效
如果星期五是非交易日,而计时器应在下一个交易日启动,则可以添加 weekcarry=True
类似于此,可以决定在每个月的第 15 天采取行动:
monthdays=[15]
如果第 15 天恰好是非交易日,而计时器应在下一个交易日启动,则可以添加 monthcarry=True
对于诸如:三月、六月、九月和十二月的第 3 个星期五(期货/期权到期日)之类的事情并没有实现,但是可以通过传递来实现规则:
allow=callable
,其中可调用接受 datetime.date
实例。请注意,这不是 datetime.datetime
实例,因为 allow 可调用仅用于确定给定日期是否适合用于计时器。
要实现类似上面规则的内容:
class FutOpExp(object):
def __init__(self):
self.fridays = 0
self.curmonth = -1
def __call__(self, d):
_, _, isowkday = d.isocalendar()
if d.month != self.curmonth:
self.curmonth = d.month
self.fridays = 0
# Mon=1 ... Sun=7
if isowkday == 5 and self.curmonth in [3, 6, 9, 12]:
self.fridays += 1
if self.friday == 3: # 3rd Friday
return True # timer allowed
return False # timer disallowed`
并且一个将 allow=FutOpeExp()
传递给计时器的创建
这将允许计时器在这些月份的第 3 个星期五启动,并且可能在期货到期前平仓。
add_timer
的参数- `when`: can be
- `datetime.time` instance (see below `tzdata`)
- `bt.timer.SESSION_START` to reference a session start
- `bt.timer.SESSION_END` to reference a session end
offset
必须是一个 datetime.timedelta
实例
用于偏移值 when
的值。它与 SESSION_START
和 SESSION_END
结合使用时具有有意义的用途,例如指示计时器在会话开始后15 分钟
被调用。
repeat
必须是一个 datetime.timedelta
实例表示在第 1 次调用后,进一步的调用是否将在同一个会话中按计划的repeat
间隔内安排
一旦计时器超过会话结束,它将被重置为 when
的原始值
weekdays
:一个排序的可迭代对象,其中包含指示实际可以调用计时器的日期(ISO 代码,星期一为 1,星期日为 7)的整数如果未指定,计时器将在所有日期上活动
weekcarry
(默认:False
)。如果为 True
并且没有看到工作日(例如:交易假日),则计时器将在下一天执行(即使是在新的一周)
monthdays
:一个排序的可迭代对象,其中包含指示月份中哪些日期要执行计时器的整数。例如,总是在每个月的第 15 天
如果未指定,计时器将在所有日期上活动
monthcarry
(默认:True
)。如果没有看到该天(周末,交易假日),则计时器将在下一个可用日期执行。
allow
(默认:None
)。一个回调函数,接收一个 datetime.date
实例,并返回 True
(如果日期允许计时器)或返回 False
tzdata
可以是 None
(默认值),一个 pytz
实例或一个 数据源
实例。
None
:when
被按字面值解释(即使它不是,也要处理它为 UTC)
pytz
实例:when
将被解释为由时区实例指定的本地时间。
data feed
实例:when
将被解释为在数据源实例的tz
参数指定的本地时间中指定。
注意:如果when
是SESSION_START
或者
`SESSION_END` and `tzdata` is `None`, the 1st *data feed*
in the system (aka `self.data0`) will be used as the reference
to find out the session times.`
strats
(默认为False
)还会调用策略的notify_timer
cheat
(默认为False
)如果为True
,则计时器将在经纪人有机会评估订单之前被调用。这就打开了在会话开始之前例如根据开盘价发布订单的机会
\*args
:任何额外的参数将传递给notify_timer
\*\*kwargs
:任何额外的关键字参数将传递给notify_timer
scheduled.py
$ ./scheduled.py --help
usage: scheduled.py [-h] [--data0 DATA0] [--fromdate FROMDATE]
[--todate TODATE] [--cerebro kwargs] [--broker kwargs]
[--sizer kwargs] [--strat kwargs] [--plot [kwargs]]
Sample Skeleton
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: )
scheduled-min.py
$ ./scheduled-min.py --help
usage: scheduled-min.py [-h] [--data0 DATA0] [--fromdate FROMDATE]
[--todate TODATE] [--cerebro kwargs] [--broker kwargs]
[--sizer kwargs] [--strat kwargs] [--plot [kwargs]]
Timer Test Intraday
optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data to read in (default: ../../datas/2006-min-005.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: )
scheduled.py
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import backtrader as bt
class St(bt.Strategy):
params = dict(
when=bt.timer.SESSION_START,
timer=True,
cheat=False,
offset=datetime.timedelta(),
repeat=datetime.timedelta(),
weekdays=[],
)
def __init__(self):
bt.ind.SMA()
if self.p.timer:
self.add_timer(
when=self.p.when,
offset=self.p.offset,
repeat=self.p.repeat,
weekdays=self.p.weekdays,
)
if self.p.cheat:
self.add_timer(
when=self.p.when,
offset=self.p.offset,
repeat=self.p.repeat,
cheat=True,
)
self.order = None
def prenext(self):
self.next()
def next(self):
_, isowk, isowkday = self.datetime.date().isocalendar()
txt = '{}, {}, Week {}, Day {}, O {}, H {}, L {}, C {}'.format(
len(self), self.datetime.datetime(),
isowk, isowkday,
self.data.open[0], self.data.high[0],
self.data.low[0], self.data.close[0])
print(txt)
def notify_timer(self, timer, when, *args, **kwargs):
print('strategy notify_timer with tid {}, when {} cheat {}'.
format(timer.p.tid, when, timer.p.cheat))
if self.order is None and timer.p.cheat:
print('-- {} Create buy order'.format(self.data.datetime.date()))
self.order = self.buy()
def notify_order(self, order):
if order.status == order.Completed:
print('-- {} Buy Exec @ {}'.format(
self.data.datetime.date(), order.executed.price))
def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()
# Data feed kwargs
kwargs = dict(
timeframe=bt.TimeFrame.Days,
compression=1,
sessionstart=datetime.time(9, 0),
sessionend=datetime.time(17, 30),
)
# 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=(
'Sample Skeleton'
)
)
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()
scheduled-min.py
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import backtrader as bt
class St(bt.Strategy):
params = dict(
when=bt.timer.SESSION_START,
timer=True,
cheat=False,
offset=datetime.timedelta(),
repeat=datetime.timedelta(),
weekdays=[],
weekcarry=False,
monthdays=[],
monthcarry=True,
)
def __init__(self):
bt.ind.SMA()
if self.p.timer:
self.add_timer(
when=self.p.when,
offset=self.p.offset,
repeat=self.p.repeat,
weekdays=self.p.weekdays,
weekcarry=self.p.weekcarry,
monthdays=self.p.monthdays,
monthcarry=self.p.monthcarry,
# tzdata=self.data0,
)
if self.p.cheat:
self.add_timer(
when=self.p.when,
offset=self.p.offset,
repeat=self.p.repeat,
weekdays=self.p.weekdays,
weekcarry=self.p.weekcarry,
monthdays=self.p.monthdays,
monthcarry=self.p.monthcarry,
tzdata=self.data0,
cheat=True,
)
self.order = None
def prenext(self):
self.next()
def next(self):
_, isowk, isowkday = self.datetime.date().isocalendar()
txt = '{}, {}, Week {}, Day {}, O {}, H {}, L {}, C {}'.format(
len(self), self.datetime.datetime(),
isowk, isowkday,
self.data.open[0], self.data.high[0],
self.data.low[0], self.data.close[0])
print(txt)
def notify_timer(self, timer, when, *args, **kwargs):
print('strategy notify_timer with tid {}, when {} cheat {}'.
format(timer.p.tid, when, timer.p.cheat))
if self.order is None and timer.params.cheat:
print('-- {} Create buy order'.format(
self.data.datetime.datetime()))
self.order = self.buy()
def notify_order(self, order):
if order.status == order.Completed:
print('-- {} Buy Exec @ {}'.format(
self.data.datetime.datetime(), order.executed.price))
def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()
# Data feed kwargs
kwargs = dict(
timeframe=bt.TimeFrame.Minutes,
compression=5,
sessionstart=datetime.time(9, 0),
sessionend=datetime.time(17, 30),
)
# 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=(
'Timer Test Intraday'
)
)
parser.add_argument('--data0', default='../../datas/2006-min-005.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()