BackTrader 中文文档(十六)(1)https://developer.aliyun.com/article/1505374
基金跟踪示例
可选参数:
-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 年的来源。
并稍微放大以更好地欣赏差异
允许辅助演员重新调整 y 轴的比例
数据源的轴始终将主数据源用作比例所有者,因为数据始终是视图中最重要的部分。例如,考虑一下布林带,可能会导致顶部带远离数据的最大值,并且允许此带重新调整图表,将减少数据在图表中占用的空间,这是不希望的。
现在可以使用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 将很有用。
结论
选择已经完全自动化。与之前一样,可以想象类似于查询数据库以获取可用策略数量,然后逐个获取策略的方法。
BackTrader 中文文档(十六)(3)https://developer.aliyun.com/article/1505377