BackTrader 中文文档(二十八)(4)

简介: BackTrader 中文文档(二十八)

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()

组合继承的选择是一个经典问题。这个例子并不是为了澄清哪种更好,而更多是为了展示:

注意

即使存在linesparams的元定义,它们也继承自基类的元定义

最后是代码和图表,展示两个版本的运行情况。

  1. 第一个展示了继承版本
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()

  1. 第一个展示了组合版本
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()


相关文章
|
5天前
BackTrader 中文文档(二十八)(2)
BackTrader 中文文档(二十八)
11 0
|
5天前
|
存储 算法 C++
BackTrader 中文文档(二十八)(3)
BackTrader 中文文档(二十八)
11 0
|
5天前
|
Python
BackTrader 中文文档(二十八)(1)
BackTrader 中文文档(二十八)
10 0
|
5天前
BackTrader 中文文档(二十九)(2)
BackTrader 中文文档(二十九)
11 0
|
5天前
|
算法 索引
BackTrader 中文文档(二十九)(1)
BackTrader 中文文档(二十九)
11 0
|
5天前
BackTrader 中文文档(二十九)(3)
BackTrader 中文文档(二十九)
10 0
|
5天前
|
存储 API
BackTrader 中文文档(二十九)(4)
BackTrader 中文文档(二十九)
14 0
|
5天前
|
存储 数据库连接 数据库
BackTrader 中文文档(二十七)(4)
BackTrader 中文文档(二十七)
10 0
|
5天前
BackTrader 中文文档(二十七)(1)
BackTrader 中文文档(二十七)
10 0
|
5天前
|
算法 索引 Python
BackTrader 中文文档(二十七)(2)
BackTrader 中文文档(二十七)
15 0