首页 > 其他分享 >BackTrader 中文文档(二十三)

BackTrader 中文文档(二十三)

时间:2024-04-15 11:25:07浏览次数:26  
标签:False 二十三 BackTrader -- self args default 文档 data

原文:www.backtrader.com/

基准测试

原文:www.backtrader.com/blog/posts/2016-07-22-benchmarking/benchmarking/

backtrader包括两种不同类型的对象,可以帮助跟踪:

  • 观察者

  • 分析器

问题 #89是关于添加针对资产的基准测试。这是合理的,因为一个人可能实际上有一种策略,即使是正的,也低于简单跟踪资产所能提供的回报。

分析器领域中,已经有一个TimeReturn对象,用于跟踪整个投资组合价值的回报演变(即:包括现金)

这显然也可以是一个观察者,因此在添加一些基准测试的同时,还进行了一些工作,以便能够将观察者分析器组合在一起,这两者都旨在跟踪相同的内容。

注意

观察者分析器之间的主要区别是观察者线性质,记录每��值并使其适合绘图和实时查询。当然,这会消耗内存。

另一方面,分析器通过get_analysis返回一组结果,实现可能直到运行结束才提供任何结果。

分析器 - 基准测试

标准TimeReturn分析器已扩展以支持跟踪数据源。涉及的两个主要参数:

  • timeframe(默认:None)如果为None,则将报告整个回测期间的完整回报

    传递TimeFrame.NoTimeFrame以考虑整个数据集而没有时间限制

  • data(默认:None

    用于跟踪而不是投资组合价值的参考资产。

    注意

    这些数据必须已经通过addataresampledatareplaydata添加到cerebro实例中

(有关更多详细信息和参数,请参阅文档中的参考资料)

因此,可以像这样跟踪投资组合的年度回报:

import backtrader as bt

cerebro = bt.Cerebro()
cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.Years)

...  # add datas, strategies ...

results = cerebro.run()
strat0 = results[0]

# If no name has been specified, the name is the class name lowercased
tret_analyzer = strat0.analyzers.getbyname('timereturn')
print(tret_analyzer.get_analysis())

如果我们想要跟踪数据的回报:

import backtrader as bt

cerebro = bt.Cerebro()

data = bt.feeds.OneOfTheFeeds(dataname='abcde', ...)
cerebro.adddata(data)

cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.Years,
                    data=data)

...  # add strategies ...

results = cerebro.run()
strat0 = results[0]

# If no name has been specified, the name is the class name lowercased
tret_analyzer = strat0.analyzers.getbyname('timereturn')
print(tret_analyzer.get_analysis())

如果两者都要被跟踪,最好给分析器分配名称:

import backtrader as bt

cerebro = bt.Cerebro()

data = bt.feeds.OneOfTheFeeds(dataname='abcde', ...)
cerebro.adddata(data)

cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.Years,
                    data=data, _name='datareturns')

cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.Years)
                    _name='timereturns')

...  # add strategies ...

results = cerebro.run()
strat0 = results[0]

# If no name has been specified, the name is the class name lowercased
tret_analyzer = strat0.analyzers.getbyname('timereturns')
print(tret_analyzer.get_analysis())
tdata_analyzer = strat0.analyzers.getbyname('datareturns')
print(tdata_analyzer.get_analysis())

观察者 - 基准测试

由于背景机制允许在观察者内部使用分析器,因此添加了 2 个新的观察者:

  • TimeReturn

  • 基准

两者都使用bt.analyzers.TimeReturn分析器来收集结果。

而不是像上面那样有代码片段,一个完整的示例带有一些运行来展示它们的功能。

观察 TimeReturn

执行:

$ ./observer-benchmark.py --plot --timereturn --timeframe notimeframe

输出。

图片

注意执行选项:

  • --timereturn:我们告诉示例就是这样做的

  • --timeframe notimeframe:告诉分析器考虑整个数据集,而不考虑时间范围边界。

最后绘制的数值为-0.26

  • 起始现金(从图表中明显)为50,000货币单位,策略最终以36,970货币单位结束,因此价值下降了-26%

观察基准测试

因为基准也会显示时间回报率的结果,让我们运行相同的内容,但激活基准

$ ./observer-benchmark.py --plot --timeframe notimeframe

输出。

image

嘿,嘿,嘿!!!

  • 策略比资产更好:-0.26-0.33

    这不应该是一个值得庆祝的事情,但至少很明显策略甚至没有资产那么糟糕。

转而以年度为基础追踪事物:

$ ./observer-benchmark.py --plot --timeframe years

输出

image

注意!

  • 策略的最后一个值从-0.26变化到-0.27

  • 另一方面,资产显示的最后一个值是-0.35(与上述的-0.33相比)

两者值如此接近的原因是,从 2005 年到 2006 年,无论是策略还是基准资产,几乎都处于 2005 年的起始水平。

转换到较低的时间范围,比如,整个情况都会改变:

$ ./observer-benchmark.py --plot --timeframe weeks

输出

image

现在:

  • 基准 观察者显示出更加紧张的状态。事物上下移动,因为现在每周都在跟踪组合和数据的回报率

  • 因为在年底的最后一周没有交易活动,资产几乎没有变动,所以最后显示的值为 0.00(最后一周之前的最后收盘价为25.54,样本数据收盘价为25.55,差异首先在第 4 位小数点上感受到)

观察基准 - 另一数据

该样本允许针对不同的数据进行基准测试。默认情况下,使用--benchdata1时,基准测试的对象是Oracle。考虑整个数据集,使用--timeframe notimeframe

$ ./observer-benchmark.py --plot --timeframe notimeframe --benchdata1

输出:

image

现在清楚为什么上面没有值得庆祝的原因了:

  • 策略的结果对于notimeframe没有改变,仍然为-26%-0.26

  • 但是当与另一数据进行基准比较时,这个数据在同一时期有+23%0.23)的增长

要么策略需要改变,要么另一种资产最好交易。

结论

现在有两种方法,使用相同的底层代码/计算,来跟踪时间回报率基准

  • 观察者TimeReturnBenchmark

  • 分析器(带有data参数的TimeReturnTimeReturn

当然,基准 不保证盈利,只是比较

使用样本的方法:

$ ./observer-benchmark.py --help
usage: observer-benchmark.py [-h] [--data0 DATA0] [--data1 DATA1]
                             [--benchdata1] [--fromdate FROMDATE]
                             [--todate TODATE] [--printout] [--cash CASH]
                             [--period PERIOD] [--stake STAKE] [--timereturn]
                             [--timeframe {months,days,notimeframe,years,None,weeks}]
                             [--plot [kwargs]]

Benchmark/TimeReturn Observers Sample

optional arguments:
  -h, --help            show this help message and exit
  --data0 DATA0         Data0 to be read in (default:
                        ../../datas/yhoo-1996-2015.txt)
  --data1 DATA1         Data1 to be read in (default:
                        ../../datas/orcl-1995-2014.txt)
  --benchdata1          Benchmark against data1 (default: False)
  --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)
  --printout            Print data lines (default: False)
  --cash CASH           Cash to start with (default: 50000)
  --period PERIOD       Period for the crossover moving average (default: 30)
  --stake STAKE         Stake to apply for the buy operations (default: 1000)
  --timereturn          Use TimeReturn observer instead of Benchmark (default:
                        None)
  --timeframe {months,days,notimeframe,years,None,weeks}
                        TimeFrame to apply to the Observer (default: None)
  --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

class St(bt.Strategy):
    params = (
        ('period', 10),
        ('printout', False),
        ('stake', 1000),
    )

    def __init__(self):
        sma = bt.indicators.SMA(self.data, period=self.p.period)
        self.crossover = bt.indicators.CrossOver(self.data, sma)

    def start(self):
        if self.p.printout:
            txtfields = list()
            txtfields.append('Len')
            txtfields.append('Datetime')
            txtfields.append('Open')
            txtfields.append('High')
            txtfields.append('Low')
            txtfields.append('Close')
            txtfields.append('Volume')
            txtfields.append('OpenInterest')
            print(','.join(txtfields))

    def next(self):
        if self.p.printout:
            # Print only 1st data ... is just a check that things are running
            txtfields = list()
            txtfields.append('%04d' % len(self))
            txtfields.append(self.data.datetime.datetime(0).isoformat())
            txtfields.append('%.2f' % self.data0.open[0])
            txtfields.append('%.2f' % self.data0.high[0])
            txtfields.append('%.2f' % self.data0.low[0])
            txtfields.append('%.2f' % self.data0.close[0])
            txtfields.append('%.2f' % self.data0.volume[0])
            txtfields.append('%.2f' % self.data0.openinterest[0])
            print(','.join(txtfields))

        if self.position:
            if self.crossover < 0.0:
                if self.p.printout:
                    print('CLOSE {} @%{}'.format(size,
                                                 self.data.close[0]))
                self.close()

        else:
            if self.crossover > 0.0:
                self.buy(size=self.p.stake)
                if self.p.printout:
                    print('BUY   {} @%{}'.format(self.p.stake,
                                                self.data.close[0]))

TIMEFRAMES = {
    None: None,
    'days': bt.TimeFrame.Days,
    'weeks': bt.TimeFrame.Weeks,
    'months': bt.TimeFrame.Months,
    'years': bt.TimeFrame.Years,
    'notimeframe': bt.TimeFrame.NoTimeFrame,
}

def runstrat(args=None):
    args = parse_args(args)

    cerebro = bt.Cerebro()
    cerebro.broker.set_cash(args.cash)

    dkwargs = dict()
    if args.fromdate:
        fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
        dkwargs['fromdate'] = fromdate

    if args.todate:
        todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
        dkwargs['todate'] = todate

    data0 = bt.feeds.YahooFinanceCSVData(dataname=args.data0, **dkwargs)
    cerebro.adddata(data0, name='Data0')

    cerebro.addstrategy(St,
                        period=args.period,
                        stake=args.stake,
                        printout=args.printout)

    if args.timereturn:
        cerebro.addobserver(bt.observers.TimeReturn,
                            timeframe=TIMEFRAMES[args.timeframe])
    else:
        benchdata = data0
        if args.benchdata1:
            data1 = bt.feeds.YahooFinanceCSVData(dataname=args.data1, **dkwargs)
            cerebro.adddata(data1, name='Data1')
            benchdata = data1

        cerebro.addobserver(bt.observers.Benchmark,
                            data=benchdata,
                            timeframe=TIMEFRAMES[args.timeframe])

    cerebro.run()

    if args.plot:
        pkwargs = dict()
        if args.plot is not True:  # evals to True but is not True
            pkwargs = eval('dict(' + args.plot + ')')  # args were passed

        cerebro.plot(**pkwargs)

def parse_args(pargs=None):

    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description='Benchmark/TimeReturn Observers Sample')

    parser.add_argument('--data0', required=False,
                        default='../../datas/yhoo-1996-2015.txt',
                        help='Data0 to be read in')

    parser.add_argument('--data1', required=False,
                        default='../../datas/orcl-1995-2014.txt',
                        help='Data1 to be read in')

    parser.add_argument('--benchdata1', required=False, action='store_true',
                        help=('Benchmark against data1'))

    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='2006-12-31',
                        help='Ending date in YYYY-MM-DD format')

    parser.add_argument('--printout', required=False, action='store_true',
                        help=('Print data lines'))

    parser.add_argument('--cash', required=False, action='store',
                        type=float, default=50000,
                        help=('Cash to start with'))

    parser.add_argument('--period', required=False, action='store',
                        type=int, default=30,
                        help=('Period for the crossover moving average'))

    parser.add_argument('--stake', required=False, action='store',
                        type=int, default=1000,
                        help=('Stake to apply for the buy operations'))

    parser.add_argument('--timereturn', required=False, action='store_true',
                        default=None,
                        help=('Use TimeReturn observer instead of Benchmark'))

    parser.add_argument('--timeframe', required=False, action='store',
                        default=None, choices=TIMEFRAMES.keys(),
                        help=('TimeFrame to apply to the Observer'))

    # 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:
        return parser.parse_args(pargs)

    return parser.parse_args()

if __name__ == '__main__':
    runstrat()

Pyfolio 集成

原文:www.backtrader.com/blog/posts/2016-07-17-pyfolio-integration/pyfolio-integration/

注意

2017 年 2 月

pyfolio的 API 已更改,create_full_tear_sheet不再具有gross_lev作为命名参数。

因此,下面的示例不起作用

初次查看教程时,由于 zipline 和 pyfolio 之间的紧密集成,被认为很难,但是 pyfolio 提供的用于其他用途的样本测试数据实际上非常有用,可以解码幕后运行的内容,从而实现集成的奇迹。

一个名为pyfolio投资组合工具的集成是在Ticket #108中提出的。

大部分组件已经就位在backtrader中:

  • 分析器基础设施

  • 子分析器

  • 一个 TimeReturn 分析器

只需要一个主要的PyFolio分析器和 3 个简单的分析器。再加上一个依赖于pyfolio已经需要的pandas的方法。

最具挑战性的部分是...“确保所有依赖项正确”。

  • 更新pandas

  • 更新numpy

  • 更新scikit-lean

  • 更新seaborn

在类 Unix 环境中使用C编译器,一切都是关于时间的。在 Windows 下,即使安装了特定的Microsoft编译器(在本例中是Python 2.7的链),事情也会失败。但是一个收集最新软件包的著名网站对Windows有所帮助。如果您需要,请访问:

如果没有经过测试,集成将不完整,这就是为什么通常的示例总是存在的原因。

没有 PyFolio

该示例使用random.randint来决定何时买入/卖出,因此这只是一个检查事情是否正常运行的方法:

$ ./pyfoliotest.py --printout --no-pyfolio --plot

输出:

Len,Datetime,Open,High,Low,Close,Volume,OpenInterest
0001,2005-01-03T23:59:59,38.36,38.90,37.65,38.18,25482800.00,0.00
BUY  1000 @%23.58
0002,2005-01-04T23:59:59,38.45,38.54,36.46,36.58,26625300.00,0.00
BUY  1000 @%36.58
SELL 500 @%22.47
0003,2005-01-05T23:59:59,36.69,36.98,36.06,36.13,18469100.00,0.00
...
SELL 500 @%37.51
0502,2006-12-28T23:59:59,25.62,25.72,25.30,25.36,11908400.00,0.00
0503,2006-12-29T23:59:59,25.42,25.82,25.33,25.54,16297800.00,0.00
SELL 250 @%17.14
SELL 250 @%37.01

image

有 3 个数据和几个买入卖出操作是随机选择并分散在测试运行的默认 2 年寿命中

一个 PyFolio 运行

当在Jupyter Notebook中运行时,pyfolio的功能运行良好,包括内联绘图。这是笔记本

注意

runstrat在此处获取[]作为参数以使用默认参数运行,并跳过由笔记本本身传递的参数

%matplotlib inline
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import argparse
import datetime
import random

import backtrader as bt

class St(bt.Strategy):
    params = (
        ('printout', False),
        ('stake', 1000),
    )

    def __init__(self):
        pass

    def start(self):
        if self.p.printout:
            txtfields = list()
            txtfields.append('Len')
            txtfields.append('Datetime')
            txtfields.append('Open')
            txtfields.append('High')
            txtfields.append('Low')
            txtfields.append('Close')
            txtfields.append('Volume')
            txtfields.append('OpenInterest')
            print(','.join(txtfields))

    def next(self):
        if self.p.printout:
            # Print only 1st data ... is just a check that things are running
            txtfields = list()
            txtfields.append('%04d' % len(self))
            txtfields.append(self.data.datetime.datetime(0).isoformat())
            txtfields.append('%.2f' % self.data0.open[0])
            txtfields.append('%.2f' % self.data0.high[0])
            txtfields.append('%.2f' % self.data0.low[0])
            txtfields.append('%.2f' % self.data0.close[0])
            txtfields.append('%.2f' % self.data0.volume[0])
            txtfields.append('%.2f' % self.data0.openinterest[0])
            print(','.join(txtfields))

        # Data 0
        for data in self.datas:
            toss = random.randint(1, 10)
            curpos = self.getposition(data)
            if curpos.size:
                if toss > 5:
                    size = curpos.size // 2
                    self.sell(data=data, size=size)
                    if self.p.printout:
                        print('SELL {} @%{}'.format(size, data.close[0]))

            elif toss < 5:
                self.buy(data=data, size=self.p.stake)
                if self.p.printout:
                    print('BUY {} @%{}'.format(self.p.stake, data.close[0]))

def runstrat(args=None):
    args = parse_args(args)

    cerebro = bt.Cerebro()
    cerebro.broker.set_cash(args.cash)

    dkwargs = dict()
    if args.fromdate:
        fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
        dkwargs['fromdate'] = fromdate

    if args.todate:
        todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
        dkwargs['todate'] = todate

    data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **dkwargs)
    cerebro.adddata(data0, name='Data0')

    data1 = bt.feeds.BacktraderCSVData(dataname=args.data1, **dkwargs)
    cerebro.adddata(data1, name='Data1')

    data2 = bt.feeds.BacktraderCSVData(dataname=args.data2, **dkwargs)
    cerebro.adddata(data2, name='Data2')

    cerebro.addstrategy(St, printout=args.printout)
    if not args.no_pyfolio:
        cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio')

    results = cerebro.run()
    if not args.no_pyfolio:
        strat = results[0]
        pyfoliozer = strat.analyzers.getbyname('pyfolio')

        returns, positions, transactions, gross_lev = pyfoliozer.get_pf_items()
        if args.printout:
            print('-- RETURNS')
            print(returns)
            print('-- POSITIONS')
            print(positions)
            print('-- TRANSACTIONS')
            print(transactions)
            print('-- GROSS LEVERAGE')
            print(gross_lev)

        import pyfolio as pf
        pf.create_full_tear_sheet(
            returns,
            positions=positions,
            transactions=transactions,
            gross_lev=gross_lev,
            live_start_date='2005-05-01',
            round_trips=True)

    if args.plot:
        cerebro.plot(style=args.plot_style)

def parse_args(args=None):

    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description='Sample for pivot point and cross plotting')

    parser.add_argument('--data0', required=False,
                        default='../../datas/yhoo-1996-2015.txt',
                        help='Data to be read in')

    parser.add_argument('--data1', required=False,
                        default='../../datas/orcl-1995-2014.txt',
                        help='Data to be read in')

    parser.add_argument('--data2', required=False,
                        default='../../datas/nvda-1999-2014.txt',
                        help='Data to be read in')

    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='2006-12-31',
                        help='Ending date in YYYY-MM-DD format')

    parser.add_argument('--printout', required=False, action='store_true',
                        help=('Print data lines'))

    parser.add_argument('--cash', required=False, action='store',
                        type=float, default=50000,
                        help=('Cash to start with'))

    parser.add_argument('--plot', required=False, action='store_true',
                        help=('Plot the result'))

    parser.add_argument('--plot-style', required=False, action='store',
                        default='bar', choices=['bar', 'candle', 'line'],
                        help=('Plot style'))

    parser.add_argument('--no-pyfolio', required=False, action='store_true',
                        help=('Do not do pyfolio things'))

    import sys
    aargs = args if args is not None else sys.argv[1:]
    return parser.parse_args(aargs)
runstrat([])
Entire data start date: 2005-01-03
Entire data end date: 2006-12-29

Out-of-Sample Months: 20
Backtest Months: 3
[-0.012 -0.025]

image

image

image

D:drobinWinPython-64bit-2.7.10.3python-2.7.10.amd64libsite-packagespyfolioplotting.py:1210: FutureWarning: .resample() is now a deferred operation
use .resample(...).mean() instead of .resample(...)
  **kwargs)

image

<matplotlib.figure.Figure at 0x23982b70>

image

使用示例:

$ ./pyfoliotest.py --help
usage: pyfoliotest.py [-h] [--data0 DATA0] [--data1 DATA1] [--data2 DATA2]
                      [--fromdate FROMDATE] [--todate TODATE] [--printout]
                      [--cash CASH] [--plot] [--plot-style {bar,candle,line}]
                      [--no-pyfolio]

Sample for pivot point and cross plotting

optional arguments:
  -h, --help            show this help message and exit
  --data0 DATA0         Data to be read in (default:
                        ../../datas/yhoo-1996-2015.txt)
  --data1 DATA1         Data to be read in (default:
                        ../../datas/orcl-1995-2014.txt)
  --data2 DATA2         Data to be read in (default:
                        ../../datas/nvda-1999-2014.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)
  --printout            Print data lines (default: False)
  --cash CASH           Cash to start with (default: 50000)
  --plot                Plot the result (default: False)
  --plot-style {bar,candle,line}
                        Plot style (default: bar)
  --no-pyfolio          Do not do pyfolio things (default: False)

体积填充

原文:www.backtrader.com/blog/posts/2016-07-14-volume-filling/volume-filling/

到目前为止,backtrader中的默认体积填充策略一直相当简单和直接:

  • 忽略体积

注意

2016 年 7 月 15 日

更正了实现中的一个错误,并更新了样本以close该位置并在休息后重复。

下面的最后一个测试运行(以及相应的图表)来自更新样本

这基于两个前提:

  • 在足够流动的市场中交易,以完全吸收buy/sell订单

  • 真实的成交量匹配需要真实的世界

    一个快速的示例是Fill or Kill订单。即使是到tick分辨率并且具有足够的fill体积,backtrader经纪人也无法知道市场中有多少额外的参与者来区分订单是否会匹配以坚持Fill部分,或者订单是否应该Kill

但是随着版本1.5.2.93的发布,可以指定经纪人采取Volumefiller以在执行订单时考虑Volume。此外,3 个初始填充器已经进入发布:

  • FixedSize:每天使用固定的匹配大小(例如:1000 个单位),前提是当前柱状图至少有 1000 个单位

  • FixedBarPerc:使用总柱状图体积的百分比尝试匹配订单

  • BarPointPerc:在价格范围高低之间进行柱状图体积的均匀分布,并使用相应于单个价格点的体积的百分比

创建填充器

backtrader生态系统中的filler可以是符合以下签名的任何callable

callable(order, price, ago)

其中:

  • order是即将执行的订单

    此对象提供了对操作目标的data对象的访问权限,创建大小/价格、执行价格/大小/剩余大小以及其他详细信息

  • price,订单将以其执行

  • ago是在其中查找体积和价格元素的orderdata的索引

    在几乎所有情况下,这将是0(当前时间点),但在一种特殊情况下,为了涵盖Close订单,这可能是-1

    例如,要访问柱状图体积,请执行以下操作:

    barvolume = order.data.volume[ago]` 
    

可调用对象可以是一个函数,或者例如支持__call__方法的类的实例,如:

class MyFiller(object):
    def __call__(self, order, price, ago):
        pass

向经纪人添加填充器

最直接的方法是使用set_filler

import backtrader as bt

cerebro = Cerebro()
cerebro.broker.set_filler(bt.broker.filler.FixedSize())

第二选择是完全替换broker,尽管这可能只适用于已经重写部分功能的BrokerBack的子类:

import backtrader as bt

cerebro = Cerebro()
filler = bt.broker.filler.FixedSize()
newbroker = bt.broker.BrokerBack(filler=filler)
cerebro.broker = newbroker

该样本

backtrader源代码包含一个名为volumefilling的样本,它允许测试一些集成的fillers(最初全部)。

该样本在源文件中使用一个默认数据样本命名为:datas/2006-volume-day-001.txt

例如,不使用填充器运行:

$ ./volumefilling.py --stakeperc 20.0

输出:

Len,Datetime,Open,High,Low,Close,Volume,OpenInterest
0001,2006-01-02,3602.00,3624.00,3596.00,3617.00,164794.00,1511674.00
++ STAKE VOLUME: 32958.0
-- NOTIFY ORDER BEGIN
Ref: 1
...
Alive: False
-- NOTIFY ORDER END
-- ORDER REMSIZE: 0.0
++ ORDER COMPLETED at data.len: 2
0002,2006-01-03,3623.00,3665.00,3614.00,3665.00,554426.00,1501792.00
...

因为输入相当冗长,所以大部分内容都被跳过了,但总结是:

  • 看到第1条时,将使用20%–stakeperc 20.0)发出买入订单

  • 如输出所示,并且根据backtrader的默认行为,订单已经在一次交易中完全匹配。没有查看成交量

注意

经纪人在示例中分配了大量的现金,以确保可以应对许多测试情况

另一个运行使用FixedSize成交量填充器和每个条的最大1000单位:

$ ./volumefilling.py --stakeperc 20.0 --filler FixedSize --filler-args size=1000

输出:

Len,Datetime,Open,High,Low,Close,Volume,OpenInterest
0001,2006-01-02,3602.00,3624.00,3596.00,3617.00,164794.00,1511674.00
++ STAKE VOLUME: 32958.0
-- NOTIFY ORDER BEGIN
...
-- NOTIFY ORDER END
-- ORDER REMSIZE: 0.0
++ ORDER COMPLETED at data.len: 34
0034,2006-02-16,3755.00,3774.00,3738.00,3773.00,502043.00,1662302.00
...

现在:

  • 所选的成交量保持不变,为32958

  • 在第34条完成执行,这似乎是合理的,因为从第2条到第34条...已经看到了33个条。每条1000单位匹配的情况下,显然需要33个条来完成执行

这并不是一个伟大的成就,所以让我们来看看FixedBarPerc

$ ./volumefilling.py --stakeperc 20.0 --filler FixedBarPerc --filler-args perc=0.75

输出:

...
-- NOTIFY ORDER END
-- ORDER REMSIZE: 0.0
++ ORDER COMPLETED at data.len: 11
0011,2006-01-16,3635.00,3664.00,3632.00,3660.00,273296.00,1592611.00
...

这次:

  • 跳过开始,仍然是32958单位的订单

  • 执行使用了0.75%的条成交量来匹配请求。

  • 完成需要从第2条到第11条(10条)

这更有趣,但让我们看看现在使用BarPointPerc更动态的成交量分配会发生什么:

$ ./volumefilling.py --stakeperc 20.0 --filler BarPointPerc --filler-args minmov=1.0,perc=10.0

输出:

...
-- NOTIFY ORDER END
-- ORDER REMSIZE: 0.0
++ ORDER COMPLETED at data.len: 22
0022,2006-01-31,3697.00,3718.00,3681.00,3704.00,749740.00,1642003.00
...

发生的事情是:

  • 同样的初始分配(跳过)到32958的订单大小

  • 完全执行需要从222(21 个条)

  • filler使用了1.0minmov(资产的最小价格变动)来在高低范围内均匀分配成交量

  • 10%的成交量分配给特定价格点用于订单匹配

对于任何对如何在每个条上部分匹配订单感兴趣的人来说,检查运行的完整输出可能是值得的时间。

注意

在 1.5.3.93 中修正了错误并更新示例以在中断后close操作

现金增加到一个更多的数量,以避免保证金调用并启用绘图:

$ ./volumefilling.py --filler FixedSize --filler-args size=10000 --stakeperc 10.0 --plot --cash 500e9

而不是查看输出,因为输出非常冗长,让我们看看图表,它已经讲述了整个故事。

图片

使用示例:

usage: volumefilling.py [-h] [--data DATA] [--cash CASH]
                        [--filler {FixedSize,FixedBarPerc,BarPointPerc}]
                        [--filler-args FILLER_ARGS] [--stakeperc STAKEPERC]
                        [--opbreak OPBREAK] [--fromdate FROMDATE]
                        [--todate TODATE] [--plot]

Volume Filling Sample

optional arguments:
  -h, --help            show this help message and exit
  --data DATA           Data to be read in (default: ../../datas/2006-volume-
                        day-001.txt)
  --cash CASH           Starting cash (default: 500000000.0)
  --filler {FixedSize,FixedBarPerc,BarPointPerc}
                        Apply a volume filler for the execution (default:
                        None)
  --filler-args FILLER_ARGS
                        kwargs for the filler with format:
                        arg1=val1,arg2=val2... (default: None)
  --stakeperc STAKEPERC
                        Percentage of 1st bar to use for stake (default: 10.0)
  --opbreak OPBREAK     Bars to wait for new op after completing another
                        (default: 10)
  --fromdate FROMDATE, -f FROMDATE
                        Starting date in YYYY-MM-DD format (default: None)
  --todate TODATE, -t TODATE
                        Ending date in YYYY-MM-DD format (default: None)
  --plot                Plot the result (default: False)

代码

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

class St(bt.Strategy):
    params = (
        ('stakeperc', 10.0),
        ('opbreak', 10),
    )

    def notify_order(self, order):
        print('-- NOTIFY ORDER BEGIN')
        print(order)
        print('-- NOTIFY ORDER END')
        print('-- ORDER REMSIZE:', order.executed.remsize)

        if order.status == order.Completed:
            print('++ ORDER COMPLETED at data.len:', len(order.data))
            self.doop = -self.p.opbreak

    def __init__(self):
        pass

    def start(self):
        self.callcounter = 0
        txtfields = list()
        txtfields.append('Len')
        txtfields.append('Datetime')
        txtfields.append('Open')
        txtfields.append('High')
        txtfields.append('Low')
        txtfields.append('Close')
        txtfields.append('Volume')
        txtfields.append('OpenInterest')
        print(','.join(txtfields))

        self.doop = 0

    def next(self):
        txtfields = list()
        txtfields.append('%04d' % len(self))
        txtfields.append(self.data0.datetime.date(0).isoformat())
        txtfields.append('%.2f' % self.data0.open[0])
        txtfields.append('%.2f' % self.data0.high[0])
        txtfields.append('%.2f' % self.data0.low[0])
        txtfields.append('%.2f' % self.data0.close[0])
        txtfields.append('%.2f' % self.data0.volume[0])
        txtfields.append('%.2f' % self.data0.openinterest[0])
        print(','.join(txtfields))

        # Single order
        if self.doop == 0:
            if not self.position.size:
                stakevol = (self.data0.volume[0] * self.p.stakeperc) // 100
                print('++ STAKE VOLUME:', stakevol)
                self.buy(size=stakevol)

            else:
                self.close()

        self.doop += 1

FILLERS = {
    'FixedSize': bt.broker.filler.FixedSize,
    'FixedBarPerc': bt.broker.filler.FixedBarPerc,
    'BarPointPerc': bt.broker.filler.BarPointPerc,
}

def runstrat():
    args = parse_args()

    datakwargs = dict()
    if args.fromdate:
        fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
        datakwargs['fromdate'] = fromdate

    if args.todate:
        fromdate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
        datakwargs['todate'] = todate

    data = bt.feeds.BacktraderCSVData(dataname=args.data, **datakwargs)

    cerebro = bt.Cerebro()
    cerebro.adddata(data)

    cerebro.broker.set_cash(args.cash)
    if args.filler is not None:
        fillerkwargs = dict()
        if args.filler_args is not None:
            fillerkwargs = eval('dict(' + args.filler_args + ')')

        filler = FILLERSargs.filler
        cerebro.broker.set_filler(filler)

    cerebro.addstrategy(St, stakeperc=args.stakeperc, opbreak=args.opbreak)

    cerebro.run()
    if args.plot:
        cerebro.plot(style='bar')

def parse_args():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description='Volume Filling Sample')

    parser.add_argument('--data', required=False,
                        default='../../datas/2006-volume-day-001.txt',
                        help='Data to be read in')

    parser.add_argument('--cash', required=False, action='store',
                        default=500e6, type=float,
                        help=('Starting cash'))

    parser.add_argument('--filler', required=False, action='store',
                        default=None, choices=FILLERS.keys(),
                        help=('Apply a volume filler for the execution'))

    parser.add_argument('--filler-args', required=False, action='store',
                        default=None,
                        help=('kwargs for the filler with format:\n'
                              '\n'
                              'arg1=val1,arg2=val2...'))

    parser.add_argument('--stakeperc', required=False, action='store',
                        type=float, default=10.0,
                        help=('Percentage of 1st bar to use for stake'))

    parser.add_argument('--opbreak', required=False, action='store',
                        type=int, default=10,
                        help=('Bars to wait for new op after completing '
                              'another'))

    parser.add_argument('--fromdate', '-f', required=False, default=None,
                        help='Starting date in YYYY-MM-DD format')

    parser.add_argument('--todate', '-t', required=False, default=None,
                        help='Ending date in YYYY-MM-DD format')

    parser.add_argument('--plot', required=False, action='store_true',
                        help=('Plot the result'))

    return parser.parse_args()

if __name__ == '__main__':
    runstrat()

以步骤方式交易一天

原文:www.backtrader.com/blog/posts/2016-07-13-day-in-steps/day-in-steps/

看起来世界上的某个地方存在一种可以总结如下的兴趣:

  • 使用每日柱状图但使用开盘价引入订单

这来自于票证交流中的对话#105 Order execution logic with current day data#101 Dynamic stake calculation

backtrader在处理每日柱状图时尽可能保持真实,并且在使用每日柱状图时适用以下前提:

  • 当评估每日柱状图时,柱状图已经结束

这是有道理的,因为所有价格(open/high/low/close)组件都是已知的。当已知close价格时允许在open价格上采取行动似乎是不合逻辑的。

这个问题的明显解决方案是使用日内数据,在已知开盘价时进入。但是似乎日内数据并不那么普遍。

这就是在数据源中添加过滤器可以帮助的地方。一个过滤器:

  • 将每日数据转换为类似日内数据的数据

碧海蓝天!!!好奇的读者会立即指出,例如从MinutesDays上采样是合乎逻辑且有效的,但是DaysMinutes的下采样是不可能的。

而且百分百正确。下面呈现的过滤器不会尝试这样做,但是一个更加谦卑和简单的目标:

  • 将每日柱状图分解为 2 部分

    1. 一个只有开盘价而没有成交量的柱状图

    2. 一个是正常的每日柱状图的副本

这仍然可以被视为一种合乎逻辑的方法:

  • 看到开盘价时,交易员可以采取行动

  • 订单在一天的其余时间匹配(实际上可能匹配也可能不匹配,取决于执行类型和价格限制)

下面呈现了完整的代码。让我们看一个使用255每日柱状图的众所周知的数据的示例运行:

$ ./daysteps.py --data ../../datas/2006-day-001.txt

输出:

Calls,Len Strat,Len Data,Datetime,Open,High,Low,Close,Volume,OpenInterest
0001,0001,0001,2006-01-02T23:59:59,3578.73,3578.73,3578.73,3578.73,0.00,0.00
- I could issue a buy order during the Opening
0002,0001,0001,2006-01-02T23:59:59,3578.73,3605.95,3578.73,3604.33,0.00,0.00
0003,0002,0002,2006-01-03T23:59:59,3604.08,3604.08,3604.08,3604.08,0.00,0.00
- I could issue a buy order during the Opening
0004,0002,0002,2006-01-03T23:59:59,3604.08,3638.42,3601.84,3614.34,0.00,0.00
0005,0003,0003,2006-01-04T23:59:59,3615.23,3615.23,3615.23,3615.23,0.00,0.00
- I could issue a buy order during the Opening
0006,0003,0003,2006-01-04T23:59:59,3615.23,3652.46,3615.23,3652.46,0.00,0.00
...
...
0505,0253,0253,2006-12-27T23:59:59,4079.70,4079.70,4079.70,4079.70,0.00,0.00
- I could issue a buy order during the Opening
0506,0253,0253,2006-12-27T23:59:59,4079.70,4134.86,4079.70,4134.86,0.00,0.00
0507,0254,0254,2006-12-28T23:59:59,4137.44,4137.44,4137.44,4137.44,0.00,0.00
- I could issue a buy order during the Opening
0508,0254,0254,2006-12-28T23:59:59,4137.44,4142.06,4125.14,4130.66,0.00,0.00
0509,0255,0255,2006-12-29T23:59:59,4130.12,4130.12,4130.12,4130.12,0.00,0.00
- I could issue a buy order during the Opening
0510,0255,0255,2006-12-29T23:59:59,4130.12,4142.01,4119.94,4119.94,0.00,0.00

以下情况发生:

  • next被调用:510 次,即255 x 2

  • 策略数据len总共达到了255,这是预期的:数据只有这么多根柱状图

  • 每当数据len增加时,4 个价格组件具有相同的值,即open价格

    这里打印出一条备注,指示在这个开盘阶段可以采取行动,例如购买。

实际上:

  • 每日数据源正在使用每天 2 步重播,在open和其余价格组件之间提供操作选项

过滤器将在下一个版本中添加到backtrader的默认分发中。

包括过滤器的示例代码。

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import argparse
from datetime import datetime, time

import backtrader as bt

class DayStepsFilter(object):
    def __init__(self, data):
        self.pendingbar = None

    def __call__(self, data):
        # Make a copy of the new bar and remove it from stream
        newbar = [data.lines[i][0] for i in range(data.size())]
        data.backwards()  # remove the copied bar from stream

        openbar = newbar[:]  # Make an open only bar
        o = newbar[data.Open]
        for field_idx in [data.High, data.Low, data.Close]:
            openbar[field_idx] = o

        # Nullify Volume/OpenInteres at the open
        openbar[data.Volume] = 0.0
        openbar[data.OpenInterest] = 0.0

        # Overwrite the new data bar with our pending data - except start point
        if self.pendingbar is not None:
            data._updatebar(self.pendingbar)

        self.pendingbar = newbar  # update the pending bar to the new bar
        data._add2stack(openbar)  # Add the openbar to the stack for processing

        return False  # the length of the stream was not changed

    def last(self, data):
        '''Called when the data is no longer producing bars
        Can be called multiple times. It has the chance to (for example)
        produce extra bars'''
        if self.pendingbar is not None:
            data.backwards()  # remove delivered open bar
            data._add2stack(self.pendingbar)  # add remaining
            self.pendingbar = None  # No further action
            return True  # something delivered

        return False  # nothing delivered here

class St(bt.Strategy):
    params = ()

    def __init__(self):
        pass

    def start(self):
        self.callcounter = 0
        txtfields = list()
        txtfields.append('Calls')
        txtfields.append('Len Strat')
        txtfields.append('Len Data')
        txtfields.append('Datetime')
        txtfields.append('Open')
        txtfields.append('High')
        txtfields.append('Low')
        txtfields.append('Close')
        txtfields.append('Volume')
        txtfields.append('OpenInterest')
        print(','.join(txtfields))

        self.lcontrol = 0

    def next(self):
        self.callcounter += 1

        txtfields = list()
        txtfields.append('%04d' % self.callcounter)
        txtfields.append('%04d' % len(self))
        txtfields.append('%04d' % len(self.data0))
        txtfields.append(self.data.datetime.datetime(0).isoformat())
        txtfields.append('%.2f' % self.data0.open[0])
        txtfields.append('%.2f' % self.data0.high[0])
        txtfields.append('%.2f' % self.data0.low[0])
        txtfields.append('%.2f' % self.data0.close[0])
        txtfields.append('%.2f' % self.data0.volume[0])
        txtfields.append('%.2f' % self.data0.openinterest[0])
        print(','.join(txtfields))

        if len(self.data) > self.lcontrol:
            print('- I could issue a buy order during the Opening')

        self.lcontrol = len(self.data)

def runstrat():
    args = parse_args()

    cerebro = bt.Cerebro()
    data = bt.feeds.BacktraderCSVData(dataname=args.data)

    data.addfilter(DayStepsFilter)
    cerebro.adddata(data)

    cerebro.addstrategy(St)

    cerebro.run(stdstats=False, runonce=False, preload=False)
    if args.plot:
        cerebro.plot(style='bar')

def parse_args():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description='Sample for pivot point and cross plotting')

    parser.add_argument('--data', required=False,
                        default='../../datas/2005-2006-day-001.txt',
                        help='Data to be read in')

    parser.add_argument('--plot', required=False, action='store_true',
                        help=('Plot the result'))

    return parser.parse_args()

if __name__ == '__main__':
    runstrat()

Visual Chart 实时数据/交易

原文:www.backtrader.com/blog/posts/2016-07-12-visualchart-feed/visualchart-feed/

从版本 1.5.1.93 开始,backtrader 支持 Visual Chart 实时数据和实时交易。

需要的东西:

  • Visual Chart 6(这个版本运行在 Windows 上)

  • comtypes,具体来说是这个分支:github.com/mementum/comtypes

    使用以下命令安装:pip install https://github.com/mementum/comtypes/archive/master.zip

    Visual Chart 的 API 基于 COM,当前的 comtypes 主分支不支持对 VT_RECORDVT_ARRAYS 进行解包。而 Visual Chart 正是使用了这个。

    Pull Request #104 已经提交但尚未集成。一旦集成,就可以使用主分支了。

  • pytz(可选但强烈建议)

    在许多情况下,数据提供的内部 SymbolInfo.TimeOffset 就足以返回市场时间的数据流(即使默认配置是 Visual Chart 中的 LocalTime

如果您不知道什么是 Visual Chart 和/或其当前关联的经纪商 Esfera Capital,请访问以下网站:

初始声明:

  • 如往常一样,在冒险之前测试,测试测试再次测试 一千次。

    从这个软件中的 bugs,到您自己的软件中的 bug 以及处理意外情况的管理:任何事情都可能出错

关于此的一些说明:

  • 数据提供非常好,并支持内置重采样。好处在于无需进行重采样。

  • 数据流不支持 Seconds 分辨率。这不太好,但可以通过 backtrader 的内置重采样解决。

  • 内置了回填功能

  • 一些国际指数市场(在交易所 096)具有奇怪的时区和市场偏移。

    对此进行了一些工作,例如以预期的 US/Eastern 时区提供 096.DJI

  • 数据提供了 continuous futures,非常方便拥有大量历史数据。

    因此,可以向数据传递第二个参数,指示实际的交易资产。

  • Good Til Date 订单的日期时间只能指定为 日期时间 部分会被忽略。

  • 没有直接的方法可以找到本地设备到数据服务器的偏移量,需要通过会话开始时的 实时数据点 进行启发式分析来找出这一点。

  • 传递带有 时间 组件的 datetime(而不是默认的 00:00:00)似乎会在 COM API 中创建一个 时间过滤器。例如,如果您想要 Minute 数据,从 3 天前开始到 14:30,您可以这样做:

    dt = datetime.now() - timedelta(days=3)
    dt.replace(hour=14, minute=30)
    
    vcstore.getdata(dataname='001ES', fromdate=dt)` 
    

    数据会一直跳过直到 14:30 不仅是 3 天前,而是以后每一天

    因此,请只传递完整日期,即默认的时间部分不受影响。

  • 经纪人 支持 Positions 的概念,但仅在它们是 open 时。 关于 Position 的最后事件(其 size 为 0)不会发送。

    因此,Position 记账完全由 backtrader 完成。

  • 经纪人 不报告佣金。

    解决方法是在实例化经纪人时提供自己的 CommissionInfo 派生类。 请参阅 backtader 文档以创建自己的类。 这相当容易。

  • CancelledExpired 订单。 此区别不存在,需要启发式方法来尝试清除这种区别。

    因此,只有Cancelled将被报告。

一些额外的说明:

  • 实时 ticks 大部分情况下不被使用。 它们为 backtrader 目的产生大量不需要的信息。 在被 backtrader 完全断开连接之前,它们有两个主要目的。

    1. 查找符号是否存在。

    2. 计算到数据服务器的偏移量。

    当然,价格信息是实时收集的,但是来自 DataSource 对象,它们同时提供历史数据。

尽可能多地记录并在通常的文档链接处提供。

从样本 vctest.pyeVisual ChartDemo Broker 进行了一些运行。

首先:015ESEuroStoxx50 连续)重新采样为 1 分钟,并具有断开连接和重新连接:

$ ./vctest.py --data0 015ES --timeframe Minutes --compression 1 --fromdate 2016-07-12

输出:

--------------------------------------------------
Strategy Created
--------------------------------------------------
Datetime, Open, High, Low, Close, Volume, OpenInterest, SMA
***** DATA NOTIF: CONNECTED
***** DATA NOTIF: DELAYED
0001, 2016-07-12T08:01:00.000000, 2871.0, 2872.0, 2869.0, 2872.0, 1915.0, 0.0, nan
0002, 2016-07-12T08:02:00.000000, 2872.0, 2872.0, 2870.0, 2871.0, 479.0, 0.0, nan
0003, 2016-07-12T08:03:00.000000, 2871.0, 2871.0, 2869.0, 2870.0, 518.0, 0.0, nan
0004, 2016-07-12T08:04:00.000000, 2870.0, 2871.0, 2870.0, 2871.0, 248.0, 0.0, nan
0005, 2016-07-12T08:05:00.000000, 2870.0, 2871.0, 2870.0, 2871.0, 234.0, 0.0, 2871.0
...
...
0639, 2016-07-12T18:39:00.000000, 2932.0, 2933.0, 2932.0, 2932.0, 1108.0, 0.0, 2932.8
0640, 2016-07-12T18:40:00.000000, 2931.0, 2932.0, 2931.0, 2931.0, 65.0, 0.0, 2932.6
***** DATA NOTIF: LIVE
0641, 2016-07-12T18:41:00.000000, 2932.0, 2932.0, 2930.0, 2930.0, 2093.0, 0.0, 2931.8
***** STORE NOTIF: (u'VisualChart is Disconnected', -65520)
***** DATA NOTIF: CONNBROKEN
***** STORE NOTIF: (u'VisualChart is Connected', -65521)
***** DATA NOTIF: CONNECTED
***** DATA NOTIF: DELAYED
0642, 2016-07-12T18:42:00.000000, 2931.0, 2931.0, 2931.0, 2931.0, 137.0, 0.0, 2931.2
0643, 2016-07-12T18:43:00.000000, 2931.0, 2931.0, 2931.0, 2931.0, 432.0, 0.0, 2931.0
...
0658, 2016-07-12T18:58:00.000000, 2929.0, 2929.0, 2929.0, 2929.0, 4.0, 0.0, 2930.0
0659, 2016-07-12T18:59:00.000000, 2929.0, 2930.0, 2929.0, 2930.0, 353.0, 0.0, 2930.0
***** DATA NOTIF: LIVE
0660, 2016-07-12T19:00:00.000000, 2930.0, 2930.0, 2930.0, 2930.0, 376.0, 0.0, 2930.0
0661, 2016-07-12T19:01:00.000000, 2929.0, 2930.0, 2929.0, 2930.0, 35.0, 0.0, 2929.8

注意

执行环境安装了 pytz

注意

注意没有 --resample:对于 Minutes,重新采样是内置于 Visual Chart 中的。

最后一些交易,购买 015ES2 个合约,单个 Market 订单,并将它们卖出为 1 个合约的 2 个订单。

执行:

$ ./vctest.py --data0 015ES --timeframe Minutes --compression 1 --fromdate 2016-07-12 2>&1 --broker --account accname --trade --stake 2

输出相当冗长,显示了订单执行的所有部分。 简要总结一下:

--------------------------------------------------
Strategy Created
--------------------------------------------------
Datetime, Open, High, Low, Close, Volume, OpenInterest, SMA
***** DATA NOTIF: CONNECTED
***** DATA NOTIF: DELAYED
0001, 2016-07-12T08:01:00.000000, 2871.0, 2872.0, 2869.0, 2872.0, 1915.0, 0.0, nan
...
0709, 2016-07-12T19:50:00.000000, 2929.0, 2930.0, 2929.0, 2930.0, 11.0, 0.0, 2930.4
***** DATA NOTIF: LIVE
0710, 2016-07-12T19:51:00.000000, 2930.0, 2930.0, 2929.0, 2929.0, 134.0, 0.0, 2930.0
-------------------------------------------------- ORDER BEGIN 2016-07-12 19:52:01.629000
Ref: 1
OrdType: 0
OrdType: Buy
Status: 1
Status: Submitted
Size: 2
Price: None
Price Limit: None
ExecType: 0
ExecType: Market
CommInfo: <backtrader.brokers.vcbroker.VCCommInfo object at 0x000000001100CE10>
End of Session: 736157.916655
Info: AutoOrderedDict()
Broker: <backtrader.brokers.vcbroker.VCBroker object at 0x000000000475D400>
Alive: True
-------------------------------------------------- ORDER END
-------------------------------------------------- ORDER BEGIN 2016-07-12 19:52:01.629000
Ref: 1
OrdType: 0
OrdType: Buy
Status: 2
Status: Accepted
Size: 2
Price: None
Price Limit: None
ExecType: 0
ExecType: Market
CommInfo: <backtrader.brokers.vcbroker.VCCommInfo object at 0x000000001100CE10>
End of Session: 736157.916655
Info: AutoOrderedDict()
Broker: None
Alive: True
-------------------------------------------------- ORDER END
-------------------------------------------------- ORDER BEGIN 2016-07-12 19:52:01.629000
Ref: 1
OrdType: 0
OrdType: Buy
Status: 4
Status: Completed
Size: 2
Price: None
Price Limit: None
ExecType: 0
ExecType: Market
CommInfo: <backtrader.brokers.vcbroker.VCCommInfo object at 0x000000001100CE10>
End of Session: 736157.916655
Info: AutoOrderedDict()
Broker: None
Alive: False
-------------------------------------------------- ORDER END
-------------------------------------------------- TRADE BEGIN 2016-07-12 19:52:01.629000
ref:1
data:<backtrader.feeds.vcdata.VCData object at 0x000000000475D9E8>
tradeid:0
size:2.0
price:2930.0
value:5860.0
commission:0.0
pnl:0.0
pnlcomm:0.0
justopened:True
isopen:True
isclosed:0
baropen:710
dtopen:736157.74375
barclose:0
dtclose:0.0
barlen:0
historyon:False
history:[]
status:1
-------------------------------------------------- TRADE END
...

以下发生了:

  • 数据正常接收。

  • 发出了一个执行类型为 MarketBUY,数量为 2

    • 收到 SubmittedAccepted 通知(仅显示了 Submitted)。

    • 一连串的 Partial 执行(仅显示了 1 个)直到收到 Completed

    实际执行没有显示,但在 order.executed 下收到的 order 实例中可用。

  • 虽然没有显示,但发出了 2 x Market SELL 订单来撤销操作。

    屏幕截图显示了在一个晚上使用 015ESEuroStoxx 50)和 034EURUSEUR.USD 外汇对)进行两次不同运行后,在 Visual Chart 中的日志。

image

示例可以做得更多,旨在彻底测试设施,如果可能的话,揭示任何粗糙的边缘。

使用方式:

$ ./vctest.py --help
usage: vctest.py [-h] [--exactbars EXACTBARS] [--plot] [--stopafter STOPAFTER]
                 [--nostore] [--qcheck QCHECK] [--no-timeoffset] --data0 DATA0
                 [--tradename TRADENAME] [--data1 DATA1] [--timezone TIMEZONE]
                 [--no-backfill_start] [--latethrough] [--historical]
                 [--fromdate FROMDATE] [--todate TODATE]
                 [--smaperiod SMAPERIOD] [--replay | --resample]
                 [--timeframe {Ticks,MicroSeconds,Seconds,Minutes,Days,Weeks,Months,Years}]
                 [--compression COMPRESSION] [--no-bar2edge] [--no-adjbartime]
                 [--no-rightedge] [--broker] [--account ACCOUNT] [--trade]
                 [--donotsell]
                 [--exectype {Market,Close,Limit,Stop,StopLimit}]
                 [--price PRICE] [--pstoplimit PSTOPLIMIT] [--stake STAKE]
                 [--valid VALID] [--cancel CANCEL]

Test Visual Chart 6 integration

optional arguments:
  -h, --help            show this help message and exit
  --exactbars EXACTBARS
                        exactbars level, use 0/-1/-2 to enable plotting
                        (default: 1)
  --plot                Plot if possible (default: False)
  --stopafter STOPAFTER
                        Stop after x lines of LIVE data (default: 0)
  --nostore             Do not Use the store pattern (default: False)
  --qcheck QCHECK       Timeout for periodic notification/resampling/replaying
                        check (default: 0.5)
  --no-timeoffset       Do not Use TWS/System time offset for non timestamped
                        prices and to align resampling (default: False)
  --data0 DATA0         data 0 into the system (default: None)
  --tradename TRADENAME
                        Actual Trading Name of the asset (default: None)
  --data1 DATA1         data 1 into the system (default: None)
  --timezone TIMEZONE   timezone to get time output into (pytz names)
                        (default: None)
  --historical          do only historical download (default: False)
  --fromdate FROMDATE   Starting date for historical download with format:
                        YYYY-MM-DD[THH:MM:SS] (default: None)
  --todate TODATE       End date for historical download with format: YYYY-MM-
                        DD[THH:MM:SS] (default: None)
  --smaperiod SMAPERIOD
                        Period to apply to the Simple Moving Average (default:
                        5)
  --replay              replay to chosen timeframe (default: False)
  --resample            resample to chosen timeframe (default: False)
  --timeframe {Ticks,MicroSeconds,Seconds,Minutes,Days,Weeks,Months,Years}
                        TimeFrame for Resample/Replay (default: Ticks)
  --compression COMPRESSION
                        Compression for Resample/Replay (default: 1)
  --no-bar2edge         no bar2edge for resample/replay (default: False)
  --no-adjbartime       no adjbartime for resample/replay (default: False)
  --no-rightedge        no rightedge for resample/replay (default: False)
  --broker              Use VisualChart as broker (default: False)
  --account ACCOUNT     Choose broker account (else first) (default: None)
  --trade               Do Sample Buy/Sell operations (default: False)
  --donotsell           Do not sell after a buy (default: False)
  --exectype {Market,Close,Limit,Stop,StopLimit}
                        Execution to Use when opening position (default:
                        Market)
  --price PRICE         Price in Limit orders or Stop Trigger Price (default:
                        None)
  --pstoplimit PSTOPLIMIT
                        Price for the limit in StopLimit (default: None)
  --stake STAKE         Stake to use in buy operations (default: 10)
  --valid VALID         Seconds or YYYY-MM-DD (default: None)
  --cancel CANCEL       Cancel a buy order after n bars in operation, to be
                        combined with orders like Limit (default: 0)

代码:

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import argparse
import datetime

# The above could be sent to an independent module
import backtrader as bt
from backtrader.utils import flushfile  # win32 quick stdout flushing
from backtrader.utils.py3 import string_types

class TestStrategy(bt.Strategy):
    params = dict(
        smaperiod=5,
        trade=False,
        stake=10,
        exectype=bt.Order.Market,
        stopafter=0,
        valid=None,
        cancel=0,
        donotsell=False,
        price=None,
        pstoplimit=None,
    )

    def __init__(self):
        # To control operation entries
        self.orderid = list()
        self.order = None

        self.counttostop = 0
        self.datastatus = 0

        # Create SMA on 2nd data
        self.sma = bt.indicators.MovAv.SMA(self.data, period=self.p.smaperiod)

        print('--------------------------------------------------')
        print('Strategy Created')
        print('--------------------------------------------------')

    def notify_data(self, data, status, *args, **kwargs):
        print('*' * 5, 'DATA NOTIF:', data._getstatusname(status), *args)
        if status == data.LIVE:
            self.counttostop = self.p.stopafter
            self.datastatus = 1

    def notify_store(self, msg, *args, **kwargs):
        print('*' * 5, 'STORE NOTIF:', msg)

    def notify_order(self, order):
        if order.status in [order.Completed, order.Cancelled, order.Rejected]:
            self.order = None

        print('-' * 50, 'ORDER BEGIN', datetime.datetime.now())
        print(order)
        print('-' * 50, 'ORDER END')

    def notify_trade(self, trade):
        print('-' * 50, 'TRADE BEGIN', datetime.datetime.now())
        print(trade)
        print('-' * 50, 'TRADE END')

    def prenext(self):
        self.next(frompre=True)

    def next(self, frompre=False):
        txt = list()
        txt.append('%04d' % len(self))
        dtfmt = '%Y-%m-%dT%H:%M:%S.%f'
        txt.append('%s' % self.data.datetime.datetime(0).strftime(dtfmt))
        txt.append('{}'.format(self.data.open[0]))
        txt.append('{}'.format(self.data.high[0]))
        txt.append('{}'.format(self.data.low[0]))
        txt.append('{}'.format(self.data.close[0]))
        txt.append('{}'.format(self.data.volume[0]))
        txt.append('{}'.format(self.data.openinterest[0]))
        txt.append('{}'.format(self.sma[0]))
        print(', '.join(txt))

        if len(self.datas) > 1:
            txt = list()
            txt.append('%04d' % len(self))
            dtfmt = '%Y-%m-%dT%H:%M:%S.%f'
            txt.append('%s' % self.data1.datetime.datetime(0).strftime(dtfmt))
            txt.append('{}'.format(self.data1.open[0]))
            txt.append('{}'.format(self.data1.high[0]))
            txt.append('{}'.format(self.data1.low[0]))
            txt.append('{}'.format(self.data1.close[0]))
            txt.append('{}'.format(self.data1.volume[0]))
            txt.append('{}'.format(self.data1.openinterest[0]))
            txt.append('{}'.format(float('NaN')))
            print(', '.join(txt))

        if self.counttostop:  # stop after x live lines
            self.counttostop -= 1
            if not self.counttostop:
                self.env.runstop()
                return

        if not self.p.trade:
            return

        # if True and len(self.orderid) < 1:
        if self.datastatus and not self.position and len(self.orderid) < 1:
            self.order = self.buy(size=self.p.stake,
                                  exectype=self.p.exectype,
                                  price=self.p.price,
                                  plimit=self.p.pstoplimit,
                                  valid=self.p.valid)

            self.orderid.append(self.order)
        elif self.position.size > 0 and not self.p.donotsell:
            if self.order is None:
                size = self.p.stake // 2
                if not size:
                    size = self.position.size  # use the remaining
                self.order = self.sell(size=size, exectype=bt.Order.Market)

        elif self.order is not None and self.p.cancel:
            if self.datastatus > self.p.cancel:
                self.cancel(self.order)

        if self.datastatus:
            self.datastatus += 1

    def start(self):
        header = ['Datetime', 'Open', 'High', 'Low', 'Close', 'Volume',
                  'OpenInterest', 'SMA']
        print(', '.join(header))

        self.done = False

def runstrategy():
    args = parse_args()

    # Create a cerebro
    cerebro = bt.Cerebro()

    storekwargs = dict()

    if not args.nostore:
        vcstore = bt.stores.VCStore(**storekwargs)

    if args.broker:
        brokerargs = dict(account=args.account, **storekwargs)
        if not args.nostore:
            broker = vcstore.getbroker(**brokerargs)
        else:
            broker = bt.brokers.VCBroker(**brokerargs)

        cerebro.setbroker(broker)

    timeframe = bt.TimeFrame.TFrame(args.timeframe)
    if args.resample or args.replay:
        datatf = bt.TimeFrame.Ticks
        datacomp = 1
    else:
        datatf = timeframe
        datacomp = args.compression

    fromdate = None
    if args.fromdate:
        dtformat = '%Y-%m-%d' + ('T%H:%M:%S' * ('T' in args.fromdate))
        fromdate = datetime.datetime.strptime(args.fromdate, dtformat)

    todate = None
    if args.todate:
        dtformat = '%Y-%m-%d' + ('T%H:%M:%S' * ('T' in args.todate))
        todate = datetime.datetime.strptime(args.todate, dtformat)

    VCDataFactory = vcstore.getdata if not args.nostore else bt.feeds.VCData

    datakwargs = dict(
        timeframe=datatf, compression=datacomp,
        fromdate=fromdate, todate=todate,
        historical=args.historical,
        qcheck=args.qcheck,
        tz=args.timezone
    )

    if args.nostore and not args.broker:   # neither store nor broker
        datakwargs.update(storekwargs)  # pass the store args over the data

    data0 = VCDataFactory(dataname=args.data0, tradename=args.tradename,
                          **datakwargs)

    data1 = None
    if args.data1 is not None:
        data1 = VCDataFactory(dataname=args.data1, **datakwargs)

    rekwargs = dict(
        timeframe=timeframe, compression=args.compression,
        bar2edge=not args.no_bar2edge,
        adjbartime=not args.no_adjbartime,
        rightedge=not args.no_rightedge,
    )

    if args.replay:
        cerebro.replaydata(dataname=data0, **rekwargs)

        if data1 is not None:
            cerebro.replaydata(dataname=data1, **rekwargs)

    elif args.resample:
        cerebro.resampledata(dataname=data0, **rekwargs)

        if data1 is not None:
            cerebro.resampledata(dataname=data1, **rekwargs)

    else:
        cerebro.adddata(data0)
        if data1 is not None:
            cerebro.adddata(data1)

    if args.valid is None:
        valid = None
    else:
        try:
            valid = float(args.valid)
        except:
            dtformat = '%Y-%m-%d' + ('T%H:%M:%S' * ('T' in args.valid))
            valid = datetime.datetime.strptime(args.valid, dtformat)
        else:
            valid = datetime.timedelta(seconds=args.valid)

    # Add the strategy
    cerebro.addstrategy(TestStrategy,
                        smaperiod=args.smaperiod,
                        trade=args.trade,
                        exectype=bt.Order.ExecType(args.exectype),
                        stake=args.stake,
                        stopafter=args.stopafter,
                        valid=valid,
                        cancel=args.cancel,
                        donotsell=args.donotsell,
                        price=args.price,
                        pstoplimit=args.pstoplimit)

    # Live data ... avoid long data accumulation by switching to "exactbars"
    cerebro.run(exactbars=args.exactbars)

    if args.plot and args.exactbars < 1:  # plot if possible
        cerebro.plot()

def parse_args():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description='Test Visual Chart 6 integration')

    parser.add_argument('--exactbars', default=1, type=int,
                        required=False, action='store',
                        help='exactbars level, use 0/-1/-2 to enable plotting')

    parser.add_argument('--plot',
                        required=False, action='store_true',
                        help='Plot if possible')

    parser.add_argument('--stopafter', default=0, type=int,
                        required=False, action='store',
                        help='Stop after x lines of LIVE data')

    parser.add_argument('--nostore',
                        required=False, action='store_true',
                        help='Do not Use the store pattern')

    parser.add_argument('--qcheck', default=0.5, type=float,
                        required=False, action='store',
                        help=('Timeout for periodic '
                              'notification/resampling/replaying check'))

    parser.add_argument('--no-timeoffset',
                        required=False, action='store_true',
                        help=('Do not Use TWS/System time offset for non '
                              'timestamped prices and to align resampling'))

    parser.add_argument('--data0', default=None,
                        required=True, action='store',
                        help='data 0 into the system')

    parser.add_argument('--tradename', default=None,
                        required=False, action='store',
                        help='Actual Trading Name of the asset')

    parser.add_argument('--data1', default=None,
                        required=False, action='store',
                        help='data 1 into the system')

    parser.add_argument('--timezone', default=None,
                        required=False, action='store',
                        help='timezone to get time output into (pytz names)')

    parser.add_argument('--historical',
                        required=False, action='store_true',
                        help='do only historical download')

    parser.add_argument('--fromdate',
                        required=False, action='store',
                        help=('Starting date for historical download '
                              'with format: YYYY-MM-DD[THH:MM:SS]'))

    parser.add_argument('--todate',
                        required=False, action='store',
                        help=('End date for historical download '
                              'with format: YYYY-MM-DD[THH:MM:SS]'))

    parser.add_argument('--smaperiod', default=5, type=int,
                        required=False, action='store',
                        help='Period to apply to the Simple Moving Average')

    pgroup = parser.add_mutually_exclusive_group(required=False)

    pgroup.add_argument('--replay',
                        required=False, action='store_true',
                        help='replay to chosen timeframe')

    pgroup.add_argument('--resample',
                        required=False, action='store_true',
                        help='resample to chosen timeframe')

    parser.add_argument('--timeframe', default=bt.TimeFrame.Names[0],
                        choices=bt.TimeFrame.Names,
                        required=False, action='store',
                        help='TimeFrame for Resample/Replay')

    parser.add_argument('--compression', default=1, type=int,
                        required=False, action='store',
                        help='Compression for Resample/Replay')

    parser.add_argument('--no-bar2edge',
                        required=False, action='store_true',
                        help='no bar2edge for resample/replay')

    parser.add_argument('--no-adjbartime',
                        required=False, action='store_true',
                        help='no adjbartime for resample/replay')

    parser.add_argument('--no-rightedge',
                        required=False, action='store_true',
                        help='no rightedge for resample/replay')

    parser.add_argument('--broker',
                        required=False, action='store_true',
                        help='Use VisualChart as broker')

    parser.add_argument('--account', default=None,
                        required=False, action='store',
                        help='Choose broker account (else first)')

    parser.add_argument('--trade',
                        required=False, action='store_true',
                        help='Do Sample Buy/Sell operations')

    parser.add_argument('--donotsell',
                        required=False, action='store_true',
                        help='Do not sell after a buy')

    parser.add_argument('--exectype', default=bt.Order.ExecTypes[0],
                        choices=bt.Order.ExecTypes,
                        required=False, action='store',
                        help='Execution to Use when opening position')

    parser.add_argument('--price', default=None, type=float,
                        required=False, action='store',
                        help='Price in Limit orders or Stop Trigger Price')

    parser.add_argument('--pstoplimit', default=None, type=float,
                        required=False, action='store',
                        help='Price for the limit in StopLimit')

    parser.add_argument('--stake', default=10, type=int,
                        required=False, action='store',
                        help='Stake to use in buy operations')

    parser.add_argument('--valid', default=None,
                        required=False, action='store',
                        help='Seconds or YYYY-MM-DD')

    parser.add_argument('--cancel', default=0, type=int,
                        required=False, action='store',
                        help=('Cancel a buy order after n bars in operation,'
                              ' to be combined with orders like Limit'))

    return parser.parse_args()

if __name__ == '__main__':
    runstrategy()

标签:False,二十三,BackTrader,--,self,args,default,文档,data
From: https://www.cnblogs.com/apachecn/p/18135545

相关文章

  • BackTrader 中文文档(二十四)
    原文:www.backtrader.com/终极振荡器原文:www.backtrader.com/blog/posts/2016-06-22-ultimate-oscillator/ultimate-oscillator/当启动backtrader的开发时,其中一个目标是使其非常容易(至少对于作者本人来说)开发新的Indicators以测试数学和视觉上的想法。Ticket#102是关于将......
  • BackTrader 中文文档(二十五)
    原文:www.backtrader.com/Bid-Ask数据到OHLC原文:www.backtrader.com/blog/posts/2016-04-14-bidask-data-to-ohlc/bidask-data-to-ohlc/最近,backtrader通过实现线覆盖来执行了从ohlcland的逃逸,这允许重新定义整个层次结构,例如拥有仅包含bid、ask和datetime行的数据......
  • BackTrader 中文文档(二十六)
    原文:www.backtrader.com/条形图同步www.backtrader.com/blog/posts/2015-10-04-bar-synchronization/bar-synchronization/文献和/或行业中缺乏标准公式并不是问题,因为问题实际上可以总结为:条形图同步工单#23提出了一些关于backtrader是否可以计算相对成交量指标的疑......
  • BackTrader 中文文档(八)
    原文:www.backtrader.com/订单订单。译文:www.backtrader.com/docu/order/Cerebro是backtrader中的关键控制系统,而Strategy(一个子类)是最终用户的关键控制点。后者需要一种方法将系统的其他部分链接起来,这就是订单发挥关键作用的地方。订单将Strategy中的逻辑决策转化......
  • BackTrader 中文文档(十)
    原文:www.backtrader.com/用户自定义佣金原文:www.backtrader.com/docu/user-defined-commissions/commission-schemes-subclassing/重塑CommInfo对象到实际形式的最重要部分涉及:保留原始的CommissionInfo类和行为为轻松创建用户定义的佣金打开大门将格式xx%......
  • BackTrader 中文文档(十一)
    原文:www.backtrader.com/基准测试原文:www.backtrader.com/docu/observer-benchmark/benchmarking/票号#89是关于添加对资产的基准测试的。理智的做法是,即使有一种策略,即使是正的,也低于简单跟踪资产将提供的内容。backtrader包含两种不同类型的对象,可以帮助进行跟踪:......
  • BackTrader 中文文档(十二)
    原文:www.backtrader.com/VisualChart原文:www.backtrader.com/docu/live/vc/vc/与VisualChart的集成支持两者:实时数据提供实时交易VisualChart是完整的交易解决方案:在单个平台上集成图表、数据源和经纪功能更多信息,请访问:www.visualchart.com需求Vi......
  • BackTrader 中文文档(十三)
    原文:www.backtrader.com/交易日历原文:www.backtrader.com/docu/tradingcalendar/tradingcalendar/发布1.9.42.116版本添加了对交易日历的支持。在例如以下情况下重采样时,这很有用:现在,从每日到每周的重采样可以将每周柱与周的最后一根柱一起交付。这是因为交易日历可以......
  • BackTrader 中文文档(十四)
    原文:www.backtrader.com/在backtrader中交易加密货币的分数大小原文:www.backtrader.com/blog/posts/2019-08-29-fractional-sizes/fractional-sizes/首先,让我们用两行总结一下backtrader的工作方式:就像一个基本构建块(Cerebro)的构建套件,可以将许多不同的部件插入其中......
  • BackTrader 中文文档(十五)
    原文:www.backtrader.com/动量策略原文:www.backtrader.com/blog/2019-05-20-momentum-strategy/momentum-strategy/在另一篇很棒的文章中,TeddyKoker再次展示了算法交易策略的发展路径:首先应用pandas进行研究使用backtrader进行回测真棒!!!文章可以在以下位置......