BackTrader 中文文档(二十二)(2)

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

BackTrader 中文文档(二十二)(1)https://developer.aliyun.com/article/1505425

样本的使用

$ ./macdsystem.py --help
usage: macdsystem.py [-h] (--data DATA | --dataset {yhoo,orcl,nvda})
                     [--fromdate FROMDATE] [--todate TODATE] [--cash CASH]
                     [--cashalloc CASHALLOC] [--commperc COMMPERC]
                     [--macd1 MACD1] [--macd2 MACD2] [--macdsig MACDSIG]
                     [--atrperiod ATRPERIOD] [--atrdist ATRDIST]
                     [--smaperiod SMAPERIOD] [--dirperiod DIRPERIOD]
                     [--riskfreerate RISKFREERATE] [--plot [kwargs]]
Sample for Tharp example with MACD
optional arguments:
  -h, --help            show this help message and exit
  --data DATA           Specific data to be read in (default: None)
  --dataset {yhoo,orcl,nvda}
                        Choose one of the predefined data sets (default: None)
  --fromdate FROMDATE   Starting date in YYYY-MM-DD format (default:
                        2005-01-01)
  --todate TODATE       Ending date in YYYY-MM-DD format (default: None)
  --cash CASH           Cash to start with (default: 50000)
  --cashalloc CASHALLOC
                        Perc (abs) of cash to allocate for ops (default: 0.2)
  --commperc COMMPERC   Perc (abs) commision in each operation. 0.001 -> 0.1%,
                        0.01 -> 1% (default: 0.0033)
  --macd1 MACD1         MACD Period 1 value (default: 12)
  --macd2 MACD2         MACD Period 2 value (default: 26)
  --macdsig MACDSIG     MACD Signal Period value (default: 9)
  --atrperiod ATRPERIOD
                        ATR Period To Consider (default: 14)
  --atrdist ATRDIST     ATR Factor for stop price calculation (default: 3.0)
  --smaperiod SMAPERIOD
                        Period for the moving average (default: 30)
  --dirperiod DIRPERIOD
                        Period for SMA direction calculation (default: 10)
  --riskfreerate RISKFREERATE
                        Risk free rate in Perc (abs) of the asset for the
                        Sharpe Ratio (default: 0.01)
  --plot [kwargs], -p [kwargs]
                        Plot the read data applying any kwargs passed For
                        example: --plot style="candle" (to plot candles)
                        (default: None)

还有代码本身

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
import argparse
import datetime
import random
import backtrader as bt
BTVERSION = tuple(int(x) for x in bt.__version__.split('.'))
class FixedPerc(bt.Sizer):
    '''This sizer simply returns a fixed size for any operation
    Params:
      - ``perc`` (default: ``0.20``) Perc of cash to allocate for operation
    '''
    params = (
        ('perc', 0.20),  # perc of cash to use for operation
    )
    def _getsizing(self, comminfo, cash, data, isbuy):
        cashtouse = self.p.perc * cash
        if BTVERSION > (1, 7, 1, 93):
            size = comminfo.getsize(data.close[0], cashtouse)
        else:
            size = cashtouse // data.close[0]
        return size
class TheStrategy(bt.Strategy):
    '''
    This strategy is loosely based on some of the examples from the Van
    K. Tharp book: *Trade Your Way To Financial Freedom*. The logic:
      - Enter the market if:
        - The MACD.macd line crosses the MACD.signal line to the upside
        - The Simple Moving Average has a negative direction in the last x
          periods (actual value below value x periods ago)
     - Set a stop price x times the ATR value away from the close
     - If in the market:
       - Check if the current close has gone below the stop price. If yes,
         exit.
       - If not, update the stop price if the new stop price would be higher
         than the current
    '''
    params = (
        # Standard MACD Parameters
        ('macd1', 12),
        ('macd2', 26),
        ('macdsig', 9),
        ('atrperiod', 14),  # ATR Period (standard)
        ('atrdist', 3.0),   # ATR distance for stop price
        ('smaperiod', 30),  # SMA Period (pretty standard)
        ('dirperiod', 10),  # Lookback period to consider SMA trend direction
    )
    def notify_order(self, order):
        if order.status == order.Completed:
            pass
        if not order.alive():
            self.order = None  # indicate no order is pending
    def __init__(self):
        self.macd = bt.indicators.MACD(self.data,
                                       period_me1=self.p.macd1,
                                       period_me2=self.p.macd2,
                                       period_signal=self.p.macdsig)
        # Cross of macd.macd and macd.signal
        self.mcross = bt.indicators.CrossOver(self.macd.macd, self.macd.signal)
        # To set the stop price
        self.atr = bt.indicators.ATR(self.data, period=self.p.atrperiod)
        # Control market trend
        self.sma = bt.indicators.SMA(self.data, period=self.p.smaperiod)
        self.smadir = self.sma - self.sma(-self.p.dirperiod)
    def start(self):
        self.order = None  # sentinel to avoid operrations on pending order
    def next(self):
        if self.order:
            return  # pending order execution
        if not self.position:  # not in the market
            if self.mcross[0] > 0.0 and self.smadir < 0.0:
                self.order = self.buy()
                pdist = self.atr[0] * self.p.atrdist
                self.pstop = self.data.close[0] - pdist
        else:  # in the market
            pclose = self.data.close[0]
            pstop = self.pstop
            if pclose < pstop:
                self.close()  # stop met - get out
            else:
                pdist = self.atr[0] * self.p.atrdist
                # Update only if greater than
                self.pstop = max(pstop, pclose - pdist)
DATASETS = {
    'yhoo': '../../datas/yhoo-1996-2014.txt',
    'orcl': '../../datas/orcl-1995-2014.txt',
    'nvda': '../../datas/nvda-1999-2014.txt',
}
def runstrat(args=None):
    args = parse_args(args)
    cerebro = bt.Cerebro()
    cerebro.broker.set_cash(args.cash)
    comminfo = bt.commissions.CommInfo_Stocks_Perc(commission=args.commperc,
                                                   percabs=True)
    cerebro.broker.addcommissioninfo(comminfo)
    dkwargs = dict()
    if args.fromdate is not None:
        fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
        dkwargs['fromdate'] = fromdate
    if args.todate is not None:
        todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
        dkwargs['todate'] = todate
    # if dataset is None, args.data has been given
    dataname = DATASETS.get(args.dataset, args.data)
    data0 = bt.feeds.YahooFinanceCSVData(dataname=dataname, **dkwargs)
    cerebro.adddata(data0)
    cerebro.addstrategy(TheStrategy,
                        macd1=args.macd1, macd2=args.macd2,
                        macdsig=args.macdsig,
                        atrperiod=args.atrperiod,
                        atrdist=args.atrdist,
                        smaperiod=args.smaperiod,
                        dirperiod=args.dirperiod)
    cerebro.addsizer(FixedPerc, perc=args.cashalloc)
    # Add TimeReturn Analyzers for self and the benchmark data
    cerebro.addanalyzer(bt.analyzers.TimeReturn, _name='alltime_roi',
                        timeframe=bt.TimeFrame.NoTimeFrame)
    cerebro.addanalyzer(bt.analyzers.TimeReturn, data=data0, _name='benchmark',
                        timeframe=bt.TimeFrame.NoTimeFrame)
    # Add TimeReturn Analyzers fot the annuyl returns
    cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.Years)
    # Add a SharpeRatio
    cerebro.addanalyzer(bt.analyzers.SharpeRatio, timeframe=bt.TimeFrame.Years,
                        riskfreerate=args.riskfreerate)
    # Add SQN to qualify the trades
    cerebro.addanalyzer(bt.analyzers.SQN)
    cerebro.addobserver(bt.observers.DrawDown)  # visualize the drawdown evol
    results = cerebro.run()
    st0 = results[0]
    for alyzer in st0.analyzers:
        alyzer.print()
    if args.plot:
        pkwargs = dict(style='bar')
        if args.plot is not True:  # evals to True but is not True
            npkwargs = eval('dict(' + args.plot + ')')  # args were passed
            pkwargs.update(npkwargs)
        cerebro.plot(**pkwargs)
def parse_args(pargs=None):
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description='Sample for Tharp example with MACD')
    group1 = parser.add_mutually_exclusive_group(required=True)
    group1.add_argument('--data', required=False, default=None,
                        help='Specific data to be read in')
    group1.add_argument('--dataset', required=False, action='store',
                        default=None, choices=DATASETS.keys(),
                        help='Choose one of the predefined data sets')
    parser.add_argument('--fromdate', required=False,
                        default='2005-01-01',
                        help='Starting date in YYYY-MM-DD format')
    parser.add_argument('--todate', required=False,
                        default=None,
                        help='Ending date in YYYY-MM-DD format')
    parser.add_argument('--cash', required=False, action='store',
                        type=float, default=50000,
                        help=('Cash to start with'))
    parser.add_argument('--cashalloc', required=False, action='store',
                        type=float, default=0.20,
                        help=('Perc (abs) of cash to allocate for ops'))
    parser.add_argument('--commperc', required=False, action='store',
                        type=float, default=0.0033,
                        help=('Perc (abs) commision in each operation. '
                              '0.001 -> 0.1%%, 0.01 -> 1%%'))
    parser.add_argument('--macd1', required=False, action='store',
                        type=int, default=12,
                        help=('MACD Period 1 value'))
    parser.add_argument('--macd2', required=False, action='store',
                        type=int, default=26,
                        help=('MACD Period 2 value'))
    parser.add_argument('--macdsig', required=False, action='store',
                        type=int, default=9,
                        help=('MACD Signal Period value'))
    parser.add_argument('--atrperiod', required=False, action='store',
                        type=int, default=14,
                        help=('ATR Period To Consider'))
    parser.add_argument('--atrdist', required=False, action='store',
                        type=float, default=3.0,
                        help=('ATR Factor for stop price calculation'))
    parser.add_argument('--smaperiod', required=False, action='store',
                        type=int, default=30,
                        help=('Period for the moving average'))
    parser.add_argument('--dirperiod', required=False, action='store',
                        type=int, default=10,
                        help=('Period for SMA direction calculation'))
    parser.add_argument('--riskfreerate', required=False, action='store',
                        type=float, default=0.01,
                        help=('Risk free rate in Perc (abs) of the asset for '
                              'the Sharpe Ratio'))
    # Plot options
    parser.add_argument('--plot', '-p', nargs='?', required=False,
                        metavar='kwargs', const=True,
                        help=('Plot the read data applying any kwargs passed\n'
                              '\n'
                              'For example:\n'
                              '\n'
                              '  --plot style="candle" (to plot candles)\n'))
    if pargs is not None:
        return parser.parse_args(pargs)
    return parser.parse_args()
if __name__ == '__main__':
    runstrat()

Pinkfish 挑战

原文:www.backtrader.com/blog/posts/2016-07-29-pinkfish-challenge/pinkfish-challenge/

(样本和更改添加到版本 1.7.1.93)

在发展过程中,backtrader 已经变得更加成熟,新增了功能,当然也变得更加复杂。许多新功能是在用户的请求、评论和问题之后引入的。一些小挑战证明了大多数设计决策至少不是那么错误,即使有些事情可能有很多其他方式来完成,有时可能更好。

因此,似乎这些小挑战是为了测试平台对新的未计划和意外情况的灵活性和适应性,pinkfish挑战是另一个例子。pinkfish是另一个 Python 回测框架(在README中列出),可以在以下网址找到:pinkfish。该网站包含了需要解决的挑战:

  • ‘买入收盘价’在‘新的 20 日高点设定’的当天是不允许的

其中一个特点提供了平台如何为这样的壮举运作的提示:

  • 使用每日数据(而不是分钟或 tick 数据)进行日内交易

作者对当时现有的回测库的复杂性感到厌烦。当时的情况是否适用于backtrader(当时还处于初期阶段)是由pinkfish的作者自己回答的问题。

无修改解决方案

backtrader 支持数据源的过滤器,其中一个允许

breaking a *daily bar* in 2 parts to let people buy after having seen only the
opening price. The 2nd part of the day (high, low, close) is evaluated in a
2nd tick. This effectively achieves the *uses daily data (vs minute or tick
data) for intraday trading*.

这个筛选器试图进行完整的重播操作,而不涉及内置的重播器。

这个筛选器的明显演变将每日柱破解为两根柱,第一根是(开盘价,最高价,最低价),然后是第二根完整的柱(开盘价,最高价,最低价,收盘价)。

买入收盘价是通过使用backtrader.Order.Close作为执行类型来实现的。

这在可用的样本中使用-no-replay。一个执行:

$ ./pinkfish-challenge.py --no-replay

输出的一部分:

...
0955,0478,0478,2006-11-22T00:00:00,27.51,28.56,27.29,28.49,16027900.00,0.00
High 28.56 > Highest 28.56
LAST 19 highs: array('d', [25.33, 25.6, 26.4, 26.7, 26.62, 26.6, 26.7, 26.7, 27.15, 27.25, 27.65, 27.5, 27.62,   27.5, 27.5, 27.33, 27.05, 27.04, 27.34])
-- BUY on date: 2006-11-22
-- BUY Completed on: 2006-11-22
-- BUY Price: 28.49
0956,0478,0478,2006-11-22T23:59:59.999989,27.51,28.56,27.29,28.49,32055800.00,0.00
...

它有效…

  • 在看到当天第一部分(行:0955)之后
  • 如果有一个新的 20 日高点,就会发出一个Close订单
  • 订单是通过当天第二部分的收盘价格执行的(行:0956
    收盘价为28.49,这是在策略中notify_order中看到的买入价格

输出包含相当冗长的部分,仅用于识别最后的20个高点。样本也非常快速出售,以便多次测试行为。但持有期可以通过--sellafter N进行更改,其中N是取消之前持有的柱数(请参见末尾的用法

no mod解决方案的问题

这实际上不是一个重播解决方案,如果将订单的执行类型Close更改为Market,就会看到这一点。一个新的执行:

$ ./pinkfish-challenge.py --no-replay --market

现在与上述相同期间的输出:

...
0955,0478,0478,2006-11-22T00:00:00,27.51,28.56,27.29,28.49,16027900.00,0.00
High 28.56 > Highest 28.56
LAST 19 highs: array('d', [25.33, 25.6, 26.4, 26.7, 26.62, 26.6, 26.7, 26.7, 27.15, 27.25, 27.65, 27.5, 27.62, 27.5, 27.5, 27.33, 27.05, 27.04, 27.34])
-- BUY on date: 2006-11-22
-- BUY Completed on: 2006-11-22
-- BUY Price: 27.51
0956,0478,0478,2006-11-22T23:59:59.999989,27.51,28.56,27.29,28.49,32055800.00,0.00
...

问题很容易被识别出来

  • 订单执行时,与收盘价相反,因为市价订单取第二根柱中可用的第一价,即27.51,而这恰好是当天的开盘价,不再可用
    这是因为过滤器实际上并非真正回放,而是将柱子分成两部分,并进行软性回放

正确的“mod”解决方案

同样获取Market订单以选择收盘价。

这包括:

  • 一个将柱子分成两部分的过滤器
  • 并且与backtrader中可用的标准回放功能兼容
    在这种情况下,第二根柱仅由close价格组成,即使显示显示完整的柱,内部机制也只会将订单与tick匹配

backtrader中的链接过滤器已经是可能的,但这种用法尚未考虑:

  • 将单个数据“心跳”拆分为 2 个数据“心跳”
    在此挑战之前,主要是将柱子合并为较大的柱子。

对核心机制加载柱进行了小扩展,允许过滤器将柱的第二部分添加到内部存储中以进行重新处理,然后再考虑新数据心跳。而且因为它是一个扩展而不是修改,所以没有影响。

此挑战还提供了机会:

  • 再次查看backtrader最初编写的早期代码以获取Close订单。
    在这里,一些代码行和if条件已经重新设计,以使匹配Close订单更加合乎逻辑,并且如果可能的话,将其立即交付给系统(即使匹配到正确的柱上,交付也通常会延迟 1 根柱)

在这些变化之后的一个好处是:

  • 过滤器中的逻辑更加简单,因为没有微妙的回放尝试。 回放由回放过滤器完成。

分解柱子第一部分的过滤器解剖:

  1. 复制传入数据柱
  2. 将其复制为OHL柱(无 Close)
  3. 将时间更改为日期 + sessionstart时间
  4. 移除部分体积(由参数closevol指定给过滤器)
  5. 使OpenInterest失效(在当天结束时可用)
  6. 移除close价格并用OHL的平均值替换它
  7. 将柱子添加到内部以供下一个过滤器或策略立即处理(回放过滤器将接管)

分解柱子第二部分的解剖:

  1. 复制传入数据柱
  2. 将 OHL 价格替换为Close价格
  3. 将时间更改为日期 + sessionend时间
  4. 移除体积的其他部分(由参数closevol指定给过滤器)
  5. 设置OpenInterest
  6. 将柱子添加到内部stash以延迟处理为下一个数据心跳,而不是从数据中获取价格

代码:

# Make a copy of current data for ohlbar
        ohlbar = [data.lines[i][0] for i in range(data.size())]
        closebar = ohlbar[:]  # Make a copy for the close
        # replace close price with o-h-l average
        ohlprice = ohlbar[data.Open] + ohlbar[data.High] + ohlbar[data.Low]
        ohlbar[data.Close] = ohlprice / 3.0
        vol = ohlbar[data.Volume]  # adjust volume
        ohlbar[data.Volume] = vohl = int(vol * (1.0 - self.p.closevol))
        oi = ohlbar[data.OpenInterest]  # adjust open interst
        ohlbar[data.OpenInterest] = 0
        # Adjust times
        dt = datetime.datetime.combine(datadt, data.p.sessionstart)
        ohlbar[data.DateTime] = data.date2num(dt)
        # Adjust closebar to generate a single tick -> close price
        closebar[data.Open] = cprice = closebar[data.Close]
        closebar[data.High] = cprice
        closebar[data.Low] = cprice
        closebar[data.Volume] = vol - vohl
        ohlbar[data.OpenInterest] = oi
        # Adjust times
        dt = datetime.datetime.combine(datadt, data.p.sessionend)
        closebar[data.DateTime] = data.date2num(dt)
        # Update stream
        data.backwards(force=True)  # remove the copied bar from stream
        data._add2stack(ohlbar)  # add ohlbar to stack
        # Add 2nd part to stash to delay processing to next round
        data._add2stack(closebar, stash=True)
        return False  # the length of the stream was not changed

在不禁用回放Close的情况下执行(让我们添加绘图):

$ ./pinkfish-challenge.py --plot

同一时期的输出:

...
0955,0478,0478,2006-11-22T00:00:00,27.51,28.56,27.29,27.79,16027900.00,0.00
High 28.56 > Highest 28.56
LAST 19 highs: array('d', [25.33, 25.6, 26.4, 26.7, 26.62, 26.6, 26.7, 26.7, 27.15, 27.25, 27.65, 27.5, 27.62, 27.5, 27.5, 27.33, 27.05, 27.04, 27.34])
-- BUY on date: 2006-11-22
-- BUY Completed on: 2006-11-22
-- BUY Price: 28.49
0956,0478,0478,2006-11-22T23:59:59.999989,27.51,28.56,27.29,28.49,32055800.00,0.00
...

一切正常,已记录收盘价为28.49

以及图表。

最后但同样重要的是检查修改是否有意义:

$ ./pinkfish-challenge.py --market

相同时期的输出:

...
0955,0478,0478,2006-11-22T00:00:00,27.51,28.56,27.29,27.79,16027900.00,0.00
High 28.56 > Highest 28.56
LAST 19 highs: array('d', [25.33, 25.6, 26.4, 26.7, 26.62, 26.6, 26.7, 26.7, 27.15, 27.25, 27.65, 27.5, 27.62, 27.5, 27.5, 27.33, 27.05, 27.04, 27.34])
-- BUY on date: 2006-11-22
-- BUY Completed on: 2006-11-22
-- BUY Price: 28.49
0956,0478,0478,2006-11-22T23:59:59.999989,27.51,28.56,27.29,28.49,32055800.00,0.00
..

现在Market订单正在以与Close订单相同的价格28.49拾取,这在这个特定的用例中是预期的,因为重播正在发生,而破碎的日线的第二部分有一个单一的标记28.49,这是收盘

示例的用法

$ ./pinkfish-challenge.py --help
usage: pinkfish-challenge.py [-h] [--data DATA] [--fromdate FROMDATE]
                             [--todate TODATE] [--cash CASH]
                             [--sellafter SELLAFTER] [--highperiod HIGHPERIOD]
                             [--no-replay] [--market] [--oldbuysell]
                             [--plot [kwargs]]
Sample for pinkfish challenge
optional arguments:
  -h, --help            show this help message and exit
  --data DATA           Data to be read in (default:
                        ../../datas/yhoo-1996-2015.txt)
  --fromdate FROMDATE   Starting date in YYYY-MM-DD format (default:
                        2005-01-01)
  --todate TODATE       Ending date in YYYY-MM-DD format (default: 2006-12-31)
  --cash CASH           Cash to start with (default: 50000)
  --sellafter SELLAFTER
                        Sell after so many bars in market (default: 2)
  --highperiod HIGHPERIOD
                        Period to look for the highest (default: 20)
  --no-replay           Use Replay + replay filter (default: False)
  --market              Use Market exec instead of Close (default: False)
  --oldbuysell          Old buysell plot behavior - ON THE PRICE (default:
                        False)
  --plot [kwargs], -p [kwargs]
                        Plot the read data applying any kwargs passed For
                        example (escape the quotes if needed): --plot
                        style="candle" (to plot candles) (default: None)


BackTrader 中文文档(二十二)(3)https://developer.aliyun.com/article/1505429

相关文章
|
21天前
|
存储 Python
BackTrader 中文文档(二十二)(3)
BackTrader 中文文档(二十二)
21 0
|
21天前
|
测试技术
BackTrader 中文文档(二十二)(4)
BackTrader 中文文档(二十二)
19 0
|
21天前
BackTrader 中文文档(二十二)(1)
BackTrader 中文文档(二十二)
17 0
|
21天前
|
编解码 索引
BackTrader 中文文档(二十三)(2)
BackTrader 中文文档(二十三)
15 0
|
21天前
|
编解码 API Windows
BackTrader 中文文档(二十三)(3)
BackTrader 中文文档(二十三)
17 0
|
21天前
BackTrader 中文文档(二十三)(4)
BackTrader 中文文档(二十三)
14 0
|
21天前
|
Oracle 测试技术 编译器
BackTrader 中文文档(二十三)(1)
BackTrader 中文文档(二十三)
16 0
|
21天前
BackTrader 中文文档(二十六)(1)
BackTrader 中文文档(二十六)
17 0
|
21天前
|
Oracle 数据可视化 关系型数据库
BackTrader 中文文档(二十六)(3)
BackTrader 中文文档(二十六)
13 0
|
21天前
|
数据可视化 测试技术 API
BackTrader 中文文档(二十六)(4)
BackTrader 中文文档(二十六)
16 0

热门文章

最新文章