前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >BackTrader 中文文档(八)

BackTrader 中文文档(八)

作者头像
ApacheCN_飞龙
发布2024-05-24 17:57:45
1240
发布2024-05-24 17:57:45
举报
文章被收录于专栏:信数据得永生信数据得永生

原文:www.backtrader.com/

订单

订单。

译文:www.backtrader.com/docu/order/

Cerebrobacktrader 中的关键控制系统,而 Strategy(一个子类)是最终用户的关键控制点。后者需要一种方法将系统的其他部分链接起来,这就是订单发挥关键作用的地方。

订单Strategy 中的逻辑决策转化为适合 Broker 执行操作的消息。这是通过以下方式完成的:

  • 创建。 通过 Strategy 的方法:buy\``,sellclose(Strategy),它们返回一个order`实例作为参考。
  • 取消。 通过 Strategy 的方法:cancel(Strategy),它接受一个订单实例进行操作。

订单也作为一种向用户传达信息的方法,通知经纪人运行情况。

  • 通知。 对 Strategy 方法:notify_order(Strategy),报告一个order实例。

订单创建。

在调用 buysellclose 时,以下参数适用于创建:

data(默认值:None)。

要创建订单的数据。如果为 None,则系统中的第一个数据,self.datas[0] 或 self.data0(又名 self.data),将被使用。

size(默认值:None)。

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

如果为 None,则将使用通过 getsizer 检索到的 sizer 实例来确定大小。

price(默认值:None)。

要使用的价格(实时经纪人可能对实际格式施加限制,如果不符合最小跳动要求)。

None 对于 MarketClose 订单是有效的(市场确定价格)。

对于 LimitStopStopLimit 订单,此值确定触发点(在 Limit 的情况下,触发显然是订单应该匹配的价格)。

plimit(默认值:None)。

仅适用于 StopLimit 订单。这是在Stop触发后设置隐式限价订单的价格(其中使用了price)。

exectype(默认值:None)。

可能的值:

  • Order.MarketNone。市价订单将以下一个可用价格执行。在回测中,它将是下一个柱的开盘价。
  • Order.Limit。只能以给定的 price 或更好的价格执行的订单。
  • Order.Stop。在 price 触发并像 Order.Market 订单一样执行的订单。
  • Order.StopLimit。在 price 触发并作为隐式限价订单执行的订单,其价格由 pricelimit 给出。

valid(默认值:None)。

可能的值:

  • None:这将生成一个不会过期的订单(也称为有效直至取消),并保持在市场上直到匹配或取消。实际上,经纪人往往会强加时间限制,但通常时间跨度很长,可以视为不会过期。
  • datetime.datetimedatetime.date 实例:日期将用于生成一个在给定日期时间之前有效的订单(也称为有效截止日期)。
  • Order.DAY0timedelta(): 将生成一个直到Session 结束(又称day订单)有效的订单
  • numeric value: 假定这是与matplotlib编码中的日期时间相对应的值(backtrader使用的编码),并将用于生成直到那个时间点有效的订单(good till date)。

tradeid(默认:0

这是backtrader用来跟踪同一资产上重叠交易的内部值。当订单状态发生变化时,该tradeid会发送回策略

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

例如:如果backtrader直接支持的 4 种订单执行类型不够用,例如Interactive Brokers的情况下,可以将以下内容作为kwargs传递:

代码语言:javascript
复制
orderType='LIT', lmtPrice=10.0, auxPrice=9.8` 

这将覆盖由backtrader创建的设置,并生成具有touched价格为 9.8 和limit价格为 10.0 的LIMIT IF TOUCHED订单。

注意

close方法将检查当前仓位,并相应地使用buysell来有效地close该仓位。 size也将自动计算,除非参数是来自用户的输入,在这种情况下可以实现部分closereversal

订单通知

要接收通知,用户子类的Strategy必须重写notify_order方法(默认行为是什么也不做)。以下内容适用于这些通知:

  • 在调用策略的next方法之前发出
  • 可能(并且将)在相同的next周期内多次发生对于相同或不同状态的相同order订单可能在next再次被调用之前提交给经纪人接受并且其执行完成。 在这种情况下,至少会有 3 个通知,其status值如下:
    • Order.Submitted因为订单已发送给经纪人
    • Order.Accepted因为订单已被经纪人接受并等待可能的执行。
    • Order.Completed因为在示例中它被迅速匹配和完全填充(这在Market订单通常情况下可能是发生的)。

即使在相同的状态下,通知也可能多次发生在Order.Partial的情况下。这个状态不会在backtesting经纪人(不考虑匹配时的成交量)中看到,但是肯定会被真实的经纪人设置。

真实的经纪人可能在更新仓位之前发出一个或多个执行,这组执行将组成Order.Partial通知。

实际执行数据在属性中:order.executed,它是OrderData类型的对象(参见下文的引用),具有常见的字段如sizeprice

创建时的值存储在order.created中,在order的整个生命周期内保持不变。

订单状态值

以下内容已定义:

  • Order.Created:在创建Order实例时设置。 除非订单实例是手动创建而不是通过买入卖出关闭,否则永远不会被最终用户看到。
  • Order.Submitted:在订单实例已传输到经纪人时设置。 这只意味着它已经发送。 在回测模式下,这将是一个立即的动作,但是在实际的时间中,这可能需要一段时间才能与真正的经纪人完成,这可能会接收订单,只有在转发到交易所时才会首次通知。
  • Order.Acceptedbroker已接受订单并且已经在系统中(或已经在交易所中)等待根据设置的参数(如执行类型,大小,价格和有效性)执行
  • Order.Partial:订单已部分执行。 order.executed包含当前填充的size和平均价格。 order.executed.exbits包含一个详细的ExecutionBits列表,详细说明了部分填充情况
  • Order.Complete:订单已完全填充的平均价格。
  • Order.Rejected:经纪人已拒绝订单。 一个参数(例如确定其生存期的valid)可能不被经纪人接受,订单将无法被接受。 原因将通过strategynotify_store方法通知。 尽管这可能看起来很奇怪,但原因是真实生活的经纪人将通过事件通知这一点,该事件可能与订单直接相关也可能与订单无关。 但是来自经纪人的通知仍然可以在notify_store中看到。 此状态将不会在回测经纪人中看到
  • Order.Margin:订单执行将意味着保证金调用,并且先前接受的订单已从系统中移除
  • Order.Cancelled(或Order.Canceled):用户请求取消的确认 必须考虑到通过策略的cancel方法取消订单的请求不是取消的保证。 订单可能已经被执行,但是经纪人可能尚未通知,和/或通知可能尚未传递给策略。
  • Order.Expired:先前接受的订单其具有时间有效性已过期并已从系统中移除

参考:订单和相关类

这些对象是backtrader生态系统中的通用类。 在与其他经纪人操作时,它们可以被扩展和/或包含额外的嵌入信息。 请参阅适当经纪人的参考资料。

backtrader.order.Order()

持有创建/执行数据和订单类型的类。

订单可能具有以下状态:

  • 已提交:已发送到经纪人并等待确认
  • 已接受:经纪人接受的
  • 部分:部分执行
  • 已完成:已完全执行
  • 已取消:用户取消的已取消
  • 已过期:已过期
  • 保证金:没有足够的现金执行订单。
  • 被拒绝:被经纪人拒绝 这可能发生在订单提交时(因此订单不会达到已接受的状态)或在执行之前,每个新的条价格因为现金已经被其他来源提取(类似期货的工具可能会减少现金或订单已经被执行)

成员属性:

  • ref:唯一的订单标识符
  • created:持有创建数据的 OrderData
  • executed:持有执行数据的订单数据
  • info:通过方法 addinfo() 传递的自定义信息。它以 OrderedDict 的形式保留,已经被子类化,因此还可以使用‘.’符号指定键

用户方法:

  • isbuy():返回一个布尔值,指示订单是否购买
  • issell():返回一个布尔值,指示订单是否卖出
  • alive():如果订单处于部分或已接受状态,则返回布尔值
类 backtrader.order.OrderData(dt=None, size=0, price=0.0, pricelimit=0.0, remsize=0, pclose=0.0, trailamount=0.0, trailpercent=0.0)

包含创建和执行的实际订单数据。

在创建时进行的请求,在执行时进行的实际结果。

成员属性:

  • exbits:此 OrderData 的 OrderExecutionBits 的可迭代对象
  • dt:日期时间(浮点数)创建/执行时间
  • size:请求/执行大小
  • price:执行价格注意:如果未给出价格且未给出价格限制,则将在订单创建时使用当前收盘价作为参考
  • pricelimit:保存 StopLimit 的价格限制(先触发)
  • trailamount:追踪止损的绝对价格距离
  • trailpercent:追踪止损的百分比价格距离
  • value:整个比特大小的市场价值
  • comm:整个比特执行的佣金
  • pnl:由此比特生成的 pnl(如果有东西被关闭)
  • margin:订单所产生的保证金(如果有)
  • psize:当前开放位置大小
  • pprice:当前开放位置价格
类 backtrader.order.OrderExecutionBit(dt=None, size=0, price=0.0, closed=0, closedvalue=0.0, closedcomm=0.0, opened=0, openedvalue=0.0, openedcomm=0.0, pnl=0.0, psize=0, pprice=0.0)

用于保存订单执行信息。 “比特” 不确定订单是否已完全/部分执行,它只是保存信息。

成员属性:

  • dt:日期时间(浮点数)执行时间
  • size:执行了多少
  • price:执行价格
  • closed:执行关闭了现有的位置多少
  • opened:执行打开了多少新的位置
  • openedvalue:打开部分的市值
  • closedvalue:关闭部分的市值
  • closedcomm:关闭部分的佣金
  • openedcomm:打开部分的佣金
  • value:整个比特大小的市场价值
  • comm:整个比特执行的佣金
  • pnl:由此比特生成的 pnl(如果有东西被关闭)
  • psize:当前开放位置大小
  • pprice:当前开放位置价格

订单管理和执行

原文:www.backtrader.com/docu/order-creation-execution/order-creation-execution/

如果订单不能模拟,回测和因此backtrader将不完整。为此,平台提供了以下功能。

对于订单管理的 3 个基本原则:

  • buy
  • sell
  • cancel

注意

显然,update原语是一种逻辑,但常识告诉我们,这种方法主要由使用判断性交易方法的手动操作者使用。

对于订单执行逻辑,有以下执行类型:

  • Market
  • Close
  • Limit
  • Stop
  • StopLimit

订单管理

一些例子:

代码语言:javascript
复制
# buy the main date, with sizer default stake, Market order
order = self.buy()

# Market order - valid will be "IGNORED"
order = self.buy(valid=datetime.datetime.now() + datetime.timedelta(days=3))

# Market order - price will be IGNORED
order = self.buy(price=self.data.close[0] * 1.02)

# Market order - manual stake
order = self.buy(size=25)

# Limit order - want to set the price and can set a validity
order = self.buy(exectype=Order.Limit,
                 price=self.data.close[0] * 1.02,
                 valid=datetime.datetime.now() + datetime.timedelta(days=3)))

# StopLimit order - want to set the price, price limit
order = self.buy(exectype=Order.StopLimit,
                 price=self.data.close[0] * 1.02,
                 plimit=self.data.close[0] * 1.07)

# Canceling an existing order
self.broker.cancel(order)

注意

所有订单类型都可以通过创建一个Order实例(或其子类之一),然后通过以下方式传递给经纪人:

代码语言:javascript
复制
order = self.broker.submit(order)

注意

broker本身有buysell的基本原则,但对于默认参数要求更严格。

订单执行逻辑

经纪人对订单执行有两个主要准则(假设?)

当前数据已经发生,不能用于执行订单。

如果策略中的逻辑是这样的:

代码语言:javascript
复制
 if self.data.close > self.sma:  # where sma is a Simple Moving Average
     self.buy()` 

期望不能是订单将以正在逻辑中检查的close价格执行,因为它已经发生。

订单可以在下一个一组开/高/低/收价格点的范围内(以及订单中规定的条件)第一次执行

交易量不起作用

如果交易者选择非流动资产或者精确地击中价格柱的极端点(高/低),它在实际交易中确实会发生。

但是击中高/低点是很少发生的(如果你这样做…你就不需要backtrader),并且所选择的资产将有足够的流动性来吸收任何常规交易的订单

市场

执行:

  • 下一个一组开/高/低/收价格的开盘价(通常称为

原因:

  • 如果逻辑在时间点 X 执行并发出Market订单,那么接下来会发生的价格是即将到来的open价格

注意

这个订单总是执行,并且忽略了用于创建它的pricevalid参数

Close

执行:

  • 当下一个柱子实际关闭时,使用下一个柱子的close价格

原因:

  • 大多数回测数据源已经包含了关闭的柱子,订单将立即以下一个柱子的close价格执行。日线数据源是最常见的例子。 但是系统可以提供“tick”价格,并且实际柱(时间/日期方面)会不断更新新的 tick,而不会实际移动到下一个柱(因为时间和/或日期尚未更改) 只有当时间或日期发生变化时,柱子才会真正关闭并执行订单
Limit

执行:

  • 订单创建时设置的price,如果data触及它,从下一个价格柱开始。 如果设置了valid且时间点已到达,则订单将被取消

价格匹配:

  • backtrader尝试为Limit订单提供最逼真的执行价格。 使用 4 个价格点(开盘价/最高价/最低价/收盘价),可以部分推断请求的价格是否可以改善。 对于Buy订单
    • 情况 1: 如果柱的开盘价低于限价,则订单立即以开盘价执行。订单已在会话开启阶段清算
    • 情况 2: 如果开盘价没有穿过限价但最低价低于限价,则在会话期间已见到限价,订单可以执行

    对于Sell订单,逻辑显然是相反的。

停止

执行:

  • 如果数据触及触发价格,则设置在订单创建时,从下一个价格柱开始。 如果设置了valid且时间点已到达,则订单将被取消

价格匹配:

  • backtrader尝试为Stop订单提供最逼真的触发价格。 使用 4 个价格点(开盘价/最高价/最低价/收盘价),可以部分推断请求的价格是否可以改善。 对于Buy\Stoporders
    • 情况 1: 如果柱的开盘价高于止损价,则订单立即以开盘价执行。 旨在在价格向上移动反对现有空头头寸时停止损失
    • 情况 2: 如果开盘价没有穿过止损价但最高价高于止损价,则在会话期间已见到止损价,订单可以执行

    对于SellStop订单,逻辑显然是相反的。

停止限价

执行:

  • 触发价格设置订单,从下一个价格柱开始。

价格匹配:

  • 触发:使用Stop匹配逻辑(但仅触发并将订单转换为Limit订单)
  • 限价:使用Limit价格匹配逻辑

一些样本

一如既往,图片(带代码)价值数百万长的解释。请注意,片段集中在订单创建部分。完整代码在底部。

使用价格在简单移动平均线上/下方关闭策略来生成买入/卖出信号

信号在图表底部可见:使用交叉指示器的CrossOver

将生成的“买入”订单的参考保留,以允许系统中最多同时存在一个订单。

执行类型:市价

在图表中看到,订单是在信号生成后的一个价格柱后执行的,使用开盘价。

代码语言:javascript
复制
 if self.p.exectype == 'Market':
                self.buy(exectype=bt.Order.Market)  # default if not given

                self.log('BUY CREATE, exectype Market, price %.2f' %
                         self.data.close[0])

输出图表。

image
image

命令行和输出:

代码语言:javascript
复制
$ ./order-execution-samples.py --exectype Market
2006-01-26T23:59:59+00:00, BUY CREATE, exectype Market, price 3641.42
2006-01-26T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-01-27T23:59:59+00:00, BUY EXECUTED, Price: 3643.35, Cost: 3643.35, Comm 0.00
2006-03-02T23:59:59+00:00, SELL CREATE, 3763.73
2006-03-02T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-03T23:59:59+00:00, SELL EXECUTED, Price: 3763.95, Cost: 3763.95, Comm 0.00
...
...
2006-12-11T23:59:59+00:00, BUY CREATE, exectype Market, price 4052.89
2006-12-11T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-12-12T23:59:59+00:00, BUY EXECUTED, Price: 4052.55, Cost: 4052.55, Comm 0.00
执行类型:关闭

现在订单也是在信号后一个柱执行的,但是使用收盘价。

代码语言:javascript
复制
 elif self.p.exectype == 'Close':
                self.buy(exectype=bt.Order.Close)

                self.log('BUY CREATE, exectype Close, price %.2f' %
                         self.data.close[0])

输出图表。

image
image

命令行和输出:

代码语言:javascript
复制
$ ./order-execution-samples.py --exectype Close
2006-01-26T23:59:59+00:00, BUY CREATE, exectype Close, price 3641.42
2006-01-26T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-01-27T23:59:59+00:00, BUY EXECUTED, Price: 3685.48, Cost: 3685.48, Comm 0.00
2006-03-02T23:59:59+00:00, SELL CREATE, 3763.73
2006-03-02T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-03T23:59:59+00:00, SELL EXECUTED, Price: 3763.95, Cost: 3763.95, Comm 0.00
...
...
2006-11-06T23:59:59+00:00, BUY CREATE, exectype Close, price 4045.22
2006-11-06T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-11-07T23:59:59+00:00, BUY EXECUTED, Price: 4072.86, Cost: 4072.86, Comm 0.00
2006-11-24T23:59:59+00:00, SELL CREATE, 4048.16
2006-11-24T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-11-27T23:59:59+00:00, SELL EXECUTED, Price: 4045.05, Cost: 4045.05, Comm 0.00
2006-12-11T23:59:59+00:00, BUY CREATE, exectype Close, price 4052.89
2006-12-11T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-12-12T23:59:59+00:00, BUY EXECUTED, Price: 4059.74, Cost: 4059.74, Comm 0.00
执行类型:限价

在几行之前计算有效性,以防已作为参数传递。

代码语言:javascript
复制
 if self.p.valid:
                valid = self.data.datetime.date(0) + \
                        datetime.timedelta(days=self.p.valid)
            else:
                valid = None

设置了比信号生成价格(信号栏收盘价)低 1%的限价。请注意,这样可以防止上述许多订单被执行。

代码语言:javascript
复制
 elif self.p.exectype == 'Limit':
                price = self.data.close * (1.0 - self.p.perc1 / 100.0)

                self.buy(exectype=bt.Order.Limit, price=price, valid=valid)

                if self.p.valid:
                    txt = 'BUY CREATE, exectype Limit, price %.2f, valid: %s'
                    self.log(txt % (price, valid.strftime('%Y-%m-%d')))
                else:
                    txt = 'BUY CREATE, exectype Limit, price %.2f'
                    self.log(txt % price)

输出图表。

image
image

仅发出了 4 个订单。试图捕捉小幅下跌的价格限制完全改变了输出结果。

命令行和输出:

代码语言:javascript
复制
$ ./order-execution-samples.py --exectype Limit --perc1 1
2006-01-26T23:59:59+00:00, BUY CREATE, exectype Limit, price 3605.01
2006-01-26T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-05-18T23:59:59+00:00, BUY EXECUTED, Price: 3605.01, Cost: 3605.01, Comm 0.00
2006-06-05T23:59:59+00:00, SELL CREATE, 3604.33
2006-06-05T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-06-06T23:59:59+00:00, SELL EXECUTED, Price: 3598.58, Cost: 3598.58, Comm 0.00
2006-06-21T23:59:59+00:00, BUY CREATE, exectype Limit, price 3491.57
2006-06-21T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-06-28T23:59:59+00:00, BUY EXECUTED, Price: 3491.57, Cost: 3491.57, Comm 0.00
2006-07-13T23:59:59+00:00, SELL CREATE, 3562.56
2006-07-13T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-07-14T23:59:59+00:00, SELL EXECUTED, Price: 3545.92, Cost: 3545.92, Comm 0.00
2006-07-24T23:59:59+00:00, BUY CREATE, exectype Limit, price 3596.60
2006-07-24T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
执行类型:带有效期的限价

为了不永远等待限价订单,该订单仅在 4(日历)天内有效,而这可能只有在价格对“买入”订单不利时才能执行。

输出图表。

image
image

生成了更多订单,但除了一个“买入”订单外,所有订单均已到期,进一步限制了操作数量。

命令行和输出:

代码语言:javascript
复制
$ ./order-execution-samples.py --exectype Limit --perc1 1 --valid 4
2006-01-26T23:59:59+00:00, BUY CREATE, exectype Limit, price 3605.01, valid: 2006-01-30
2006-01-26T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-01-30T23:59:59+00:00, BUY EXPIRED
2006-03-10T23:59:59+00:00, BUY CREATE, exectype Limit, price 3760.48, valid: 2006-03-14
2006-03-10T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-14T23:59:59+00:00, BUY EXPIRED
2006-03-30T23:59:59+00:00, BUY CREATE, exectype Limit, price 3835.86, valid: 2006-04-03
2006-03-30T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-04-03T23:59:59+00:00, BUY EXPIRED
2006-04-20T23:59:59+00:00, BUY CREATE, exectype Limit, price 3821.40, valid: 2006-04-24
2006-04-20T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-04-24T23:59:59+00:00, BUY EXPIRED
2006-05-04T23:59:59+00:00, BUY CREATE, exectype Limit, price 3804.65, valid: 2006-05-08
2006-05-04T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-05-08T23:59:59+00:00, BUY EXPIRED
2006-06-01T23:59:59+00:00, BUY CREATE, exectype Limit, price 3611.85, valid: 2006-06-05
2006-06-01T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-06-05T23:59:59+00:00, BUY EXPIRED
2006-06-21T23:59:59+00:00, BUY CREATE, exectype Limit, price 3491.57, valid: 2006-06-25
2006-06-21T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-06-26T23:59:59+00:00, BUY EXPIRED
2006-07-24T23:59:59+00:00, BUY CREATE, exectype Limit, price 3596.60, valid: 2006-07-28
2006-07-24T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-07-28T23:59:59+00:00, BUY EXPIRED
2006-09-12T23:59:59+00:00, BUY CREATE, exectype Limit, price 3751.07, valid: 2006-09-16
2006-09-12T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-09-18T23:59:59+00:00, BUY EXPIRED
2006-09-20T23:59:59+00:00, BUY CREATE, exectype Limit, price 3802.90, valid: 2006-09-24
2006-09-20T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-09-22T23:59:59+00:00, BUY EXECUTED, Price: 3802.90, Cost: 3802.90, Comm 0.00
2006-11-02T23:59:59+00:00, SELL CREATE, 3974.62
2006-11-02T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-11-03T23:59:59+00:00, SELL EXECUTED, Price: 3979.73, Cost: 3979.73, Comm 0.00
2006-11-06T23:59:59+00:00, BUY CREATE, exectype Limit, price 4004.77, valid: 2006-11-10
2006-11-06T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-11-10T23:59:59+00:00, BUY EXPIRED
2006-12-11T23:59:59+00:00, BUY CREATE, exectype Limit, price 4012.36, valid: 2006-12-15
2006-12-11T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-12-15T23:59:59+00:00, BUY EXPIRED
执行类型:Stop

设置了比信号价格高 1%的停止价格。这意味着策略只有在信号产生且价格继续上涨时才会购买,这可能被解释为一种强势信号。

这完全改变了执行全景。

代码语言:javascript
复制
 elif self.p.exectype == 'Stop':
                price = self.data.close * (1.0 + self.p.perc1 / 100.0)

                self.buy(exectype=bt.Order.Stop, price=price, valid=valid)

                if self.p.valid:
                    txt = 'BUY CREATE, exectype Stop, price %.2f, valid: %s'
                    self.log(txt % (price, valid.strftime('%Y-%m-%d')))
                else:
                    txt = 'BUY CREATE, exectype Stop, price %.2f'
                    self.log(txt % price)

输出图表。

image
image

命令行和输出:

代码语言:javascript
复制
$ ./order-execution-samples.py --exectype Stop --perc1 1
2006-01-26T23:59:59+00:00, BUY CREATE, exectype Stop, price 3677.83
2006-01-26T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-01-27T23:59:59+00:00, BUY EXECUTED, Price: 3677.83, Cost: 3677.83, Comm 0.00
2006-03-02T23:59:59+00:00, SELL CREATE, 3763.73
2006-03-02T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-03T23:59:59+00:00, SELL EXECUTED, Price: 3763.95, Cost: 3763.95, Comm 0.00
2006-03-10T23:59:59+00:00, BUY CREATE, exectype Stop, price 3836.44
2006-03-10T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-15T23:59:59+00:00, BUY EXECUTED, Price: 3836.44, Cost: 3836.44, Comm 0.00
2006-03-28T23:59:59+00:00, SELL CREATE, 3811.45
2006-03-28T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-29T23:59:59+00:00, SELL EXECUTED, Price: 3811.85, Cost: 3811.85, Comm 0.00
2006-03-30T23:59:59+00:00, BUY CREATE, exectype Stop, price 3913.36
2006-03-30T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-09-29T23:59:59+00:00, BUY EXECUTED, Price: 3913.36, Cost: 3913.36, Comm 0.00
2006-11-02T23:59:59+00:00, SELL CREATE, 3974.62
2006-11-02T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-11-03T23:59:59+00:00, SELL EXECUTED, Price: 3979.73, Cost: 3979.73, Comm 0.00
2006-11-06T23:59:59+00:00, BUY CREATE, exectype Stop, price 4085.67
2006-11-06T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-11-13T23:59:59+00:00, BUY EXECUTED, Price: 4085.67, Cost: 4085.67, Comm 0.00
2006-11-24T23:59:59+00:00, SELL CREATE, 4048.16
2006-11-24T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-11-27T23:59:59+00:00, SELL EXECUTED, Price: 4045.05, Cost: 4045.05, Comm 0.00
2006-12-11T23:59:59+00:00, BUY CREATE, exectype Stop, price 4093.42
2006-12-11T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-12-13T23:59:59+00:00, BUY EXECUTED, Price: 4093.42, Cost: 4093.42, Comm 0.00
执行类型:StopLimit

设置了比信号价格高 1%的停止价格。但限价设置在信号(收盘)价格的 0.5%以上,这可能被解释为:等待力量显现,但不要买入顶峰。等待下跌。

有效期限制为 20(日历)天

代码语言:javascript
复制
 elif self.p.exectype == 'StopLimit':
                price = self.data.close * (1.0 + self.p.perc1 / 100.0)

                plimit = self.data.close * (1.0 + self.p.perc2 / 100.0)

                self.buy(exectype=bt.Order.StopLimit, price=price, valid=valid,
                         plimit=plimit)

                if self.p.valid:
                    txt = ('BUY CREATE, exectype StopLimit, price %.2f,'
                           ' valid: %s, pricelimit: %.2f')
                    self.log(txt % (price, valid.strftime('%Y-%m-%d'), plimit))
                else:
                    txt = ('BUY CREATE, exectype StopLimit, price %.2f,'
                           ' pricelimit: %.2f')
                    self.log(txt % (price, plimit))

输出图表。

image
image

命令行和输出:

代码语言:javascript
复制
$ ./order-execution-samples.py --exectype StopLimit --perc1 1 --perc2 0.5 --valid 20
2006-01-26T23:59:59+00:00, BUY CREATE, exectype StopLimit, price 3677.83, valid: 2006-02-15, pricelimit: 3659.63
2006-01-26T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-02-03T23:59:59+00:00, BUY EXECUTED, Price: 3659.63, Cost: 3659.63, Comm 0.00
2006-03-02T23:59:59+00:00, SELL CREATE, 3763.73
2006-03-02T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-03T23:59:59+00:00, SELL EXECUTED, Price: 3763.95, Cost: 3763.95, Comm 0.00
2006-03-10T23:59:59+00:00, BUY CREATE, exectype StopLimit, price 3836.44, valid: 2006-03-30, pricelimit: 3817.45
2006-03-10T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-21T23:59:59+00:00, BUY EXECUTED, Price: 3817.45, Cost: 3817.45, Comm 0.00
2006-03-28T23:59:59+00:00, SELL CREATE, 3811.45
2006-03-28T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-29T23:59:59+00:00, SELL EXECUTED, Price: 3811.85, Cost: 3811.85, Comm 0.00
2006-03-30T23:59:59+00:00, BUY CREATE, exectype StopLimit, price 3913.36, valid: 2006-04-19, pricelimit: 3893.98
2006-03-30T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-04-19T23:59:59+00:00, BUY EXPIRED
...
...
2006-12-11T23:59:59+00:00, BUY CREATE, exectype StopLimit, price 4093.42, valid: 2006-12-31, pricelimit: 4073.15
2006-12-11T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-12-22T23:59:59+00:00, BUY EXECUTED, Price: 4073.15, Cost: 4073.15, Comm 0.00
测试脚本执行

在命令行help中详细说明:

代码语言:javascript
复制
$ ./order-execution-samples.py --help
usage: order-execution-samples.py [-h] [--infile INFILE]
                                  [--csvformat {bt,visualchart,sierrachart,yahoo,yahoo_unreversed}]
                                  [--fromdate FROMDATE] [--todate TODATE]
                                  [--plot] [--plotstyle {bar,line,candle}]
                                  [--numfigs NUMFIGS] [--smaperiod SMAPERIOD]
                                  [--exectype EXECTYPE] [--valid VALID]
                                  [--perc1 PERC1] [--perc2 PERC2]

Showcase for Order Execution Types

optional arguments:
  -h, --help            show this help message and exit
  --infile INFILE, -i INFILE
                        File to be read in
  --csvformat {bt,visualchart,sierrachart,yahoo,yahoo_unreversed},
  -c {bt,visualchart,sierrachart,yahoo,yahoo_unreversed}
                        CSV Format
  --fromdate FROMDATE, -f FROMDATE
                        Starting date in YYYY-MM-DD format
  --todate TODATE, -t TODATE
                        Ending date in YYYY-MM-DD format
  --plot, -p            Plot the read data
  --plotstyle {bar,line,candle}, -ps {bar,line,candle}
                        Plot the read data
  --numfigs NUMFIGS, -n NUMFIGS
                        Plot using n figures
  --smaperiod SMAPERIOD, -s SMAPERIOD
                      Simple Moving Average Period
  --exectype EXECTYPE, -e EXECTYPE
                        Execution Type: Market (default), Close, Limit,
                        Stop, StopLimit
  --valid VALID, -v VALID
                        Validity for Limit sample: default 0 days
  --perc1 PERC1, -p1 PERC1
                        % distance from close price at order creation time for
                        the limit/trigger price in Limit/Stop orders
  --perc2 PERC2, -p2 PERC2
                        % distance from close price at order creation time for
                        the limit price in StopLimit orders
完整代码
代码语言:javascript
复制
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 OrderExecutionStrategy(bt.Strategy):
    params = (
        ('smaperiod', 15),
        ('exectype', 'Market'),
        ('perc1', 3),
        ('perc2', 1),
        ('valid', 4),
    )

    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:
            # An order is pending ... nothing can be done
            return

        # Check if we are in the market
        if self.position:
            # In the maerket - check if it's the time to sell
            if self.buysell < 0:
                self.log('SELL CREATE, %.2f' % self.data.close[0])
                self.sell()

        elif self.buysell > 0:
            if self.p.valid:
                valid = self.data.datetime.date(0) + \
                        datetime.timedelta(days=self.p.valid)
            else:
                valid = None

            # Not in the market and signal to buy
            if self.p.exectype == 'Market':
                self.buy(exectype=bt.Order.Market)  # default if not given

                self.log('BUY CREATE, exectype Market, price %.2f' %
                         self.data.close[0])

            elif self.p.exectype == 'Close':
                self.buy(exectype=bt.Order.Close)

                self.log('BUY CREATE, exectype Close, price %.2f' %
                         self.data.close[0])

            elif self.p.exectype == 'Limit':
                price = self.data.close * (1.0 - self.p.perc1 / 100.0)

                self.buy(exectype=bt.Order.Limit, price=price, valid=valid)

                if self.p.valid:
                    txt = 'BUY CREATE, exectype Limit, price %.2f, valid: %s'
                    self.log(txt % (price, valid.strftime('%Y-%m-%d')))
                else:
                    txt = 'BUY CREATE, exectype Limit, price %.2f'
                    self.log(txt % price)

            elif self.p.exectype == 'Stop':
                price = self.data.close * (1.0 + self.p.perc1 / 100.0)

                self.buy(exectype=bt.Order.Stop, price=price, valid=valid)

                if self.p.valid:
                    txt = 'BUY CREATE, exectype Stop, price %.2f, valid: %s'
                    self.log(txt % (price, valid.strftime('%Y-%m-%d')))
                else:
                    txt = 'BUY CREATE, exectype Stop, price %.2f'
                    self.log(txt % price)

            elif self.p.exectype == 'StopLimit':
                price = self.data.close * (1.0 + self.p.perc1 / 100.0)

                plimit = self.data.close * (1.0 + self.p.perc2 / 100.0)

                self.buy(exectype=bt.Order.StopLimit, price=price, valid=valid,
                         plimit=plimit)

                if self.p.valid:
                    txt = ('BUY CREATE, exectype StopLimit, price %.2f,'
                           ' valid: %s, pricelimit: %.2f')
                    self.log(txt % (price, valid.strftime('%Y-%m-%d'), plimit))
                else:
                    txt = ('BUY CREATE, exectype StopLimit, price %.2f,'
                           ' pricelimit: %.2f')
                    self.log(txt % (price, plimit))

def runstrat():
    args = parse_args()

    cerebro = bt.Cerebro()

    data = getdata(args)
    cerebro.adddata(data)

    cerebro.addstrategy(
        OrderExecutionStrategy,
        exectype=args.exectype,
        perc1=args.perc1,
        perc2=args.perc2,
        valid=args.valid,
        smaperiod=args.smaperiod
    )
    cerebro.run()

    if args.plot:
        cerebro.plot(numfigs=args.numfigs, style=args.plotstyle)

def getdata(args):

    dataformat = dict(
        bt=btfeeds.BacktraderCSVData,
        visualchart=btfeeds.VChartCSVData,
        sierrachart=btfeeds.SierraChartCSVData,
        yahoo=btfeeds.YahooFinanceCSVData,
        yahoo_unreversed=btfeeds.YahooFinanceCSVData
    )

    dfkwargs = dict()
    if args.csvformat == 'yahoo_unreversed':
        dfkwargs['reverse'] = True

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

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

    dfkwargs['dataname'] = args.infile

    dfcls = dataformat[args.csvformat]

    return dfcls(**dfkwargs)

def parse_args():
    parser = argparse.ArgumentParser(
        description='Showcase for Order Execution Types')

    parser.add_argument('--infile', '-i', required=False,
                        default='../../datas/2006-day-001.txt',
                        help='File to be read in')

    parser.add_argument('--csvformat', '-c', required=False, default='bt',
                        choices=['bt', 'visualchart', 'sierrachart',
                                 'yahoo', 'yahoo_unreversed'],
                        help='CSV Format')

    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', '-p', action='store_false', required=False,
                        help='Plot the read data')

    parser.add_argument('--plotstyle', '-ps', required=False, default='bar',
                        choices=['bar', 'line', 'candle'],
                        help='Plot the read data')

    parser.add_argument('--numfigs', '-n', required=False, default=1,
                        help='Plot using n figures')

    parser.add_argument('--smaperiod', '-s', required=False, default=15,
                        help='Simple Moving Average Period')

    parser.add_argument('--exectype', '-e', required=False, default='Market',
                        help=('Execution Type: Market (default), Close, Limit,'
                              ' Stop, StopLimit'))

    parser.add_argument('--valid', '-v', required=False, default=0, type=int,
                        help='Validity for Limit sample: default 0 days')

    parser.add_argument('--perc1', '-p1', required=False, default=0.0,
                        type=float,
                        help=('%% distance from close price at order creation'
                              ' time for the limit/trigger price in Limit/Stop'
                              ' orders'))

    parser.add_argument('--perc2', '-p2', required=False, default=0.0,
                        type=float,
                        help=('%% distance from close price at order creation'
                              ' time for the limit price in StopLimit orders'))

    return parser.parse_args()

if __name__ == '__main__':
    runstrat()

目标订单

原文:www.backtrader.com/docu/order_target/order_target/

直到版本1.8.10.96,通过Strategy方法buysellbacktrader上实现了智能的投注。这一切都是关于在方程中添加一个Sizer,负责赌注的大小。

Sizer无法决定操作是买入还是卖出。这意味着需要一个新概念,其中添加了一个小智能层来做出这样的决定。

这就是Strategy中的order_target_xxx方法家族发挥作用的地方。受到zipline中方法的启发,这些方法提供了简单指定最终target的机会,无论目标是什么:

  • size -> 特定资产组合中的股票、合约数量
  • value -> 组合中资产的货币单位价值
  • percent -> 百分比(来自当前组合)资产在当前组合中的价值

注意

方法的参考可以在 Strategy 中找到。总结是,这些方法使用与buysell相同的signature,只是参数size被参数target替换

在这种情况下,关键在于指定最终的target,而方法决定操作是买入还是卖出。这个逻辑也适用于 3 种方法。让我们从order_target_size开始

  • 如果target大于持仓量,则发出买入指令,差额为target - position_size 示例:
    • Pos: 0, target: 7 -> 买入(size=7 - 0) -> 买入(size=7)
    • Pos: 3, target: 7 -> 买入(size=7 - 3) -> 买入(size=4)
    • Pos: -3, target: 7 -> 买入(size=7 - -3) -> 买入(size=10)
    • Pos: -3, target: -2 -> 买入(size=-2 - -3) -> 买入(size=1)
  • 如果target小于持仓量,则发出卖出指令,差额为position_size - target 示例:
    • Pos: 0, target: -7 -> 卖出(size=0 - -7) -> 卖出(size=7)
    • Pos: 3, target: -7 -> 卖出(size=3 - -7) -> 卖出(size=10)
    • Pos: -3, target: -7 -> sell(size=-3 - -7) -> sell(size=4)
    • Pos: 3, target: 2 -> sell(size=3 - 2) -> sell(size=1)

使用order_target_value来设定目标值时,会考虑组合中资产的当前valueposition size,以决定最终的基础操作。推理如下:

  • 如果position size为负(空头)且target value必须大于当前值,这意味着:卖出更多

因此,逻辑如下:

  • 如果target > valuesize >=0 -> 买入
  • 如果target > valuesize < 0 -> 卖出
  • 如果target < valuesize >= 0 -> 卖出
  • 如果target < valuesize < 0 -> 买入

order_target_percent的逻辑与order_target_value相同。该方法仅考虑组合的当前总价值,以确定资产的目标值

示例

backtrader尝试为每个新功能提供一个示例,这不例外。没有花里胡哨,只是为了测试结果是否符合预期。这个示例在order_target目录中。

示例中的逻辑相当愚蠢,只是用于测试:

  • 奇数月(一月,三月,…),使用作为目标(对于order_target_value,将日乘以1000) 这模仿了一个递增的目标
  • 偶数月(二月,四月,…)使用31 - day作为目标。 这模仿了一个递减的目标
order_target_size

让我们看看在一月二月会发生什么。

代码语言:javascript
复制
$ ./order_target.py --target-size -- plot
0001 - 2005-01-03 - Position Size:     00 - Value 1000000.00
0001 - 2005-01-03 - Order Target Size: 03
0002 - 2005-01-04 - Position Size:     03 - Value 999994.39
0002 - 2005-01-04 - Order Target Size: 04
0003 - 2005-01-05 - Position Size:     04 - Value 999992.48
0003 - 2005-01-05 - Order Target Size: 05
0004 - 2005-01-06 - Position Size:     05 - Value 999988.79
...
0020 - 2005-01-31 - Position Size:     28 - Value 999968.70
0020 - 2005-01-31 - Order Target Size: 31
0021 - 2005-02-01 - Position Size:     31 - Value 999954.68
0021 - 2005-02-01 - Order Target Size: 30
0022 - 2005-02-02 - Position Size:     30 - Value 999979.65
0022 - 2005-02-02 - Order Target Size: 29
0023 - 2005-02-03 - Position Size:     29 - Value 999966.33
0023 - 2005-02-03 - Order Target Size: 28
...

一月目标从第一交易日的3开始增加。初始时,仓位大小从03,然后以1的增量递增。

完成一月时,最后一个order_target31,当进入二月的第一天时,报告了该仓位大小,新的目标方向要求为30,并随着仓位递减。

图片
图片
order_target_value

类似的行为可预期来自目标值

代码语言:javascript
复制
$ ./order_target.py --target-value --plot
0001 - 2005-01-03 - Position Size:     00 - Value 1000000.00
0001 - 2005-01-03 - data value 0.00
0001 - 2005-01-03 - Order Target Value: 3000.00
0002 - 2005-01-04 - Position Size:     78 - Value 999854.14
0002 - 2005-01-04 - data value 2853.24
0002 - 2005-01-04 - Order Target Value: 4000.00
0003 - 2005-01-05 - Position Size:     109 - Value 999801.68
0003 - 2005-01-05 - data value 3938.17
0003 - 2005-01-05 - Order Target Value: 5000.00
0004 - 2005-01-06 - Position Size:     138 - Value 999699.57
...
0020 - 2005-01-31 - Position Size:     808 - Value 999206.37
0020 - 2005-01-31 - data value 28449.68
0020 - 2005-01-31 - Order Target Value: 31000.00
0021 - 2005-02-01 - Position Size:     880 - Value 998807.33
0021 - 2005-02-01 - data value 30580.00
0021 - 2005-02-01 - Order Target Value: 30000.00
0022 - 2005-02-02 - Position Size:     864 - Value 999510.21
0022 - 2005-02-02 - data value 30706.56
0022 - 2005-02-02 - Order Target Value: 29000.00
0023 - 2005-02-03 - Position Size:     816 - Value 999130.05
0023 - 2005-02-03 - data value 28633.44
0023 - 2005-02-03 - Order Target Value: 28000.00
...

有一行额外的信息告诉实际数据值(在投资组合中)是什么。这有助于确定是否已达到目标值

初始目标是3000.0,报告的初始值是2853.24。这里的问题是这是否足够接近。答案是是的

  • 示例在每日 K 线图结束时使用Market订单和最后可用价格来计算满足目标价值目标大小
  • 执行随后使用下一天的open价格,这不太可能是前一天的close

以任何其他方式做都意味着一个人在欺骗自己。

下一个目标值最终值要接近得多:40003938.17

当转换为二月时,目标值31000减少到3000029000。数据值也是如此,从30580.0030706.56,然后到28633.44。等等:

  • 30580 -> 30706.56是一个正变化。 确实。在这种情况下,计算出的目标值大小遇到了一个将值提高到30706.56开盘价

如何避免这种影响:

  • 示例在订单中使用Market类型的执行,这种效果无法避免。
  • 方法order_target_xxx允许指定执行类型价格。 一个可以指定Limit作为执行订单的方式,并让价格成为收盘价格(如果没有提供其他内容,则由该方法选择)或者甚至提供具体定价。
图片
图片
order_target_percent

在这种情况下,它只是当前投资组合价值的百分比。

代码语言:javascript
复制
$ ./order_target.py --target-percent --plot
0001 - 2005-01-03 - Position Size:     00 - Value 1000000.00
0001 - 2005-01-03 - data percent 0.00
0001 - 2005-01-03 - Order Target Percent: 0.03
0002 - 2005-01-04 - Position Size:     785 - Value 998532.05
0002 - 2005-01-04 - data percent 0.03
0002 - 2005-01-04 - Order Target Percent: 0.04
0003 - 2005-01-05 - Position Size:     1091 - Value 998007.44
0003 - 2005-01-05 - data percent 0.04
0003 - 2005-01-05 - Order Target Percent: 0.05
0004 - 2005-01-06 - Position Size:     1381 - Value 996985.64
...
0020 - 2005-01-31 - Position Size:     7985 - Value 991966.28
0020 - 2005-01-31 - data percent 0.28
0020 - 2005-01-31 - Order Target Percent: 0.31
0021 - 2005-02-01 - Position Size:     8733 - Value 988008.94
0021 - 2005-02-01 - data percent 0.31
0021 - 2005-02-01 - Order Target Percent: 0.30
0022 - 2005-02-02 - Position Size:     8530 - Value 995005.45
0022 - 2005-02-02 - data percent 0.30
0022 - 2005-02-02 - Order Target Percent: 0.29
0023 - 2005-02-03 - Position Size:     8120 - Value 991240.75
0023 - 2005-02-03 - data percent 0.29
0023 - 2005-02-03 - Order Target Percent: 0.28
...

数据信息已更改,以查看投资组合中数据所代表的%

图片
图片

示例用法

代码语言:javascript
复制
$ ./order_target.py --help
usage: order_target.py [-h] [--data DATA] [--fromdate FROMDATE]
                       [--todate TODATE] [--cash CASH]
                       (--target-size | --target-value | --target-percent)
                       [--plot [kwargs]]

Sample for Order Target

optional arguments:
  -h, --help            show this help message and exit
  --data DATA           Specific data to be read in (default:
                        ../../datas/yhoo-1996-2015.txt)
  --fromdate FROMDATE   Starting date in YYYY-MM-DD format (default:
                        2005-01-01)
  --todate TODATE       Ending date in YYYY-MM-DD format (default: 2006-12-31)
  --cash CASH           Ending date in YYYY-MM-DD format (default: 1000000)
  --target-size         Use order_target_size (default: False)
  --target-value        Use order_target_value (default: False)
  --target-percent      Use order_target_percent (default: False)
  --plot [kwargs], -p [kwargs]
                        Plot the read data applying any kwargs passed For
                        example: --plot style="candle" (to plot candles)
                        (default: None)

示例代码

代码语言:javascript
复制
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import argparse
from datetime import datetime

import backtrader as bt

class TheStrategy(bt.Strategy):
  '''
 This strategy is loosely based on some of the examples from the Van
 K. Tharp book: *Trade Your Way To Financial Freedom*. The logic:

 - Enter the market if:
 - The MACD.macd line crosses the MACD.signal line to the upside
 - The Simple Moving Average has a negative direction in the last x
 periods (actual value below value x periods ago)

 - Set a stop price x times the ATR value away from the close

 - If in the market:

 - Check if the current close has gone below the stop price. If yes,
 exit.
 - If not, update the stop price if the new stop price would be higher
 than the current
 '''

    params = (
        ('use_target_size', False),
        ('use_target_value', False),
        ('use_target_percent', False),
    )

    def notify_order(self, order):
        if order.status == order.Completed:
            pass

        if not order.alive():
            self.order = None  # indicate no order is pending

    def start(self):
        self.order = None  # sentinel to avoid operrations on pending order

    def next(self):
        dt = self.data.datetime.date()

        portfolio_value = self.broker.get_value()
        print('%04d - %s - Position Size: %02d - Value %.2f' %
              (len(self), dt.isoformat(), self.position.size, portfolio_value))

        data_value = self.broker.get_value([self.data])

        if self.p.use_target_value:
            print('%04d - %s - data value %.2f' %
                  (len(self), dt.isoformat(), data_value))

        elif self.p.use_target_percent:
            port_perc = data_value / portfolio_value
            print('%04d - %s - data percent %.2f' %
                  (len(self), dt.isoformat(), port_perc))

        if self.order:
            return  # pending order execution

        size = dt.day
        if (dt.month % 2) == 0:
            size = 31 - size

        if self.p.use_target_size:
            target = size
            print('%04d - %s - Order Target Size: %02d' %
                  (len(self), dt.isoformat(), size))

            self.order = self.order_target_size(target=size)

        elif self.p.use_target_value:
            value = size * 1000

            print('%04d - %s - Order Target Value: %.2f' %
                  (len(self), dt.isoformat(), value))

            self.order = self.order_target_value(target=value)

        elif self.p.use_target_percent:
            percent = size / 100.0

            print('%04d - %s - Order Target Percent: %.2f' %
                  (len(self), dt.isoformat(), percent))

            self.order = self.order_target_percent(target=percent)

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

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

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

    # data
    data = bt.feeds.YahooFinanceCSVData(dataname=args.data, **dkwargs)
    cerebro.adddata(data)

    # strategy
    cerebro.addstrategy(TheStrategy,
                        use_target_size=args.target_size,
                        use_target_value=args.target_value,
                        use_target_percent=args.target_percent)

    cerebro.run()

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

        cerebro.plot(**pkwargs)

def parse_args(pargs=None):

    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description='Sample for Order Target')

    parser.add_argument('--data', required=False,
                        default='../../datas/yhoo-1996-2015.txt',
                        help='Specific 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('--cash', required=False, action='store',
                        type=float, default=1000000,
                        help='Ending date in YYYY-MM-DD format')

    pgroup = parser.add_mutually_exclusive_group(required=True)

    pgroup.add_argument('--target-size', required=False, action='store_true',
                        help=('Use order_target_size'))

    pgroup.add_argument('--target-value', required=False, action='store_true',
                        help=('Use order_target_value'))

    pgroup.add_argument('--target-percent', required=False,
                        action='store_true',
                        help=('Use order_target_percent'))

    # Plot options
    parser.add_argument('--plot', '-p', nargs='?', required=False,
                        metavar='kwargs', const=True,
                        help=('Plot the read data applying any kwargs passed\n'
                              '\n'
                              'For example:\n'
                              '\n'
                              '  --plot style="candle" (to plot candles)\n'))

    if pargs is not None:
        return parser.parse_args(pargs)

    return parser.parse_args()

if __name__ == '__main__':
    runstrat()

OCO 订单

原文:www.backtrader.com/docu/order-creation-execution/oco/oco/

版本1.9.34.116添加了OCO(也称为One Cancel Others)到回测工具中。

注意

这仅在回测中实现,尚未为实时经纪人实现

注意

更新至版本1.9.36.116。交互经纪人支持StopTrailStopTrailLimitOCO

  • OCO总是将组中的第 1 个订单指定为参数oco
  • StopTrailLimit: 经纪人模拟和IB经纪人具有相同的行为。指定:price作为初始止损触发价格(还要指定trailamount),然后plimi作为初始限价。两者之间的差异将确定limitoffset(限价与止损触发价格之间的距离)

使用模式尽量保持用户友好。因此,如果策略中的逻辑决定发出订单,使用OCO可以这样做:

代码语言:javascript
复制
def next(self):
    ...
    o1 = self.buy(...)
    ...
    o2 = self.buy(..., oco=o1)
    ...
    o3 = self.buy(..., oco=o1)  # or even oco=o2, o2 is already in o1 group

简单。第 1 个订单o1将成为组长。o2o3通过指定o1oco命名参数成为OCO 组的一部分。请注意代码片段中的注释指出,o3也可以通过指定o2(已经是组的一部分)成为组的一部分

组成后将发生以下情况:

  • 如果组中的任何订单被执行、取消或到期,其他订单将被取消

下面的示例展示了OCO概念的运用。一个带有绘图的标准执行:

代码语言:javascript
复制
$ ./oco.py --broker cash=50000 --plot

注意

现金增加到50000,因为资产达到4000的值,3 个1个项目的订单至少需要12000货币单位(经纪人的默认值为10000

使用以下图表。

image
image

实际上并没有提供太多信息(这是一个标准的SMA Crossover策略)。示例执行以下操作:

  • 当快速SMA向上穿越慢速SMA时,将发出 3 个订单
  • order1是一个Limit订单,将在limdays天(策略的参数)内到期,限价为close价格减少的百分比
  • order2是一个Limit订单,具有更长的到期时间和更低的限价。
  • order3是一个Limit订单,进一步降低了限价

因此,order2order3的执行不会发生,因为:

  • order1将首先执行,这应该触发其他订单的取消

  • order1将到期,这将触发其他订单的取消

系统保留 3 个订单的ref标识符,并且只有在notify_order中看到这三个ref标识符为CompletedCancelledMarginExpired时才会发出新的buy订单

退出只需在持有头寸一段时间后进行。

为了尝试跟踪实际执行情况,会产生文本输出。其中一些内容:

代码语言:javascript
复制
2005-01-28: Oref 1 / Buy at 2941.11055
2005-01-28: Oref 2 / Buy at 2896.7722
2005-01-28: Oref 3 / Buy at 2822.87495
2005-01-31: Order ref: 1 / Type Buy / Status Submitted
2005-01-31: Order ref: 2 / Type Buy / Status Submitted
2005-01-31: Order ref: 3 / Type Buy / Status Submitted
2005-01-31: Order ref: 1 / Type Buy / Status Accepted
2005-01-31: Order ref: 2 / Type Buy / Status Accepted
2005-01-31: Order ref: 3 / Type Buy / Status Accepted
2005-02-01: Order ref: 1 / Type Buy / Status Expired
2005-02-01: Order ref: 3 / Type Buy / Status Canceled
2005-02-01: Order ref: 2 / Type Buy / Status Canceled
...
2006-06-23: Oref 49 / Buy at 3532.39925
2006-06-23: Oref 50 / Buy at 3479.147
2006-06-23: Oref 51 / Buy at 3390.39325
2006-06-26: Order ref: 49 / Type Buy / Status Submitted
2006-06-26: Order ref: 50 / Type Buy / Status Submitted
2006-06-26: Order ref: 51 / Type Buy / Status Submitted
2006-06-26: Order ref: 49 / Type Buy / Status Accepted
2006-06-26: Order ref: 50 / Type Buy / Status Accepted
2006-06-26: Order ref: 51 / Type Buy / Status Accepted
2006-06-26: Order ref: 49 / Type Buy / Status Completed
2006-06-26: Order ref: 51 / Type Buy / Status Canceled
2006-06-26: Order ref: 50 / Type Buy / Status Canceled
...
2006-11-10: Order ref: 61 / Type Buy / Status Canceled
2006-12-11: Oref 63 / Buy at 4032.62555
2006-12-11: Oref 64 / Buy at 3971.8322
2006-12-11: Oref 65 / Buy at 3870.50995
2006-12-12: Order ref: 63 / Type Buy / Status Submitted
2006-12-12: Order ref: 64 / Type Buy / Status Submitted
2006-12-12: Order ref: 65 / Type Buy / Status Submitted
2006-12-12: Order ref: 63 / Type Buy / Status Accepted
2006-12-12: Order ref: 64 / Type Buy / Status Accepted
2006-12-12: Order ref: 65 / Type Buy / Status Accepted
2006-12-15: Order ref: 63 / Type Buy / Status Expired
2006-12-15: Order ref: 65 / Type Buy / Status Canceled
2006-12-15: Order ref: 64 / Type Buy / Status Canceled

出现了以下情况:

  • 第 1 个订单批次被下发。订单 1 到期,而 2 和 3 被取消。正如预期的那样。
  • 几个月后,又下发了另一批 3 个订单。在这种情况下,订单 49 被标记为已完成,而 50 和 51 则立即被取消。
  • 最后一个批次与第 1 个批次完全相同

现在让我们来检查一下没有OCO时的行为:

代码语言:javascript
复制
$ ./oco.py --strat do_oco=False --broker cash=50000

2005-01-28: Oref 1 / Buy at 2941.11055
2005-01-28: Oref 2 / Buy at 2896.7722
2005-01-28: Oref 3 / Buy at 2822.87495
2005-01-31: Order ref: 1 / Type Buy / Status Submitted
2005-01-31: Order ref: 2 / Type Buy / Status Submitted
2005-01-31: Order ref: 3 / Type Buy / Status Submitted
2005-01-31: Order ref: 1 / Type Buy / Status Accepted
2005-01-31: Order ref: 2 / Type Buy / Status Accepted
2005-01-31: Order ref: 3 / Type Buy / Status Accepted
2005-02-01: Order ref: 1 / Type Buy / Status Expired

这就是全部,其实并不多(没有顺序执行,也不需要太多图表)

  • 订单批次被下发
  • 订单 1 到期了,但是因为策略已经设置了参数do_oco=False,订单 2 和 3 没有被纳入OCO
  • 因此,订单 2 和 3 并没有被取消,而且由于默认到期时间差为1000天后,根据样本数据(2 年的数据)它们永远不会到期。
  • 系统从未下发第 2 批订单。

使用示例

代码语言:javascript
复制
$ ./oco.py --help
usage: oco.py [-h] [--data0 DATA0] [--fromdate FROMDATE] [--todate TODATE]
              [--cerebro kwargs] [--broker kwargs] [--sizer kwargs]
              [--strat kwargs] [--plot [kwargs]]

Sample Skeleton

optional arguments:
  -h, --help           show this help message and exit
  --data0 DATA0        Data to read in (default:
                       ../../datas/2005-2006-day-001.txt)
  --fromdate FROMDATE  Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
  --todate TODATE      Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
  --cerebro kwargs     kwargs in key=value format (default: )
  --broker kwargs      kwargs in key=value format (default: )
  --sizer kwargs       kwargs in key=value format (default: )
  --strat kwargs       kwargs in key=value format (default: )
  --plot [kwargs]      kwargs in key=value format (default: )

代码示例

代码语言:javascript
复制
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,
        switchp1p2=False,  # switch prices of order1 and order2
        oco1oco2=False,  # False - use order1 as oco for order3, else order2
        do_oco=True,  # use oco or not
    )

    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()

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

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

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

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

                o1 = self.buy(exectype=bt.Order.Limit, price=p1,
                              valid=datetime.timedelta(self.p.limdays))

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

                oco2 = o1 if self.p.do_oco else None
                o2 = self.buy(exectype=bt.Order.Limit, price=p2,
                              valid=datetime.timedelta(self.p.limdays2),
                              oco=oco2)

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

                if self.p.do_oco:
                    oco3 = o1 if not self.p.oco1oco2 else oco2
                else:
                    oco3 = None

                o3 = self.buy(exectype=bt.Order.Limit, price=p3,
                              valid=datetime.timedelta(self.p.limdays2),
                              oco=oco3)

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

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

        else:  # in the market
            if (len(self) - self.holdstart) >= self.p.hold:
                self.close()

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()

括号订单

原文:www.backtrader.com/docu/order-creation-execution/bracket/bracket/

发布 1.9.37.116 添加了 bracket 订单,提供了由回测经纪人支持的非常广泛的订单范围(MarketLimitCloseStopStopLimitStopTrailStopTrailLimitOCO

注意

这是为了 回测交互式经纪人 存储而实现的

bracket 订单不是单个订单,而实际上是由 3 个订单组成的。让我们考虑长侧

  • 一个主要的 buy 订单,通常设置为 LimitStopLimit 订单
  • 一个低侧 sell 订单,通常设置为 Stop 订单以限制损失
  • 高侧 sell 订单,通常设置为 Limit 订单以获利

对应的 sell 和 2 x buy 订单用于短侧。

低/高侧订单实际上会围绕主要侧订单创建一个括号。

为了加入一些逻辑,以下规则适用:

  • 3 个订单一起提交,以避免它们中的任何一个独立触发
  • 低/高侧订单被标记为主要侧的子订单
  • 子订单在主要侧执行前不活动
  • 取消主要侧会同时取消低侧和高侧
  • 主要侧的执行会激活低侧和高侧
  • 一旦激活
    • 任何一侧订单的执行或取消都会自动取消另一侧订单

使用模式

创建括号订单集的两种可能性

  • 单独发出 3 个订单
  • 手动发出 3 个订单

单独发出括号

backtraderStrategy 中提供了两种控制 bracket 订单的新方法。

  • buy_bracketsell_bracket

注意

签名和信息如下或在 Strategy 参考部分中。

通过一个单一语句完成 3 个订单的完整集合。例如:

代码语言:javascript
复制
brackets = self.buy_bracket(limitprice=14.00, price=13.50, stopprice=13.00)

注意 stoppricelimitprice 如何围绕主要的 price 设置。

这应该足够了。实际目标 data 将是 data0,而 size 将由默认大小器自动确定。当然,可以指定许多其他参数以对执行进行精细控制。

返回值为:

  • 一个包含这 3 个订单的列表,顺序如下:[main, stop, limit]

因为在发出 sell_bracket 订单时,低侧和高侧会被调转,参数按照约定命名为 stoplimit

  • stop 旨在停止损失(长操作的低侧和短操作的高侧)
  • limit 旨在获取利润(长操作的高侧和短操作的低侧)

手动发出括号

这涉及生成 3 个订单并玩弄 transmitparent 参数。规则如下:

  • 主要侧订单必须首先创建并具有 transmit=False
  • 低/高侧订单必须具有 parent=main_side_order
  • 要创建的第 1 个低/高侧订单必须具有 transmit=False
  • 最后创建的订单(无论是低端还是高端)设置transmit=True

执行上面单个命令的实际示例:

代码语言:javascript
复制
mainside = self.buy(price=13.50, exectype=bt.Order.Limit, transmit=False)
lowside  = self.sell(price=13.00, size=mainside.size, exectype=bt.Order.Stop,
                     transmit=False, parent=mainside)
highside = self.sell(price=14.00, size=mainside.size, exectype=bt.Order.Limit,
                     transmit=True, parent=mainside)

还有很多工作要做的地方:

  • 跟踪mainside订单以指示它是其他订单的父订单
  • 控制transmit以确保只有最后一个订单触发联合传输
  • 指定执行类型
  • 为低端和高端指定size 因为size必须相同。如果未手动指定参数并且最终用户已引入调整器,则调整器实际上可以指示订单的不同值。这就是为什么在为mainside订单设置后必须手动将其添加到调用中的原因。

其中的一个样本

从下面的示例运行产生了这个输出(为简洁起见截断)

代码语言:javascript
复制
$ ./bracket.py --plot

2005-01-28: Oref 1 / Buy at 2941.11055
2005-01-28: Oref 2 / Sell Stop at 2881.99275
2005-01-28: Oref 3 / Sell Limit at 3000.22835
2005-01-31: Order ref: 1 / Type Buy / Status Submitted
2005-01-31: Order ref: 2 / Type Sell / Status Submitted
2005-01-31: Order ref: 3 / Type Sell / Status Submitted
2005-01-31: Order ref: 1 / Type Buy / Status Accepted
2005-01-31: Order ref: 2 / Type Sell / Status Accepted
2005-01-31: Order ref: 3 / Type Sell / Status Accepted
2005-02-01: Order ref: 1 / Type Buy / Status Expired
2005-02-01: Order ref: 2 / Type Sell / Status Canceled
2005-02-01: Order ref: 3 / Type Sell / Status Canceled
...
2005-08-11: Oref 16 / Buy at 3337.3892
2005-08-11: Oref 17 / Sell Stop at 3270.306
2005-08-11: Oref 18 / Sell Limit at 3404.4724
2005-08-12: Order ref: 16 / Type Buy / Status Submitted
2005-08-12: Order ref: 17 / Type Sell / Status Submitted
2005-08-12: Order ref: 18 / Type Sell / Status Submitted
2005-08-12: Order ref: 16 / Type Buy / Status Accepted
2005-08-12: Order ref: 17 / Type Sell / Status Accepted
2005-08-12: Order ref: 18 / Type Sell / Status Accepted
2005-08-12: Order ref: 16 / Type Buy / Status Completed
2005-08-18: Order ref: 17 / Type Sell / Status Completed
2005-08-18: Order ref: 18 / Type Sell / Status Canceled
...
2005-09-26: Oref 22 / Buy at 3383.92535
2005-09-26: Oref 23 / Sell Stop at 3315.90675
2005-09-26: Oref 24 / Sell Limit at 3451.94395
2005-09-27: Order ref: 22 / Type Buy / Status Submitted
2005-09-27: Order ref: 23 / Type Sell / Status Submitted
2005-09-27: Order ref: 24 / Type Sell / Status Submitted
2005-09-27: Order ref: 22 / Type Buy / Status Accepted
2005-09-27: Order ref: 23 / Type Sell / Status Accepted
2005-09-27: Order ref: 24 / Type Sell / Status Accepted
2005-09-27: Order ref: 22 / Type Buy / Status Completed
2005-10-04: Order ref: 24 / Type Sell / Status Completed
2005-10-04: Order ref: 23 / Type Sell / Status Canceled
...

显示了 3 种不同的结果:

  • 在第一种情况下,主要的副作用已经过期,这自动取消了其他两个
  • 在第二种情况下,主要的副作用已经完成,低(在购买情况下停止)被执行以限制损失
  • 在第三种情况下,主要的副作用已经完成,而高侧(限制)被执行 这可以注意到,因为已完成的 ids 是2224端订单最后被发出,这意味着未执行的低端订单的 id 为 23。

视觉上

image
image

可以立即看到,失败的交易围绕着相同的值以及成功的交易,这就是背书的目的。控制两侧。

正在运行的示例手动发出了 3 个订单,但可以告诉它使用buy_bracket。让我们看看输出:

代码语言:javascript
复制
$ ./bracket.py --strat usebracket=True

具有相同结果

image
image

一些参考资料

查看新的buy_bracketsell_bracket方法

代码语言:javascript
复制
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):
  '''
 Create a bracket order group (low side - buy order - high side). The
 default behavior is as follows:

 - Issue a **buy** order with execution ``Limit`

 - Issue a *low side* bracket **sell** order with execution ``Stop``

 - Issue a *high side* bracket **sell** order with execution
 ``Limit``.

 See below for the different parameters

 - ``data`` (default: ``None``)

 For which data the order has to be created. If ``None`` then the
 first data in the system, ``self.datas[0] or self.data0`` (aka
 ``self.data``) will be used

 - ``size`` (default: ``None``)

 Size to use (positive) of units of data to use for the order.

 If ``None`` the ``sizer`` instance retrieved via ``getsizer`` will
 be used to determine the size.

 **Note**: The same size is applied to all 3 orders of the bracket

 - ``price`` (default: ``None``)

 Price to use (live brokers may place restrictions on the actual
 format if it does not comply to minimum tick size requirements)

 ``None`` is valid for ``Market`` and ``Close`` orders (the market
 determines the price)

 For ``Limit``, ``Stop`` and ``StopLimit`` orders this value
 determines the trigger point (in the case of ``Limit`` the trigger
 is obviously at which price the order should be matched)

 - ``plimit`` (default: ``None``)

 Only applicable to ``StopLimit`` orders. This is the price at which
 to set the implicit *Limit* order, once the *Stop* has been
 triggered (for which ``price`` has been used)

 - ``trailamount`` (default: ``None``)

 If the order type is StopTrail or StopTrailLimit, this is an
 absolute amount which determines the distance to the price (below
 for a Sell order and above for a buy order) to keep the trailing
 stop

 - ``trailpercent`` (default: ``None``)

 If the order type is StopTrail or StopTrailLimit, this is a
 percentage amount which determines the distance to the price (below
 for a Sell order and above for a buy order) to keep the trailing
 stop (if ``trailamount`` is also specified it will be used)

 - ``exectype`` (default: ``bt.Order.Limit``)

 Possible values: (see the documentation for the method ``buy``

 - ``valid`` (default: ``None``)

 Possible values: (see the documentation for the method ``buy``

 - ``tradeid`` (default: ``0``)

 Possible values: (see the documentation for the method ``buy``

 - ``oargs`` (default: ``{}``)

 Specific keyword arguments (in a ``dict``) to pass to the main side
 order. Arguments from the default ``**kwargs`` will be applied on
 top of this.

 - ``**kwargs``: additional broker implementations may support extra
 parameters. ``backtrader`` will pass the *kwargs* down to the
 created order objects

 Possible values: (see the documentation for the method ``buy``

 **Note**: this ``kwargs`` will be applied to the 3 orders of a
 bracket. See below for specific keyword arguments for the low and
 high side orders

 - ``stopprice`` (default: ``None``)

 Specific price for the *low side* stop order

 - ``stopexec`` (default: ``bt.Order.Stop``)

 Specific execution type for the *low side* order

 - ``stopargs`` (default: ``{}``)

 Specific keyword arguments (in a ``dict``) to pass to the low side
 order. Arguments from the default ``**kwargs`` will be applied on
 top of this.

 - ``limitprice`` (default: ``None``)

 Specific price for the *high side* stop order

 - ``stopexec`` (default: ``bt.Order.Limit``)

 Specific execution type for the *high side* order

 - ``limitargs`` (default: ``{}``)

 Specific keyword arguments (in a ``dict``) to pass to the high side
 order. Arguments from the default ``**kwargs`` will be applied on
 top of this.

 Returns:
 - A list containing the 3 bracket orders [order, stop side, limit
 side]
 '''

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):
  '''
 Create a bracket order group (low side - buy order - high side). The
 default behavior is as follows:

 - Issue a **sell** order with execution ``Limit`

 - Issue a *high side* bracket **buy** order with execution ``Stop``

 - Issue a *low side* bracket **buy** order with execution ``Limit``.

 See ``bracket_buy`` for the meaning of the parameters

 Returns:
 - A list containing the 3 bracket orders [order, stop side, limit
 side]
 '''

样本用法

代码语言:javascript
复制
$ ./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

optional arguments:
  -h, --help           show this help message and exit
  --data0 DATA0        Data to read in (default:
                       ../../datas/2005-2006-day-001.txt)
  --fromdate FROMDATE  Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
  --todate TODATE      Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
  --cerebro kwargs     kwargs in key=value format (default: )
  --broker kwargs      kwargs in key=value format (default: )
  --sizer kwargs       kwargs in key=value format (default: )
  --strat kwargs       kwargs in key=value format (default: )
  --plot [kwargs]      kwargs in key=value format (default: )

样本代码

代码语言:javascript
复制
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()
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-05-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 订单
  • 订单。
    • 订单创建。
      • 订单通知
        • 订单状态值
          • 参考:订单和相关类
          • 订单管理和执行
            • 订单管理
              • 订单执行逻辑
                • 市场
                • Close
                • Limit
                • 停止
                • 停止限价
              • 一些样本
                • 执行类型:市价
                • 执行类型:关闭
                • 执行类型:限价
                • 执行类型:带有效期的限价
                • 执行类型:Stop
                • 执行类型:StopLimit
                • 测试脚本执行
                • 完整代码
            • 目标订单
              • 示例
                • order_target_size
                • order_target_value
                • order_target_percent
              • 示例用法
                • 示例代码
                • OCO 订单
                  • 使用示例
                    • 代码示例
                    • 括号订单
                      • 使用模式
                        • 单独发出括号
                          • 手动发出括号
                            • 其中的一个样本
                              • 一些参考资料
                                • 样本用法
                                  • 样本代码
                                  领券
                                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档