Bracket订单#

发布版本``1.9.37.116``新增了Bracket订单功能,提供了后测经纪商支持的各种订单类型(MarketLimitCloseStopStopLimitStopTrailStopTrailLimitOCO)。

Bracket订单不是一个单独的订单,实际上由*3*个订单组成。让我们考虑看多的情况:

  • 主要的买入订单,通常设为``Limit``或``StopLimit``订单

  • 低价位的卖出订单,通常设为``Stop``订单以限制损失

  • 高价位的卖出订单,通常设为``Limit``订单以获利

而看空情况则是相应的卖出和2个买入订单。

低价位和高价位的订单实际上围绕主要订单形成一个Bracket。

为了使其具备逻辑,以下规则适用:使用模式#

有两种方法可以创建订单的括号设置

  • 单次提交3个订单- 手动发出三个订单

单独发出一组括号订单#

backtrader 在``Strategy``中提供了两种控制*括号*订单的新方法。

  • buy_bracketsell_bracket

注意

签名和信息可以在``Strategy``参考章节中找到。

通过一条语句完成一组包含3个订单。示例如下:

brackets = self.buy_bracket(limitprice=14.00, price=13.50, stopprice=13.00)

请注意``stopprice`` 和 limitprice 包裹了主要的``price``

这就足够了。实际的目标``data``应该是``data0``,并且``size``将会由默认的sizer自动确定。当然,还可以指定其他参数来对执行进行细致控制。

返回值为:- 包含这个顺序的三个订单的 list: [主订单, 停止订单, 限制订单]

因为在发出一个 sell_bracket 订单时,低位和高位将被颠倒,参数的命名遵循惯例,即以 stoplimit 命名。

  • stop 用于止损(在做多操作中的低位,以及在做空操作中的高位)

  • limit 用于获利(在做多操作中的高位,以及在做空操作中的低位)

手动发起 Bracket#

这涉及到生成三个订单并调整 transmitparent 参数。规则如下:

  • 主订单必须首先创建,并且具有 transmit=False

  • 低位/高位订单必须具有 parent=主订单

  • 第一个低位/高位订单必须具有 transmit=False

  • 最后一个被创建的订单(无论是低位还是高位)设置 transmit=True。上面的单个命令实际示例:

    mainside = self.buy(price=13.50, exectype=bt.Order.Limit, transmit=False) lowside = self.sell(price=13.00, size=mainsize.size, exectype=bt.Order.Stop,

    transmit=False, parent=mainside)

    highside = self.sell(price=14.00, size=mainsize.size, exectype=bt.Order.Limit,

    transmit=True, parent=mainside)

这里还有更多要做的事情:

  • 跟踪 mainside 订单,指示它是其他订单的父订单

  • 控制 transmit 以确保只有最后一个订单触发联合传输

  • 指定执行类型

  • 为低侧和高侧指定 size

    因为 size 必须 是相同的。如果未手动指定该参数,并且最终用户引入了一个定量器,定量器实际上可以指示订单的不同值。这就是为什么必须在为 mainside 订单设置完毕之后,在调用中手动添加它的原因。

示例#

运行下面的示例会产生以下输出(为了简洁起见):

$ ./bracket.py --plot

2005-01-28: 订单编号 1 / 以 2941.11055 的价格购买
2005-01-28: 订单编号 2 / 以 2881.99275 的价格卖出止损单
2005-01-28: 订单编号 3 / 以 3000.22835 的价格卖出限价单
2005-01-31: 订单编号: 1 / 类型 购买 / 状态 已提交
2005-01-31: 订单编号: 2 / 类型 卖出 / 状态 已提交
2005-01-31: 订单编号: 3 / 类型 卖出 / 状态 已提交
2005-01-31: 订单编号: 1 / 类型 购买 / 状态 已接受
2005-01-31: 订单编号: 2 / 类型 卖出 / 状态 已接受
2005-01-31: 订单编号: 3 / 类型 卖出 / 状态 已接受
2005-02-01: 订单编号: 1 / 类型 购买 / 状态 已过期
2005-02-01: 订单编号: 2 / 类型 卖出 / 状态 已取消
2005-02-01: 订单编号: 3 / 类型 卖出 / 状态 已取消
...
2005-08-11: 订单编号 16 / 以 3337.3892 的价格购买
2005-08-11: 订单编号 17 / 以 3270.306 的价格卖出止损单
2005-08-11: 订单编号 18 / 以 3404.4724 的价格卖出限价单
2005-08-12: 订单编号: 16 / 类型 购买 / 状态 已提交
2005-08-12: 订单编号: 17 / 类型 卖出 / 状态 已提交
2005-08-12: 订单编号: 18 / 类型 卖出 / 状态 已提交
2005-08-12: 订单编号: 16 / 类型 购买 / 状态 已接受
2005-08-12: 订单编号: 17 / 类型 卖出 / 状态 已接受
2005-08-12: 订单编号: 18 / 类型 卖出 / 状态 已接受
2005-08-12: 订单编号: 16 / 类型 购买 / 状态 已完成
2005-08-18: 订单编号: 17 / 类型 卖出 / 状态 已完成
2005-08-18: 订单编号: 18 / 类型 卖出 / 状态 已取消
...
2005-09-26: 订单编号 22 / 以 3383.92535 的价格购买
2005-09-26: 订单编号 23 / 以 3315.90675 的价格卖出止损单
2005-09-26: 订单编号 24 / 以 3451.94395 的价格卖出限价单
2005-09-27: 订单编号: 22 / 类型 购买 / 状态 已提交
2005-09-27: 订单编号: 23 / 类型 卖出 / 状态 已提交
2005-09-27: 订单编号: 24 / 类型 卖出 / 状态 已提交
2005-09-27: 订单编号: 22 / 类型 购买 / 状态 已接受
2005-09-27: 订单编号: 23 / 类型 卖出 / 状态 已接受
2005-09-27: 订单编号: 24 / 类型 卖出 / 状态 已接受
2005-09-27: 订单编号: 22 / 类型 购买 / 状态 已完成
2005-10-04: 订单编号: 24 / 类型 卖出 / 状态 已完成
2005-10-04: 订单编号: 23 / 类型 卖出 / 状态 已取消
...

显示了3种不同的结果:- 在第一种情况下,主要的附加订单已过期,从而自动取消了其他两个订单。

  • 在第二种情况下,主要的附加订单已完成,并且低价(在买入情况下为止损)被执行,限制了损失。

  • 在第三种情况下,主要的附加订单已完成,并且高价(限价)被执行。

可以注意到,“已完成”订单的id为22和24,而**高**附加订单是最后发出的,这意味着未执行的低附加订单的id为23。

视觉上

可以立即看出亏损交易和盈利交易都围绕着相同的值对齐,这是括号的目的。控制两侧。

运行样例时,可以手动发出3个订单,但也可以告诉它使用``buy_bracket``。让我们看看输出结果:

$ ./bracket.py --strat usebracket=True

结果相同.. thumbnail:: bracket-buy_bracket.png

一些参考#

查看新的 buy_bracketsell_bracket 方法

  def buy_bracket(self, data=None, size=None, price=None, plimit=None,
                  exectype=bt.Order.Limit, valid=None, tradeid=0,
                  trailamount=None, trailpercent=None, oargs={},
                  stopprice=None, stopexec=bt.Order.Stop, stopargs={},
                  limitprice=None, limitexec=bt.Order.Limit, limitargs={},
                  **kwargs):
      '''
      创建一个买卖订单组(低侧 - 买单 - 高侧)。默认行为如下:

        - 发出一个**买**单,执行为 ``Limit``

        - 发出一个低侧**卖**断订单,执行为 ``Stop``

        - 发出一个高侧**卖**断订单,执行为 ``Limit``

      以下是不同参数的说明

        - ``data`` (默认值: ``None``)

          要创建订单的数据。如果为 ``None``,则使用系统中的第一个数据,即 ``self.datas[0] or self.data0``(也称为 ``self.data``)。- `size` (默认值: `None` )

用于订单的数据单位的大小(正数)。

如果为 `None` ,则会使用通过 `getsizer` 获取的 `sizer` 实例来确定大小。

**注意**:相同的大小适用于括号的所有3个订单
  • price (默认值: None

    要使用的价格(如果不符合最小tick大小的要求,实时经纪商可能会对实际格式施加限制)

    对于 Market 和`Close 订单, None`是有效的(市场确定价格)

    对于 LimitStop `和`StopLimit 订单,此值确定触发点(对于 Limit ,明显触发是订单应该匹配的价格)

  • plimit (默认值: None

    仅适用于 StopLimit 订单。这是在触发 Stop (使用了 price )后设置隐式 Limit 订单的价格。 - trailamount``(默认值:``None

    如果订单类型是停止跟踪或停止跟踪限价订单,这是一个绝对金额,用于确定价格的距离(对于卖出订单是低于价格,对于买入订单是高于价格)以保持追踪停止。

    • trailpercent``(默认值:``None

      如果订单类型是停止跟踪或停止跟踪限价订单,这是一个百分比金额,用于确定价格的距离(对于卖出订单是低于价格,对于买入订单是高于价格)以保持追踪停止(如果还指定了``trailamount``,则会使用它)。

    • exectype``(默认值:``bt.Order.Limit

      可能的值:(请参阅方法``buy``的文档)

    • valid``(默认值:``None

      可能的值:(请参阅方法``buy``的文档)

    • tradeid``(默认值:``0

      可能的值:(请参阅方法``buy``的文档)- ``oargs``(默认值:“{}”)

    传递给主体交易订单的特定关键字参数(以 dict 形式)。默认的 **kwargs 参数将在此基础上应用。

  • **kwargs:其他经纪人实现可能支持额外的参数。 backtrader 将把 kwargs 传递给创建的订单对象。

    可能的值:(请参阅``buy``方法的文档)

    注意:这些``kwargs``将应用于bracket中的3个订单。有关低侧和高侧订单的特定关键字参数,请参阅下文

  • stopprice``(默认值:``None

    低侧止损订单的特定价格

  • stopexec``(默认值:``bt.Order.Stop

    低侧订单的特定执行类型

  • stopargs``(默认值:“{}”)特定关键字参数(在 ``dict 中)传递给低位订单。默认 **kwargs 中的参数将被应用在这个参数的顶部。

  • limitprice``(默认值: ``None

    高位 止损单的特定价格

  • stopexec``(默认值: ``bt.Order.Limit

    高位 订单的特定执行类型

  • limitargs``(默认值: ``{}

    传递给高位订单的特定关键字参数(在 dict 中)。默认 **kwargs 中的参数将被应用在这个参数的顶部。

返回值: - 包含 3 个括号订单 [order, 停损方向, 止盈方向] 的列表 ‘’’

def sell_bracket(self, data=None,

size=None, price=None, plimit=None, exectype=bt.Order.Limit, valid=None, tradeid=0, trailamount=None, trailpercent=None, oargs={}, stopprice=None, stopexec=bt.Order.Stop, stopargs={}, limitprice=None, limitexec=bt.Order.Limit, limitargs={}, **kwargs):

‘’’ 创建一个括号订单组(低位 - 卖出订单 - 高位)。默认行为如下:

  • 发出一个具有执行方式 Limit卖出 订单- 发出一份*高侧*括号**买入**订单,执行方式为“Stop”

  • 发出一份*低侧*括号**买入**订单,执行方式为“Limit”

有关参数含义,请参考“bracket_buy”

返回: - 包含3个括号订单[订单,止损方向,限价方向]的列表

示例用法#

$ ./bracket.py --help
usage: bracket.py [-h] [--data0 DATA0] [--fromdate FROMDATE] [--todate TODATE]
                  [--cerebro kwargs] [--broker kwargs] [--sizer kwargs]
                  [--strat kwargs] [--plot [kwargs]]

Sample Skeleton

可选参数:
  -h, --help           显示此帮助消息并退出
  --data0 DATA0        要读取的数据(默认值:../../datas/2005-2006-day-001.txt)
  --fromdate FROMDATE  格式为YYYY-MM-DD[THH:MM:SS]的日期[时间](默认值:)
  --todate TODATE      格式为YYYY-MM-DD[THH:MM:SS]的日期[时间](默认值:)
  --cerebro kwargs     以键=值格式的kwargs(默认值:)
  --broker kwargs      以键=值格式的kwargs(默认值:)
  --sizer kwargs       以键=值格式的kwargs(默认值:)
  --strat kwargs       以键=值格式的kwargs(默认值:)
  --plot [kwargs]      以键=值格式的kwargs(默认值:)

示例代码#

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


import argparse
import datetime

import backtrader as bt


class St(bt.Strategy):
    params = dict(
        ma=bt.ind.SMA,
        p1=5,
        p2=15,
        limit=0.005,
        limdays=3,
        limdays2=1000,
        hold=10,
        usebracket=False,  # use order_target_size
        switchp1p2=False,  # switch prices of order1 and order2
    )

    def notify_order(self, order):
        print('{}: Order ref: {} / Type {} / Status {}'.format(
            self.data.datetime.date(0),
            order.ref, 'Buy' * order.isbuy() or 'Sell',
            order.getstatusname()))

        if order.status == order.Completed:
            self.holdstart = len(self)

        if not order.alive() and order.ref in self.orefs:
            self.orefs.remove(order.ref)

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

        self.orefs = list()

        if self.p.usebracket:
            print('-' * 5, 'Using buy_bracket')

    def next(self):
        if self.orefs:
            return  # pending orders do nothing

        if not self.position:
            if self.cross > 0.0:  # crossing up

                close = self.data.close[0]
                p1 = close * (1.0 - self.p.limit)
                p2 = p1 - 0.02 * close
                p3 = p1 + 0.02 * close

                valid1 = datetime.timedelta(self.p.limdays)
                valid2 = valid3 = datetime.timedelta(self.p.limdays2)

                if self.p.switchp1p2:
                    p1, p2 = p2, p1
                    valid1, valid2 = valid2, valid1

                if not self.p.usebracket:
                    o1 = self.buy(exectype=bt.Order.Limit,
                                  price=p1,
                                  valid=valid1,
                                  transmit=False)

                    print('{}: Oref {} / Buy at {}'.format(
                        self.datetime.date(), o1.ref, p1))

                    o2 = self.sell(exectype=bt.Order.Stop,
                                   price=p2,
                                   valid=valid2,
                                   parent=o1,
                                   transmit=False)

                    print('{}: Oref {} / Sell Stop at {}'.format(
                        self.datetime.date(), o2.ref, p2))

                    o3 = self.sell(exectype=bt.Order.Limit,
                                   price=p3,
                                   valid=valid3,
                                   parent=o1,
                                   transmit=True)

                    print('{}: Oref {} / Sell Limit at {}'.format(
                        self.datetime.date(), o3.ref, p3))

                    self.orefs = [o1.ref, o2.ref, o3.ref]

                else:
                    os = self.buy_bracket(
                        price=p1, valid=valid1,
                        stopprice=p2, stopargs=dict(valid=valid2),
                        limitprice=p3, limitargs=dict(valid=valid3),)

                    self.orefs = [o.ref for o in os]

        else:  # in the market
            if (len(self) - self.holdstart) >= self.p.hold:
                pass  # do nothing in this case


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

    cerebro = bt.Cerebro()

    # Data feed kwargs
    kwargs = dict()

    # Parse from/to-date
    dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S'
    for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']):
        if a:
            strpfmt = dtfmt + tmfmt * ('T' in a)
            kwargs[d] = datetime.datetime.strptime(a, strpfmt)

    # Data feed
    data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **kwargs)
    cerebro.adddata(data0)

    # Broker
    cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')'))

    # Sizer
    cerebro.addsizer(bt.sizers.FixedSize, **eval('dict(' + args.sizer + ')'))

    # Strategy
    cerebro.addstrategy(St, **eval('dict(' + args.strat + ')'))

    # Execute
    cerebro.run(**eval('dict(' + args.cerebro + ')'))

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


def parse_args(pargs=None):
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description=(
            'Sample Skeleton'
        )
    )

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

    # Defaults for dates
    parser.add_argument('--fromdate', required=False, default='',
                        help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')

    parser.add_argument('--todate', required=False, default='',
                        help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')

    parser.add_argument('--cerebro', required=False, default='',
                        metavar='kwargs', help='kwargs in key=value format')

    parser.add_argument('--broker', required=False, default='',
                        metavar='kwargs', help='kwargs in key=value format')

    parser.add_argument('--sizer', required=False, default='',
                        metavar='kwargs', help='kwargs in key=value format')

    parser.add_argument('--strat', required=False, default='',
                        metavar='kwargs', help='kwargs in key=value format')

    parser.add_argument('--plot', required=False, default='',
                        nargs='?', const='{}',
                        metavar='kwargs', help='kwargs in key=value format')

    return parser.parse_args(pargs)


if __name__ == '__main__':
    runstrat()