BackTrader 中文文档(二十七)(3)

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

BackTrader 中文文档(二十七)(2)https://developer.aliyun.com/article/1505478

以及代码:

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
import argparse
import datetime
import inspect
import itertools
import random
import string
import sys
import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btinds
import backtrader.observers as btobs
import backtrader.strategies as btstrats
import backtrader.analyzers as btanalyzers
DATAFORMATS = dict(
    btcsv=btfeeds.BacktraderCSVData,
    vchartcsv=btfeeds.VChartCSVData,
    vchart=btfeeds.VChartData,
    sierracsv=btfeeds.SierraChartCSVData,
    yahoocsv=btfeeds.YahooFinanceCSVData,
    yahoocsv_unreversed=btfeeds.YahooFinanceCSVData
)
def runstrat():
    args = parse_args()
    stdstats = not args.nostdstats
    cerebro = bt.Cerebro(stdstats=stdstats)
    for data in getdatas(args):
        cerebro.adddata(data)
    # Prepare a dictionary of extra args passed to push them to the strategy
    # pack them in pairs
    packedargs = itertools.izip_longest(*[iter(args.args)] * 2, fillvalue='')
    # prepare a string for evaluation, eval and store the result
    evalargs = 'dict('
    for key, value in packedargs:
        evalargs += key + '=' + value + ','
    evalargs += ')'
    stratkwargs = eval(evalargs)
    # Get the strategy and add it with any arguments
    strat = getstrategy(args)
    cerebro.addstrategy(strat, **stratkwargs)
    obs = getobservers(args)
    for ob in obs:
        cerebro.addobserver(ob)
    ans = getanalyzers(args)
    for an in ans:
        cerebro.addanalyzer(an)
    setbroker(args, cerebro)
    runsts = cerebro.run()
    runst = runsts[0]  # single strategy and no optimization
    if runst.analyzers:
        print('====================')
        print('== Analyzers')
        print('====================')
        for name, analyzer in runst.analyzers.getitems():
            print('## ', name)
            analysis = analyzer.get_analysis()
            for key, val in analysis.items():
                print('-- ', key, ':', val)
    if not args.noplot:
        cerebro.plot(numfigs=args.plotfigs, style=args.plotstyle)
def setbroker(args, cerebro):
    broker = cerebro.getbroker()
    if args.cash is not None:
        broker.setcash(args.cash)
    commkwargs = dict()
    if args.commission is not None:
        commkwargs['commission'] = args.commission
    if args.margin is not None:
        commkwargs['margin'] = args.margin
    if args.mult is not None:
        commkwargs['mult'] = args.mult
    if commkwargs:
        broker.setcommission(**commkwargs)
def getdatas(args):
    # Get the data feed class from the global dictionary
    dfcls = DATAFORMATS[args.csvformat]
    # Prepare some args
    dfkwargs = dict()
    if args.csvformat == 'yahoo_unreversed':
        dfkwargs['reverse'] = True
    fmtstr = '%Y-%m-%d'
    if args.fromdate:
        dtsplit = args.fromdate.split('T')
        if len(dtsplit) > 1:
            fmtstr += 'T%H:%M:%S'
        fromdate = datetime.datetime.strptime(args.fromdate, fmtstr)
        dfkwargs['fromdate'] = fromdate
    fmtstr = '%Y-%m-%d'
    if args.todate:
        dtsplit = args.todate.split('T')
        if len(dtsplit) > 1:
            fmtstr += 'T%H:%M:%S'
        todate = datetime.datetime.strptime(args.todate, fmtstr)
        dfkwargs['todate'] = todate
    datas = list()
    for dname in args.data:
        dfkwargs['dataname'] = dname
        data = dfcls(**dfkwargs)
        datas.append(data)
    return datas
def getmodclasses(mod, clstype, clsname=None):
    clsmembers = inspect.getmembers(mod, inspect.isclass)
    clslist = list()
    for name, cls in clsmembers:
        if not issubclass(cls, clstype):
            continue
        if clsname:
            if clsname == name:
                clslist.append(cls)
                break
        else:
            clslist.append(cls)
    return clslist
def loadmodule(modpath, modname=''):
    # generate a random name for the module
    if not modname:
        chars = string.ascii_uppercase + string.digits
        modname = ''.join(random.choice(chars) for _ in range(10))
    version = (sys.version_info[0], sys.version_info[1])
    if version < (3, 3):
        mod, e = loadmodule2(modpath, modname)
    else:
        mod, e = loadmodule3(modpath, modname)
    return mod, e
def loadmodule2(modpath, modname):
    import imp
    try:
        mod = imp.load_source(modname, modpath)
    except Exception, e:
        return (None, e)
    return (mod, None)
def loadmodule3(modpath, modname):
    import importlib.machinery
    try:
        loader = importlib.machinery.SourceFileLoader(modname, modpath)
        mod = loader.load_module()
    except Exception, e:
        return (None, e)
    return (mod, None)
def getstrategy(args):
    sttokens = args.strategy.split(':')
    if len(sttokens) == 1:
        modpath = sttokens[0]
        stname = None
    else:
        modpath, stname = sttokens
    if modpath:
        mod, e = loadmodule(modpath)
        if not mod:
            print('')
            print('Failed to load module %s:' % modpath, e)
            sys.exit(1)
    else:
        mod = btstrats
    strats = getmodclasses(mod=mod, clstype=bt.Strategy, clsname=stname)
    if not strats:
        print('No strategy %s / module %s' % (str(stname), modpath))
        sys.exit(1)
    return strats[0]
def getanalyzers(args):
    analyzers = list()
    for anspec in args.analyzers or []:
        tokens = anspec.split(':')
        if len(tokens) == 1:
            modpath = tokens[0]
            name = None
        else:
            modpath, name = tokens
        if modpath:
            mod, e = loadmodule(modpath)
            if not mod:
                print('')
                print('Failed to load module %s:' % modpath, e)
                sys.exit(1)
        else:
            mod = btanalyzers
        loaded = getmodclasses(mod=mod, clstype=bt.Analyzer, clsname=name)
        if not loaded:
            print('No analyzer %s / module %s' % ((str(name), modpath)))
            sys.exit(1)
        analyzers.extend(loaded)
    return analyzers
def getobservers(args):
    observers = list()
    for obspec in args.observers or []:
        tokens = obspec.split(':')
        if len(tokens) == 1:
            modpath = tokens[0]
            name = None
        else:
            modpath, name = tokens
        if modpath:
            mod, e = loadmodule(modpath)
            if not mod:
                print('')
                print('Failed to load module %s:' % modpath, e)
                sys.exit(1)
        else:
            mod = btobs
        loaded = getmodclasses(mod=mod, clstype=bt.Observer, clsname=name)
        if not loaded:
            print('No observer %s / module %s' % ((str(name), modpath)))
            sys.exit(1)
        observers.extend(loaded)
    return observers
def parse_args():
    parser = argparse.ArgumentParser(
        description='Backtrader Run Script')
    group = parser.add_argument_group(title='Data options')
    # Data options
    group.add_argument('--data', '-d', action='append', required=True,
                       help='Data files to be added to the system')
    datakeys = list(DATAFORMATS.keys())
    group.add_argument('--csvformat', '-c', required=False,
                       default='btcsv', choices=datakeys,
                       help='CSV Format')
    group.add_argument('--fromdate', '-f', required=False, default=None,
                       help='Starting date in YYYY-MM-DD[THH:MM:SS] format')
    group.add_argument('--todate', '-t', required=False, default=None,
                       help='Ending date in YYYY-MM-DD[THH:MM:SS] format')
    # Module where to read the strategy from
    group = parser.add_argument_group(title='Strategy options')
    group.add_argument('--strategy', '-st', required=True,
                       help=('Module and strategy to load with format '
                             'module_path:strategy_name.\n'
                             '\n'
                             'module_path:strategy_name will load '
                             'strategy_name from the given module_path\n'
                             '\n'
                             'module_path will load the module and return '
                             'the first available strategy in the module\n'
                             '\n'
                             ':strategy_name will load the given strategy '
                             'from the set of built-in strategies'))
    # Observers
    group = parser.add_argument_group(title='Observers and statistics')
    group.add_argument('--nostdstats', action='store_true',
                       help='Disable the standard statistics observers')
    group.add_argument('--observer', '-ob', dest='observers',
                       action='append', required=False,
                       help=('This option can be specified multiple times\n'
                             '\n'
                             'Module and observer to load with format '
                             'module_path:observer_name.\n'
                             '\n'
                             'module_path:observer_name will load '
                             'observer_name from the given module_path\n'
                             '\n'
                             'module_path will load the module and return '
                             'all available observers in the module\n'
                             '\n'
                             ':observer_name will load the given strategy '
                             'from the set of built-in strategies'))
    # Anaylzers
    group = parser.add_argument_group(title='Analyzers')
    group.add_argument('--analyzer', '-an', dest='analyzers',
                       action='append', required=False,
                       help=('This option can be specified multiple times\n'
                             '\n'
                             'Module and analyzer to load with format '
                             'module_path:analzyer_name.\n'
                             '\n'
                             'module_path:analyzer_name will load '
                             'observer_name from the given module_path\n'
                             '\n'
                             'module_path will load the module and return '
                             'all available analyzers in the module\n'
                             '\n'
                             ':anaylzer_name will load the given strategy '
                             'from the set of built-in strategies'))
    # Broker/Commissions
    group = parser.add_argument_group(title='Cash and Commission Scheme Args')
    group.add_argument('--cash', '-cash', required=False, type=float,
                       help='Cash to set to the broker')
    group.add_argument('--commission', '-comm', required=False, type=float,
                       help='Commission value to set')
    group.add_argument('--margin', '-marg', required=False, type=float,
                       help='Margin type to set')
    group.add_argument('--mult', '-mul', required=False, type=float,
                       help='Multiplier to use')
    # Plot options
    group = parser.add_argument_group(title='Plotting options')
    group.add_argument('--noplot', '-np', action='store_true', required=False,
                       help='Do not plot the read data')
    group.add_argument('--plotstyle', '-ps', required=False, default='bar',
                       choices=['bar', 'line', 'candle'],
                       help='Plot style for the input data')
    group.add_argument('--plotfigs', '-pn', required=False, default=1,
                       type=int, help='Plot using n figures')
    # Extra arguments
    parser.add_argument('args', nargs=argparse.REMAINDER,
                        help='args to pass to the loaded strategy')
    return parser.parse_args()
if __name__ == '__main__':
    runstrat()

观察者和统计

原文:www.backtrader.com/blog/posts/2015-08-12-observers-and-statistics/observers-and-statistics/

运行在 backtrader 内部的策略主要处理数据指标

数据被添加到Cerebro实例中,并最终成为策略的输入的一部分(被解析并作为实例的属性提供),而指标是由策略本身声明和管理的。

到目前为止,backtrader 的所有示例图表都有 3 个似乎被视为理所当然的东西,因为它们没有在任何地方声明:

  • 现金和价值(经纪人的资金情况)
  • 交易(也称为操作)
  • 买入/卖出订单

它们是观察者,存在于子模块backtrader.observers中。它们在那里是因为Cerebro支持一个参数,可以自动将它们添加(或不添加)到策略中:

  • stdstats(默认值:True)

如果默认值被遵守,Cerebro执行以下等效用户代码:

import backtrader as bt
...
cerebro = bt.Cerebro()  # default kwarg: stdstats=True
cerebro.addobserver(backtrader.observers.Broker)
cerebro.addobserver(backtrader.observers.Trades)
cerebro.addobserver(backtrader.observers.BuySell)

让我们看看具有这 3 个默认观察者的通常图表(即使没有发出订单,因此没有交易发生,也没有现金和投资组合价值的变化)

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
import backtrader as bt
import backtrader.feeds as btfeeds
if __name__ == '__main__':
    cerebro = bt.Cerebro(stdstats=False)
    cerebro.addstrategy(bt.Strategy)
    data = bt.feeds.BacktraderCSVData(dataname='../datas/2006-day-001.txt')
    cerebro.adddata(data)
    cerebro.run()
    cerebro.plot()

现在让我们在创建Cerebro实例时将stdstats的值更改为False(也可以在调用run时完成):

cerebro = bt.Cerebro(stdstats=False)
• 1

现在图表不同了。

访问观察者

如上所述,观察者已经存在于默认情况下,并收集可用于统计目的的信息,这就是为什么可以通过策略的一个属性来访问观察者的原因:

  • stats

它只是一个占位符。如果我们回想一下如何添加默认观察者之一,就像上面描述的那样:

...
cerebro.addobserver(backtrader.observers.Broker)
...

显而易见的问题是如何访问Broker观察者。以下是一个示例,展示了如何从策略的next方法中完成这个操作:

class MyStrategy(bt.Strategy):
    def next(self):
        if self.stats.broker.value[0] < 1000.0:
           print('WHITE FLAG ... I LOST TOO MUCH')
        elif self.stats.broker.value[0] > 10000000.0:
           print('TIME FOR THE VIRGIN ISLANDS ....!!!')

Broker观察者就像一个数据、一个指标和策略本身一样,也是一个Lines对象。在这种情况下,Broker有 2 条线:

  • cash
  • value

观察者实现

实现非常类似于指标的实现:

class Broker(Observer):
    alias = ('CashValue',)
    lines = ('cash', 'value')
    plotinfo = dict(plot=True, subplot=True)
    def next(self):
        self.lines.cash[0] = self._owner.broker.getcash()
        self.lines.value[0] = value = self._owner.broker.getvalue()

步骤:

  • Observer派生(而不是从Indicator派生)
  • 根据需要声明线和参数(Broker有 2 条线但没有参数)
  • 将会有一个自动属性_owner,它是持有观察者的策略。

观察者开始行动:

  • 所有指标计算完成后
  • 策略的next方法执行完成后
  • 这意味着:在周期结束时…他们观察发生了什么

Broker情况下,它只是盲目地记录了每个时间点的经纪人现金和投资组合价值。

将观察者添加到策略中

如上所指出,Cerebro 使用stdstats参数来决定是否添加 3 个默认的观察者,减轻了最终用户的工作量。

将其他观察者添加到混合中是可能的,无论是沿着stdstats还是移除那些。

让我们继续使用通常的策略,当close价格高于SimpleMovingAverage时购买,反之亦然时卖出。

有一个“添加”:

  • DrawDown,这是backtrader生态系统中已经存在的观察者
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
import argparse
import datetime
import os.path
import time
import sys
import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind
class MyStrategy(bt.Strategy):
    params = (('smaperiod', 15),)
    def log(self, txt, dt=None):
        ''' Logging function fot this strategy'''
        dt = dt or self.data.datetime[0]
        if isinstance(dt, float):
            dt = bt.num2date(dt)
        print('%s, %s' % (dt.isoformat(), txt))
    def __init__(self):

视觉输出显示了回撤的演变

以及部分文本输出:

...
2006-12-14T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-15T23:59:59+00:00, DrawDown: 0.22
2006-12-15T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-18T23:59:59+00:00, DrawDown: 0.00
2006-12-18T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-19T23:59:59+00:00, DrawDown: 0.00
2006-12-19T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-20T23:59:59+00:00, DrawDown: 0.10
2006-12-20T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-21T23:59:59+00:00, DrawDown: 0.39
2006-12-21T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-22T23:59:59+00:00, DrawDown: 0.21
2006-12-22T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-27T23:59:59+00:00, DrawDown: 0.28
2006-12-27T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-28T23:59:59+00:00, DrawDown: 0.65
2006-12-28T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-29T23:59:59+00:00, DrawDown: 0.06
2006-12-29T23:59:59+00:00, MaxDrawDown: 2.62

注意

如文本输出和代码中所见,DrawDown观察者实际上有 2 行:

  • drawdown
  • maxdrawdown

选择不绘制maxdrawdown线,但仍然使其对用户可用。

实际上,maxdrawdown的最后一个值也可以通过名为maxdd的直接属性(而不是一行)获得

开发观察者

上面展示了Broker观察者的实现。为了生成有意义的观察者,实现可以使用以下信息:

  • self._owner是当前执行的策略
    因此,观察者可以访问策略中的任何内容
  • 策略中可用的默认内部内容可能会有用:
  • broker -> 属性,提供对策略创建订单的经纪人实例的访问
  • 如在Broker中所见,通过调用getcashgetvalue方法收集现金和投资组合价值
  • _orderspending -> 列出由策略创建并经纪人已通知策略的事件的订单。
  • BuySell观察者遍历列表,寻找已执行(完全或部分)的订单,以创建给定时间点(索引 0)的平均执行价格
  • _tradespending -> 交易列表(一组已完成的买入/卖出或卖出/买入对),从买入/卖出订单编译而成

Observer显然可以通过self._owner.stats路径访问其他观察者。

自定义OrderObserver

标准的BuySell观察者只关心已执行的操作。我们可以创建一个观察者,显示订单何时创建以及是否已过期。

为了可见性,显示将不沿价格绘制,而是在单独的轴上。

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
import math
import backtrader as bt
class OrderObserver(bt.observer.Observer):
    lines = ('created', 'expired',)
    plotinfo = dict(plot=True, subplot=True, plotlinelabels=True)
    plotlines = dict(
        created=dict(marker='*', markersize=8.0, color='lime', fillstyle='full'),
        expired=dict(marker='s', markersize=8.0, color='red', fillstyle='full')
    )
    def next(self):
        for order in self._owner._orderspending:
            if order.data is not self.data:
                continue
            if not order.isbuy():
                continue
            # Only interested in "buy" orders, because the sell orders
            # in the strategy are Market orders and will be immediately
            # executed
            if order.status in [bt.Order.Accepted, bt.Order.Submitted]:
                self.lines.created[0] = order.created.price
            elif order.status in [bt.Order.Expired]:
                self.lines.expired[0] = order.created.price

自定义观察者只关心买入订单,因为这是一个只购买以试图获利的策略。卖出订单是市价订单,将立即执行。

Close-SMA CrossOver 策略已更改为:

  • 创建一个限价订单,价格低于信号时的收盘价的 1.0%
  • 订单有效期为 7(日历)天

结果图表。

如新子图中所见,有几个订单已过期(红色方块),我们还可以看到在“创建”和“执行”之间有几天的时间。

注意

从提交 1560fa8802 开始,在 development 分支中,如果在订单创建时价格未设置,则会使用收盘价格作为参考价格。

这不会影响市场订单,但始终保持 order.create.price 可用,并简化了 buy 的使用。

最后,应用新的观察者的策略代码。

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
import datetime
import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind
from orderobserver import OrderObserver
class MyStrategy(bt.Strategy):
    params = (
        ('smaperiod', 15),
        ('limitperc', 1.0),
        ('valid', 7),
    )
    def log(self, txt, dt=None):
        ''' Logging function fot this strategy'''
        dt = dt or self.data.datetime[0]
        if isinstance(dt, float):
            dt = bt.num2date(dt)
        print('%s, %s' % (dt.isoformat(), txt))
    def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            # Buy/Sell order submitted/accepted to/by broker - Nothing to do
            self.log('ORDER ACCEPTED/SUBMITTED', dt=order.created.dt)
            self.order = order
            return
        if order.status in [order.Expired]:
            self.log('BUY EXPIRED')
        elif order.status in [order.Completed]:
            if order.isbuy():
                self.log(
                    'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                    (order.executed.price,
                     order.executed.value,
                     order.executed.comm))
            else:  # Sell
                self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                         (order.executed.price,
                          order.executed.value,
                          order.executed.comm))
        # Sentinel to None: new orders allowed
        self.order = None
    def __init__(self):
        # SimpleMovingAverage on main data
        # Equivalent to -> sma = btind.SMA(self.data, period=self.p.smaperiod)
        sma = btind.SMA(period=self.p.smaperiod)
        # CrossOver (1: up, -1: down) close / sma
        self.buysell = btind.CrossOver(self.data.close, sma, plot=True)
        # Sentinel to None: new ordersa allowed
        self.order = None
    def next(self):
        if self.order:
            # pending order ... do nothing
            return
        # Check if we are in the market
        if self.position:
            if self.buysell < 0:
                self.log('SELL CREATE, %.2f' % self.data.close[0])
                self.sell()
        elif self.buysell > 0:
            plimit = self.data.close[0] * (1.0 - self.p.limitperc / 100.0)
            valid = self.data.datetime.date(0) + \
                datetime.timedelta(days=self.p.valid)
            self.log('BUY CREATE, %.2f' % plimit)
            self.buy(exectype=bt.Order.Limit, price=plimit, valid=valid)
def runstrat():
    cerebro = bt.Cerebro()
    data = bt.feeds.BacktraderCSVData(dataname='../datas/2006-day-001.txt')
    cerebro.adddata(data)
    cerebro.addobserver(OrderObserver)
    cerebro.addstrategy(MyStrategy)
    cerebro.run()
    cerebro.plot()
if __name__ == '__main__':
    runstrat()


BackTrader 中文文档(二十七)(4)https://developer.aliyun.com/article/1505480

相关文章
|
6月前
|
算法 索引 Python
BackTrader 中文文档(二十七)(2)
BackTrader 中文文档(二十七)
61 0
|
6月前
BackTrader 中文文档(二十七)(1)
BackTrader 中文文档(二十七)
44 0
|
6月前
|
存储 数据库连接 数据库
BackTrader 中文文档(二十七)(4)
BackTrader 中文文档(二十七)
45 0
|
6月前
|
Python
BackTrader 中文文档(二十八)(1)
BackTrader 中文文档(二十八)
41 0
|
6月前
BackTrader 中文文档(二十八)(2)
BackTrader 中文文档(二十八)
47 0
|
6月前
|
测试技术 C语言 Python
BackTrader 中文文档(二十八)(4)
BackTrader 中文文档(二十八)
44 0
|
6月前
|
存储 算法 C++
BackTrader 中文文档(二十八)(3)
BackTrader 中文文档(二十八)
59 0
|
6月前
BackTrader 中文文档(二十九)(2)
BackTrader 中文文档(二十九)
44 0
|
6月前
|
算法 索引
BackTrader 中文文档(二十九)(1)
BackTrader 中文文档(二十九)
57 0
|
6月前
BackTrader 中文文档(二十九)(3)
BackTrader 中文文档(二十九)
50 0