目标订单#

在版本 1.8.10.96 之前,使用 backtrader 的*Strategy*方法: buysell 可以进行智能投注。这涉及到在公式中添加一个 Sizer ,它负责确定投注的大小。

然而, Sizer 无法确定操作是 buy 还是 sell 。这意味着需要引入一个小的智能层来做出这样的决策。

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

  • size -> 特定资产组合中的股票数量、合同数量

  • value -> 资产在组合中的货币单位价值

  • percent -> 当前资产在当前组合中的百分比值

注意

方法的参考文档可以在 策略 找到。总结起来,这些方法和 buysell 具有相同的“签名”,只是参数 size 被参数 target 替代。

这些方法只需要指定最终的 target ,方法会决定操作是 buy 还是 sell 。对于这3个方法都适用相同的逻辑。让我们从 order_target_size 开始。

  • 如果 target 大于现有仓位,则发出一个 buy 操作,差额为 target - position_size

    例子:

    • Pos: 0target : 7 -> buy(size=7 - 0) -> buy(size=7) - Pos: 3target : 7 -> buy(size=7 - 3) -> buy(size=4)

  • Pos: -3target : 7 -> buy(size=7 - -3) -> buy(size=10)

  • Pos: -3target : -2 -> buy(size=-2 - -3) -> buy(size=1)

  • 如果 target 小于位置,则发出 sell 命令,差值为 position_size - target

    示例:

    • Pos: 0target : -7 -> sell(size=0 - -7) -> sell(size=7)

    • Pos: 3target : -7 -> sell(size=3 - -7) -> sell(size=10)

    • Pos: -3target : -7 -> sell(size=-3 - -7) -> sell(size=4)

    • Pos: 3target : 2 -> sell(size=3 - 2) -> sell(size=1)

当使用 order_target_value 以特定的值为目标时,会考虑到资产在投资组合中的当前 value 和*position size ,以决定最终的基础操作。原因是:- 如果 仓位大小 为负数( 空头 )且*目标值 必须大于当前值,则意味着需要*卖出*更多

因此逻辑如下所示:

  • 如果 target > valuesize >=0 -> 买入

  • 如果 target > valuesize < 0 -> 卖出

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

  • 如果 target < valuesize * < 0 -> * 买入*

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

示例#

backtrader 试图为每个新功能提供一个示例,这并不例外。没有花哨的东西,只是为了测试结果是否如预期。此示例位于示例中的 order_target 目录下。

示例中的逻辑非常简单,仅用于测试:- 在 奇数月 (1月、3月…)中,使用*天数*作为目标(在 order_target_value 的情况下,将天数乘以 1000

这模拟了逐渐增加的*目标*

  • 偶数月 (2月、4月…)中,使用 31 - 天数 作为目标

    这模拟了逐渐减少的*目标*

订单目标大小#

让我们看看在 1月*和 2月*会发生什么。

$ ./order_target.py --target-size -- plot
0001 - 2005-01-03 - 仓位大小:     00 - 值 1000000.00
0001 - 2005-01-03 - 订单目标大小:03
0002 - 2005-01-04 - 仓位大小:     03 - 值 999994.39
0002 - 2005-01-04 - 订单目标大小:04
0003 - 2005-01-05 - 仓位大小:     04 - 值 999992.48
0003 - 2005-01-05 - 订单目标大小:05
0004 - 2005-01-06 - 仓位大小:     05 - 值 999988.79
...
0020 - 2005-01-31 - 仓位大小:     28 - 值 999968.70
0020 - 2005-01-31 - 订单目标大小:31
0021 - 2005-02-01 - 仓位大小:     31 - 值 999954.68
0021 - 2005-02-01 - 订单目标大小:30
0022 - 2005-02-02 - 仓位大小:     30 - 值 999979.65
0022 - 2005-02-02 - 订单目标大小:29
0023 - 2005-02-03 - 仓位大小:     29 - 值 999966.33
0023 - 2005-02-03 - 订单目标大小:28
...

1月*中, 目标 从年度的第一个交易日的 ``3`` 开始增加。而 仓位*大小一开始从 0 增加到 3 ,然后以 1 的增量递增。

在结束 1月*时,最后一个 订单目标 是 ``31`` ,并且在进入 2月 的第一天报告了该 仓位大小 ,当时新的 目标值*要求为 30 ,并且随着递减的仓位以减量 1 进行更改。


期望的 目标值 在这里具有相似的行为

$ ./order_target.py --target-value --plot
0001 - 2005-01-03 - 仓位规模:     00 - 价值 1000000.00
0001 - 2005-01-03 - 数据值 0.00
0001 - 2005-01-03 - 目标值订单: 3000.00
0002 - 2005-01-04 - 仓位规模:     78 - 价值 999854.14
0002 - 2005-01-04 - 数据值 2853.24
0002 - 2005-01-04 - 目标值订单: 4000.00
0003 - 2005-01-05 - 仓位规模:     109 - 价值 999801.68
0003 - 2005-01-05 - 数据值 3938.17
0003 - 2005-01-05 - 目标值订单: 5000.00
0004 - 2005-01-06 - 仓位规模:     138 - 价值 999699.57
...
0020 - 2005-01-31 - 仓位规模:     808 - 价值 999206.37
0020 - 2005-01-31 - 数据值 28449.68
0020 - 2005-01-31 - 目标值订单: 31000.00
0021 - 2005-02-01 - 仓位规模:     880 - 价值 998807.33
0021 - 2005-02-01 - 数据值 30580.00
0021 - 2005-02-01 - 目标值订单: 30000.00
0022 - 2005-02-02 - 仓位规模:     864 - 价值 999510.21
0022 - 2005-02-02 - 数据值 30706.56
0022 - 2005-02-02 - 目标值订单: 29000.00
0023 - 2005-02-03 - 仓位规模:     816 - 价值 999130.05
0023 - 2005-02-03 - 数据值 28633.44
0023 - 2005-02-03 - 目标值订单: 28000.00
...

多了一行额外的信息,告诉我们实际的 数据值 (在投资组合中)是多少。这有助于找出是否达到了 目标值

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

  • 该示例使用了每日收盘时的市场订单和最后一个可用价格来计算符合 目标值 的*目标规模*。

  • 执行使用了下一天的开盘价,这很可能不是前一天的收盘价。

以其他方式进行操作意味着自欺欺人。

下一个 目标值 和*最终值*更接近:分别是 40003938.17

当进入 二月*时, 目标值 从 ``31000`` 递减到 ``30000`` ,再到 ``29000`` 。而 数据值*则从 30580.00 递减到 30706.56 ,然后到 28633.44 。等一下:- 30580 -> 30706.56 是一个正的改变

确实。在这种情况下,计算出的 目标值 的*大小 与*开盘价 相遇,将值提高到 30706.56

如何避免这种影响:

  • 示例使用了“市场”类型的执行方式,这种影响是无法避免的

  • order_target_xxx 方法允许指定 执行类型 和*价格*。

    一个方法是指定执行订单为“限价”并让价格为 收盘价 (如果未提供其他价格)或者甚至提供特定定价

order_target_percent#

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

$ ./order_target.py --target-percent --plot
0001 - 2005-01-03 - 仓位大小:     00 - 价值 1000000.00
0001 - 2005-01-03 - 数据百分比 0.00
0001 - 2005-01-03 - 委托目标百分比:0.03
0002 - 2005-01-04 - 仓位大小:     785 - 价值 998532.05
0002 - 2005-01-04 - 数据百分比 0.03
0002 - 2005-01-04 - 委托目标百分比:0.04
0003 - 2005-01-05 - 仓位大小:     1091 - 价值 998007.44
0003 - 2005-01-05 - 数据百分比 0.04
0003 - 2005-01-05 - 委托目标百分比:0.05
0004 - 2005-01-06 - 仓位大小:     1381 - 价值 996985.64
...
0020 - 2005-01-31 - 仓位大小:     7985 - 价值 991966.28
0020 - 2005-01-31 - 数据百分比 0.28
0020 - 2005-01-31 - 委托目标百分比:0.31
0021 - 2005-02-01 - 仓位大小:     8733 - 价值 988008.94
0021 - 2005-02-01 - 数据百分比 0.31
0021 - 2005-02-01 - 委托目标百分比:0.30
0022 - 2005-02-02 - 仓位大小:     8530 - 价值 995005.45
0022 - 2005-02-02 - 数据百分比 0.30
0022 - 2005-02-02 - 委托目标百分比:0.29
0023 - 2005-02-03 - 仓位大小:     8120 - 价值 991240.75
0023 - 2005-02-03 - 数据百分比 0.29
0023 - 2005-02-03 - 委托目标百分比:0.28
...信息已更改以查看投资组合中数据所代表的 ``%`` 。

示例用法#

$ ./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]]

Order Target示例

可选参数:
  -h, --help            显示帮助信息并退出
  --data DATA           要读取的特定数据 (默认值:
                        ../../datas/yhoo-1996-2015.txt)
  --fromdate FROMDATE   开始日期,格式为YYYY-MM-DD (默认值:
                        2005-01-01)
  --todate TODATE       结束日期,格式为YYYY-MM-DD (默认值: 2006-12-31)
  --cash CASH           现金金额,格式为YYYY-MM-DD (默认值: 1000000)
  --target-size         使用order_target_size (默认值: False)
  --target-value        使用order_target_value (默认值: False)
  --target-percent      使用order_target_percent (默认值: False)
  --plot [kwargs], -p [kwargs]
                        绘制已读取的数据,并应用传递的任何kwargs
                        例如:--plot style="candle" (绘制蜡烛图)
                        (默认值: None)

示例代码 *** ** ** ** **

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