Backtrader分数大小

  |  

首先,让我们用两总结反向交易方法的工作原理:

  • 它就像一个带有基本构建块 ( Cerebro ) 的建筑套件,可以插入许多不同的部件

  • 基本分布包含许多部分,如指针、分析器观察器、 Sizer 、过滤器、数据馈送、经纪人、佣金/资产信息方案,...

  • 可以从头开始或基于现有的构建块轻松构建新的构建块

  • 基本构建块 ( Cerebro ) 已经做了一些自动“插入”,以便更轻松地使用框架,而不必担心所有细节。

因此,该框架已预先配置为提供具有默认值的行为,例如:

  • 使用单个/主要数据馈送
  • 1-day时间框架/压缩组合
  • 10,000 单位货币
  • 股票交易

这可能并不适合所有人,但重要的是:它可以根据每个交易者/进程员的个人需求进行定制

交易股票:整数

如上所述,默认配置是用于股票交易,当交易股票时,购买/出售完整股(即:1、2 ... 50 ... 1000,而不是像1.51001.7589股那样的数量。

这意味着当用户在默认配置中运行以下操作时:

    def next(self):
        # Apply 50% of the portfolio to buy the main asset
        self.order_target_percent(target=0.5)

会发生以下情况:

  • 系统计算需要多少股资产,使给定资产在投资组合中的价值尽可能接近50%

  • 但是因为默认配置是使用共享,所以生成的共享数将是一个整数,即:整数

笔记

请注意,默认配置是使用单个/主要数据提要,这就是为什么在对order_percent_target的调用中未指定实际数据的原因。在使用多个数据源进行操作时,必须指定要获取/出售哪些数据(除非是指主要数据)

交易加密货币:分数

很明显,在交易加密货币时,即使是小数点后 20 位,也可以买到“半个比特币”。

好消息是人们实际上可以更改与资产有关的信息。这是通过CommissionInfo系列可插拔部件实现的。

一些文档: Docs - Commission Schemes - https://www. backtrader .com/docu/commission-schemes/commission-schemes/

笔记

不得不承认,这个名字是不幸的,因为这些计划不仅包含有关佣金的信息,还包含其他信息。

在分数场景中,感兴趣的是该方案的这种方法: getsize(price, cash) ,它具有以下文档字符串

Returns the needed size to meet a cash operation at a given price

这些方案与代理密切相关,通过代理API,可以将方案添加到系统中。

代理文档位于: Docs - Broker - https://www。 backtrader .com/docu/broker/

相关的方法是: addcommissioninfo(comminfo, name=None) 。除了添加适用于所有资产的方案(当nameNone时)之外,还可以设置仅适用于具有特定名称的资产的方案。

实施分数方案

这可以通过扩展名为CommissionInfo的现有基础方案轻松实现。

class CommInfoFractional(bt.CommissionInfo):
    def getsize(self, price, cash):
        '''Returns fractional size for cash operation @price'''
        return self.p.leverage * (cash / price)

同上并完成。继承CommissionInfo并编写一行方法,目的就达到了。因为原始方案定义支持leverage ,所以在计算中考虑到了这一点,以防万一可以用杠杆购买加密货币(默认值为1.0 ,即:无杠杆)

稍后在代码中,将像这样添加方案(通过命令参数控制)

    if args.fractional:  # use the fractional scheme if requested
        cerebro.broker.addcommissioninfo(CommInfoFractional())

即:添加子类方案的实例(注意()实例化)。如上所述, name参数未设置,这意味着它将应用于系统中的所有资产。

测试野兽

下面提供了实现多头/空头头寸的简单移动平均线交叉的完整脚本,可以直接在 shell 中使用。测试的默认数据馈送是来自backtrader存储库的数据馈送之一。

整数运行:没有分数 - 没有乐趣

$ ./fractional-sizes.py --plot
2005-02-14,3079.93,3083.38,3065.27,3075.76,0.00
2005-02-15,3075.20,3091.64,3071.08,3086.95,0.00
...
2005-03-21,3052.39,3059.18,3037.80,3038.14,0.00
2005-03-21,Enter Short
2005-03-22,Sell Order Completed - Size: -16 @Price: 3040.55 Value: -48648.80 Comm: 0.00
2005-03-22,Trade Opened  - Size -16 @Price 3040.55
2005-03-22,3040.55,3053.18,3021.66,3050.44,0.00
...

一个规模为16单位的空头交易已打开。整个日志(由于明显原因未显示)包含许多其他操作,所有操作都包含完整大小的交易。

分数运行

在对分数进行硬子类化和单线工作之后......

$ ./fractional-sizes.py --fractional --plot
2005-02-14,3079.93,3083.38,3065.27,3075.76,0.00
2005-02-15,3075.20,3091.64,3071.08,3086.95,0.00
...
2005-03-21,3052.39,3059.18,3037.80,3038.14,0.00
2005-03-21,Enter Short
2005-03-22,Sell Order Completed - Size: -16.457437774427774 @Price: 3040.55 Value: -50039.66 Comm: 0.00
2005-03-22,Trade Opened  - Size -16.457437774427774 @Price 3040.55
2005-03-22,3040.55,3053.18,3021.66,3050.44,0.00
...

V代表胜利。空头交易以相同的交叉开盘,但这次的小数大小为-16.457437774427774

请注意,图表中的最终投资组合价值不同,这是因为实际交易规模不同。

是的,反向交易者可以。使用可插入/可扩展的构建工具包方法,可以很容易地根据交易者进程员的特定需求定制行为。

#!/usr/bin/env python
# -*- coding: utf-8; py-indent-offset:4 -*-
###############################################################################
# Copyright (C) 2019 Daniel Rodriguez - MIT License
#  - https://opensource.org/licenses/MIT
#  - https://en.wikipedia.org/wiki/MIT_License
###############################################################################
import argparse
import logging
import sys

import backtrader as bt

# This defines not only the commission info, but some other aspects
# of a given data asset like the "getsize" information from below
# params = dict(stocklike=True)  # No margin, no multiplier


class CommInfoFractional(bt.CommissionInfo):
    def getsize(self, price, cash):
        '''Returns fractional size for cash operation @price'''
        return self.p.leverage * (cash / price)


class St(bt.Strategy):
    params = dict(
        p1=10, p2=30,  # periods for crossover
        ma=bt.ind.SMA,  # moving average to use
        target=0.5,  # percentage of value to use
    )

    def __init__(self):
        ma1, ma2 = [self.p.ma(period=p) for p in (self.p.p1, self.p.p2)]
        self.cross = bt.ind.CrossOver(ma1, ma2)

    def next(self):
        self.logdata()
        if self.cross > 0:
            self.loginfo('Enter Long')
            self.order_target_percent(target=self.p.target)
        elif self.cross < 0:
            self.loginfo('Enter Short')
            self.order_target_percent(target=-self.p.target)

    def notify_trade(self, trade):
        if trade.justopened:
            self.loginfo('Trade Opened  - Size {} @Price {}',
                         trade.size, trade.price)
        elif trade.isclosed:
            self.loginfo('Trade Closed  - Profit {}', trade.pnlcomm)

        else:  # trade updated
            self.loginfo('Trade Updated - Size {} @Price {}',
                         trade.size, trade.price)

    def notify_order(self, order):
        if order.alive():
            return

        otypetxt = 'Buy ' if order.isbuy() else 'Sell'
        if order.status == order.Completed:
            self.loginfo(
                ('{} Order Completed - '
                 'Size: {} @Price: {} '
                 'Value: {:.2f} Comm: {:.2f}'),
                otypetxt, order.executed.size, order.executed.price,
                order.executed.value, order.executed.comm
            )
        else:
            self.loginfo('{} Order rejected', otypetxt)

    def loginfo(self, txt, *args):
        out = [self.datetime.date().isoformat(), txt.format(*args)]
        logging.info(','.join(out))

    def logerror(self, txt, *args):
        out = [self.datetime.date().isoformat(), txt.format(*args)]
        logging.error(','.join(out))

    def logdebug(self, txt, *args):
        out = [self.datetime.date().isoformat(), txt.format(*args)]
        logging.debug(','.join(out))

    def logdata(self):
        txt = []
        txt += ['{:.2f}'.format(self.data.open[0])]
        txt += ['{:.2f}'.format(self.data.high[0])]
        txt += ['{:.2f}'.format(self.data.low[0])]
        txt += ['{:.2f}'.format(self.data.close[0])]
        txt += ['{:.2f}'.format(self.data.volume[0])]
        self.loginfo(','.join(txt))


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

    cerebro = bt.Cerebro()

    data = bt.feeds.BacktraderCSVData(dataname=args.data)
    cerebro.adddata(data)  # create and add data feed

    cerebro.addstrategy(St)  # add the strategy

    cerebro.broker.set_cash(args.cash)  # set broker cash

    if args.fractional:  # use the fractional scheme if requested
        cerebro.broker.addcommissioninfo(CommInfoFractional())

    cerebro.run()  # execute

    if args.plot:  # Plot if requested to
        cerebro.plot(**eval('dict(' + args.plot + ')'))


def logconfig(pargs):
    if pargs.quiet:
        verbose_level = logging.ERROR
    else:
        verbose_level = logging.INFO - pargs.verbose * 10  # -> DEBUG

    logger = logging.getLogger()
    for h in logger.handlers:  # Remove all loggers from root
        logger.removeHandler(h)

    stream = sys.stdout if not pargs.stderr else sys.stderr  # choose stream

    logging.basicConfig(
        stream=stream,
        format="%(message)s",  # format="%(levelname)s: %(message)s",
        level=verbose_level,
    )


def parse_args(pargs=None):
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description='Fractional Sizes with CommInfo',
    )

    pgroup = parser.add_argument_group('Data Options')
    parser.add_argument('--data', default='../../datas/2005-2006-day-001.txt',
                        help='Data to read in')

    pgroup = parser.add_argument_group(title='Broker Arguments')
    pgroup.add_argument('--cash', default=100000.0, type=float,
                        help='Starting cash to use')

    pgroup.add_argument('--fractional', action='store_true',
                        help='Use fractional commission info')

    pgroup = parser.add_argument_group(title='Plotting Arguments')
    pgroup.add_argument('--plot', default='', nargs='?', const='{}',
                        metavar='kwargs', help='kwargs: "k1=v1,k2=v2,..."')

    pgroup = parser.add_argument_group('Verbosity Options')
    pgroup.add_argument('--stderr', action='store_true',
                        help='Log to stderr, else to stdout')
    pgroup = pgroup.add_mutually_exclusive_group()
    pgroup.add_argument('--quiet', '-q', action='store_true',
                        help='Silent (errors will be reported)')
    pgroup.add_argument('--verbose', '-v', action='store_true',
                        help='Increase verbosity level')

    # Parse and process some args
    pargs = parser.parse_args(pargs)
    logconfig(pargs)  # config logging
    return pargs


if __name__ == '__main__':
    run()

推荐阅读

相关文章

Backtrader教程:分析仪

无论是回溯测试还是交易,能够分析交易系统的性能是了解是否不仅获得了利润,而且是否在风险太大的情况下实现了利润,或者与参考资产(或无风险资产)相比,是否真的值得付出努力的关键。 这就是对象家族的用武之Analyzer 地:提供对所发生事件甚至实际发生的事情的分析。

Backtrader做空现金

从一开始,反向交易者就可以做空任何东西,包括类似股票和类似期货的工具。当做空时,现金减少,被卖空资产的价值用于总净清算价值。从一侧移除并添加到另一侧可以保持平衡。人们似乎更喜欢增加现金,这可能会增加支出。在1.9.7.105版本中,经纪人已将默认行为更改为添加现金和移除价值。

Backtrader对逐笔报价数据重新采样

backtrader 已经可以从分钟数据中重新采样。接受价格变动数据不是问题,只需将 4 个常用字段(open、 high、 low、 close)设置为价格变动值。 但是传递要重新采样的逐笔报价数据再次生成相同的数据。作为或版本 1.1.11.88,情况已不再如此。

Backtrader教程:仓位

资产的头寸通常从策略中检查: position (财产)或 getposition(data=None, broker=None) 这将返回策略在默认broker状态下datas[0]的位置,由cerebro 仓位只是指示: 资产被持有size 平均价格price 它作为一种状态,

Backtrader 教程:绘图 - 同一轴

上一篇future-spot 将原始数据和稍微(随机)修改的数据绘制在同一空间上,但不在同一轴上。从该帖子中恢复第一张图片。有人能看见:图表左右两侧有不同的刻度当查看在原始数据周围振荡+- 50点的摆动红线(随机数据)时,这一点最为明显。在图表上,视觉印像是这些随机数据大多总是高于原始数据。

Backtrader教程:数据馈送 - 展期交割

并非每个供应商都为可以交易的工具提供连续的未来。有时提供的数据是仍然有效的到期日期的数据,即:仍在交易的日期 这在回溯测试方面并不是很有帮助,因为数据分散在几个不同的仪器上,这些仪器另外...时间重叠。 能够正确地将这些仪器的数据从过去连接到连续的流中,可以减轻疼痛。

Backtrader教程:Cerebro - 优化 - 改进

backtrader版本1.8.12.99改进了在多处理过程中管理data feeds和结果的方式。

Backtrader版本 1.2.1.88

将次要版本号从 1 更改为 2 需要一段时间,但旧的 DataResampler 和 DataReplayer 的弃用导致了这种情况。 readthedocs 的文档有 文档已更新为仅引用现代方法 resampling 和 replaying。

Backtrader 教程:订单 - 目标订单

在1.8.10.96版本之前,可以使用反向交易者通过策略方法进行智能质押: buy和sell 。这一切都是为了在负责赌注大小的等式中添加一个Sizer 。 Sizer不能做的是决定操作是买入还是卖出。这意味着需要一个新概念,在其中添加一个小的智能层来做出这样的决定。

Backtrader信号策略

操作 backtrader 也是可能的,而无需编写策略。虽然这是首选方式,但由于构成机器的对象层次结构,使用信号也是可能的。