BackTrader 中文文档(二十八)(3)https://developer.aliyun.com/article/1505488
现在是与 SMA 交叉的“真实”比较
使用 SimpleMovingAverage 交叉作为入场/出场信号,将使用类似期货
的佣金方案对同一数据集进行测试,然后再使用类似股票
的方案。
注意
期货头寸不仅可以在每次发生时赋予进入/退出行为,还可以在每次发生时赋予反转行为。但是,此示例是关于比较佣金方案的。
代码(请参阅底部获取完整策略)是相同的,可以在定义策略之前选择方案。
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()
多核优化
www.backtrader.com/blog/posts/2015-07-23-multicore-optimization/multicore-optimization/
利用所有可用核心是我对 backtrader 有的想法,但从未实现。支持自然操作,移除数组表示法,包含新的指标等等。
实际上,我并不是优化的忠实粉丝,因此对于为此利用所有核心也不是忠实粉丝。在我看来,一个好主意值得百万次优化。
注意
初始的多核支持已经存在,并且对于众所周知的一组测试用例有效。鉴于pickle
所展示的行为,预计还需要进行一些其他调整,以确保在进行多核优化时可以在进程之间传递所有指标和函数。
注意
针对多核的一些额外校正已经发布为 1.0.10.88 版本,以使更多的“不可序列化”项变得可序列化。到目前为止,指标测试没有出现任何问题。
但是 BigMikeTrading 论坛上有人询问这个平台相比其他平台有什么优势,我提到了一些功能,包括PyAlgoTrade,例如,已经有了(甚至是多机器的)。
这需要做一点小而正确的推动。根据过去的经验以及因为互联网上充满了参考资料,我已经知道:多线程即使是最简单的(无论 GIL 律师们可能说什么),在 Python 中也是行不通的,无论版本如何。在 Python 中,多线程是假的,因为你有多个线程,但没有代码的并行执行。在 Python 中使用多线程可能会创建抽象,并用 IO 绑定的线程分开代码路径的执行,但这确实是一个致命问题。
那么我只剩下一个选择:模块multiprocessing
或类似的模块。
展望光明的未来,我决定选择现代版本:concurrent.futures
(后来证明是一个错误的选择)。即使这意味着为 Python 2.6/2.7 支持添加外部依赖。
历史:
- Python 的一些动态特性与在进程之间发送数据不兼容
- 当序列化一些像类不在模块级别定义、lambda 表达式、对实例方法的引用以及没有唯一名称的动态类(即使类本身是唯一的)时,所涉及的模块(
pickle
)会出错。
我把这些东西散落在代码中。然后我发现了dill和 pathos 多进程的兄弟姐妹pypi.python.org/pypi/multiprocess
。显然它们可以解决序列化问题,但是添加更多的外部依赖……不行不行。
回到起点,看看那些不可序列化的项是否可以被序列化,即使pickle
模块产生了一些错误,这将使一些旧的 GCC 开发人员非常高兴。
它完成了吗……还是没有?
- 将不可选的项目改造为可选项目
- 用 Python 2.7.9 进行测试,并像风一样轻松地运行……我的机器的 8 个核心顺畅且令人耳目一新
- 使用 Python 3.4.3 进行测试,8 个核心开始运作,但在进行一些优化后,每个后续策略的执行时间会越来越长……直到不堪忍受为止。
显然,将结果(完整的执行策略)反向 pickling 到主进程中触及了一些与内存分配相关的限制(我的机器有大量空闲 RAM……足够多以进行几小时的并行优化)
阅读了一些额外的内容后,我考虑简化我的情景:
- 使用
concurrent.futures
看起来更具未来性 - 但标准的
multiprocessing
模块已经具备了backtrader
所需的功能
闻起来好像有点过度,一些行被迅速改写成:
- 测试用 Python 2.7 运行正常(甚至比以前更快)
- 测试用 Python 3.4 同样快速运行
进行清理,运行完整的一系列测试并执行推送,发布 1.0.9.88。没有新的指标……只是多核优化的普通旧方式
读完这些……是时候写一个关于如何控制优化以使用多个核心的清爽脚本了
- 好消息……不需要做任何事情……它在用户不介入的情况下完成了
当用户希望优化 strategy
时,Strategy
子类将被添加到 Cerebro
实例中,如下所示:
cerebro.optstrategy(StrategyClass, *args, **kwargs)
与向 Cerebro
传递策略的常规方式相反:
cerebro.addstrategy(StrategyClass, *args, **kwargs)
这一直都是这样,没有改变。背景是:
Cerebro
需要了解是否要优化策略,以正确处理可能已经是常规策略的策略的参数
现在……通过 optstrategy
传递给 cerebro
的策略将获得使用机器上所有可用核心的额外好处。
当然,如果最终用户希望对使用的核心进行精细控制……是可能的。创建 Cerebro
的标准方式:
cerebro = bt.Cerebro() # runonce 为 True,preload 为 True,且 “new” maxcpus 为 None
maxcpus
(此版本中的新参数)是控制键:
- maxcpus = None -> 使用所有可用的 CPU
- maxcpus = 1 -> 不要运行多核
- maxcpues = 2 … -> 使用指定数量的核心
这是一种选择退出策略,因为多核已经存在。
在拥有 16 GBytes RAM 的 4 核(每核 2 个线程 - 总共 8 个逻辑处理器)机器上进行比较,运行 Windows 8.1 和 Python 64 位 2.7.9
- 使用 1 个核心执行:326 秒
- 使用 8 个核心执行:127 秒
不同的测试运行显示,平均比例约为 2.75:1。
不幸的是,进程的创建/销毁和对象的反复 pickling 带来了潜在的好处,但加速效果仍然显著。
图像显示了正在使用的 8 个核心。
代码如下。只需将maxcpus
参数的1
更改为限制测试为 1 个核心。
from __future__ import (absolute_import, division, print_function, unicode_literals) import time from six.moves import xrange import backtrader as bt import backtrader.indicators as btind import backtrader.feeds as btfeeds class OptimizeStrategy(bt.Strategy): params = (('smaperiod', 15), ('macdperiod1', 12), ('macdperiod2', 26), ('macdperiod3', 9), ) def __init__(self): # Add indicators to add load btind.SMA(period=self.p.smaperiod) btind.MACD(period_me1=self.p.macdperiod1, period_me2=self.p.macdperiod2, period_signal=self.p.macdperiod3) if __name__ == '__main__': # Create a cerebro entity cerebro = bt.Cerebro(maxcpus=None) # Add a strategy cerebro.optstrategy( OptimizeStrategy, smaperiod=xrange(5, 40), macdperiod1=xrange(12, 20), macdperiod2=xrange(26, 30), macdperiod3=xrange(9, 15), ) # 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) # clock the start of the process tstart = time.clock() # Run over everything stratruns = cerebro.run() # clock the end of the process tend = time.clock() print('==================================================') for stratrun in stratruns: print('**************************************************') for strat in stratrun: print('--------------------------------------------------') print(strat.p._getkwargs()) print('==================================================') # print out the result print('Time used:', str(tend - tstart))
扩展一个指标
原文:
www.backtrader.com/blog/posts/2015-07-20-extending-an-indicator/extending-an-indicator/
在面向对象编程中,当然也包括 Python 本身,对现有类的扩展可以通过两种方式实现。
- 继承(或子类化)
- 组合(或嵌入)
在开发一个指标时,指标Trix
只需几行代码就可以开发完成。ChartSchool - Trix 参考文献中有一个带有信号线的Trix
,显示了与 MACD 的相似之处。
让我们使用已经开发的Trix
“组合”MyTrixSignal
class MyTrixSignalComposed(bt.Indicator): lines = ('trix', 'signal') params = (('period', 15), ('sigperiod', 9)) def __init__(self): self.lines.trix = MyTrix(self.data, period=self.p.period) self.lines.signal = btind.EMA(self.lines.trix, period=self.p.sigperiod)
在定义中有一些必须重复的内容,比如trix
线的名称和用于计算的period
。定义了一个新的signal
线和相应的sigperiod
参数。
这个两行的结果很好。
现在让我们来看看继承,但首先回顾一下Trix
的样子:
class MyTrix(bt.Indicator): lines = ('trix',) params = (('period', 15),) def __init__(self): ema1 = btind.EMA(self.data, period=self.p.period) ema2 = btind.EMA(ema1, period=self.p.period) ema3 = btind.EMA(ema2, period=self.p.period) self.lines.trix = 100.0 * (ema3 - ema3(-1)) / ema3(-1)
使用Trix
作为基类,这是TrixSignal
的外观
class MyTrixSignalInherited(MyTrix): lines = ('signal',) params = (('sigperiod', 9),) def __init__(self): super(MyTrixSignalInherited, self).__init__() self.lines.signal = btind.EMA(self.lines.trix, period=self.p.sigperiod)
继承的指标最终也是一个两行代码,但是:
- 不需要重新定义
trix
线 - 不需要重新定义
period
参数
两者都是从基类Trix
继承而来。trix
线的计算是在基类__init__
方法中完成的:
- super(MyTrixSignalInherited, self).init()
组合与继承的选择是一个经典问题。这个例子并不是为了澄清哪种更好,而更多是为了展示:
注意
即使存在lines和params的元定义,它们也继承自基类的元定义
最后是代码和图表,展示两个版本的运行情况。
- 第一个展示了继承版本
from __future__ import (absolute_import, division, print_function, unicode_literals) import backtrader as bt import backtrader.feeds as btfeeds from mytrix import MyTrixSignalInherited class NoStrategy(bt.Strategy): params = (('trixperiod', 15), ('analyzer', False),) def __init__(self): MyTrixSignalInherited(self.data, period=self.p.trixperiod) if __name__ == '__main__': # Create a cerebro entity cerebro = bt.Cerebro() # Add a strategy cerebro.addstrategy(NoStrategy, trixperiod=15) # 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) # Run over everything cerebro.run() # Plot the result cerebro.plot()
- 第一个展示了组合版本
from __future__ import (absolute_import, division, print_function, unicode_literals) import backtrader as bt import backtrader.feeds as btfeeds from mytrix import MyTrixSignalComposed class NoStrategy(bt.Strategy): params = (('trixperiod', 15), ('analyzer', False),) def __init__(self): MyTrixSignalComposed(self.data, period=self.p.trixperiod) if __name__ == '__main__': # Create a cerebro entity cerebro = bt.Cerebro() # Add a strategy cerebro.addstrategy(NoStrategy, trixperiod=15) # 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) # Run over everything cerebro.run() # Plot the result cerebro.plot()