目标订单
原文:
www.backtrader.com/blog/posts/2016-09-02-target-orders/target-orders/
直到版本 1.8.10.96,通过Strategy方法:买入和卖出,在backtrader上实现了智能的投注。一切都是关于向方程中添加一个 Sizer,它负责赌注的大小。
Sizer无法决定操作是买入还是卖出。这意味着需要引入一个新概念,在其中添加一个小的智能层来做出这样的决定。
这就是Strategy
中的order_target_xxx
方法家族发挥作用的地方。受到zipline
中的方法的启发,这些方法提供了简单指定最终目标的机会,目标可以是:
注意
方法的参考可以在策略参考中找到。简而言之,这些方法使用与buy
和sell
相同的签名,除了参数size
被参数target
替换。
在这种情况下,重点是指定最终目标,方法决定操作是买入还是卖出。相同的逻辑适用于这 3 种方法。让我们从order_target_size
开始
- 如果目标大于仓位,则会发出买入指令,差额为
目标 - 仓位大小
示例:
- 仓位:
0
,目标:7
-> 买入(size=7 - 0) -> 买入(size=7) - 仓位:
3
,目标:7
-> 买入(size=7 - 3) -> 买入(size=4) - 仓位:
-3
,目标:7
-> 买入(size=7 - -3) -> 买入(size=10) - 仓位:
-3
,目标:-2
-> 买入(size=-2 - -3) -> 买入(size=1)
- 如果目标小于仓位,则会发出卖出指令,差额为
仓位大小 - 目标
示例:
- 仓位:
0
,目标:-7
-> 卖出(size=0 - -7) -> 卖出(size=7) - 仓位:
3
,目标:-7
-> 卖出(size=3 - -7) -> 卖出(size=10) - 仓位:
-3
,目标:-7
-> 卖出(size=-3 - -7) -> 卖出(size=4) - 仓位:
3
,目标:2
-> 卖出(size=3 - 2) -> 卖出(size=1)
当使用order_target_value
来设置目标值时,投资组合中资产的当前价值和仓位大小都会被考虑在内,以决定最终的基础操作。推理如下:
- 如果仓位大小为负值(空头)且目标价值必须大于当前价值,则意味着:卖出更多
因此,逻辑如下:
- 如果
目标 > 值
且size >=0
-> 买入 - 如果
目标 > 值
且size < 0
-> 卖出 - 如果
目标 < 值
且size >= 0
-> 卖出 - 如果
目标 < 值
且size\* < 0
-> 买入
order_target_percent
的逻辑与order_target_value
相同。该方法简单地考虑了投资组合的当前总价值,以确定资产的目标价值。
示例
backtrader尝试为每个新功能提供一个示例,这不例外。没有花里胡哨,只是为了测试结果是否符合预期。这个示例位于 samples 中的order_target
目录下。
示例中的逻辑相当愚蠢,只是用于测试:
- 在奇数月(一月,三月,…)中,使用日作为目标(对于
order_target_value
,将日乘以1000
)
这模拟了一个递增的目标。 - 在偶数月(二月,四月,…)中,使用
31 - 日
作为目标
这模拟了一个递减的目标。
order_target_size
让我们看看一月和二月发生了什么。
$ ./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 ...
在一月,目标从年初的第 1 个交易日开始为3
,并逐渐增加。持仓大小最初从0
增加到3
,然后以1
的增量移动。
结束一月时,最后的order_target为31
,当进入二月的第 1 天时报告了该持仓大小,当新的目标大小请求为30
时,并随着持仓以1
的递减变化。
order_target_value
预计目标值会有类似的行为。
$ ./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
。
以任何其他方式进行将意味着在欺骗自己。
下一个目标值和最终值更接近:4000
和3938.17
。
当转变为二月时,目标价值开始从31000
减少到30000
和29000
。数据值也随之从30580.00
减少到30706.56
,然后到28633.44
。等待:
30580
->30706.56
是一个正向变化。
确实。在这种情况下,计算出的目标值的大小遇到了将值提升到30706.56
的开盘价。
如何避免这种影响:
- 该示例使用
Market
类型执行订单,这种效果无法避免。 - 方法
order_target_xxx
允许指定执行类型和价格。
可以指定Limit
作为执行订单,并让价格为close价格(如果没有提供其他价格,则由方法选择),甚至提供特定定价。
order_target_value
在这种情况下,它只是当前投资组合价值的一个百分比。
$ ./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 ...
信息已更改,以查看投资组合中数据代表的%
。
示例用法
$ ./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)
示例代码
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()
将期货滚动
原文:
www.backtrader.com/blog/posts/2016-08-31-rolling-over-futures/rolling-futures-over/
并非每个提供商都为可以交易的工具提供连续未来数据。有时,提供的数据是仍然有效的到期日期的数据,即:仍在交易的数据
当涉及到回测时,这并不是很有帮助,因为数据分散在几个不同的工具中,而且还… 时间上重叠。
能够将那些过去的工具数据正确地连接成连续流可以减轻痛苦。问题在于:
- 没有一条法律规定如何最好地将不同到期日期的期货合并成一个连续的期货
一些文献,由SierraChart提供:
- 我的链接
滚动数据源
backtrader在 1.8.10.99 版本中添加了一个功能,可以将不同到期日期的期货数据合并成一个连续的期货:
import backtrader as bt cerebro = bt.Cerebro() data0 = bt.feeds.MyFeed(dataname='Expiry0') data1 = bt.feeds.MyFeed(dataname='Expiry1') ... dataN = bt.feeds.MyFeed(dataname='ExpiryN') drollover = cerebro.rolloverdata(data0, data1, ..., dataN, name='MyRoll', **kwargs) cerebro.run()
注
下面解释了可能的\*\*kwargs
也可以通过直接访问RollOver
数据源来完成(如果进行了子类化,则会有所帮助):
import backtrader as bt cerebro = bt.Cerebro() data0 = bt.feeds.MyFeed(dataname='Expiry0') data1 = bt.feeds.MyFeed(dataname='Expiry1') ... dataN = bt.feeds.MyFeed(dataname='ExpiryN') drollover = bt.feeds.RollOver(data0, data1, ..., dataN, dataname='MyRoll', **kwargs) cerebro.adddata(drollover) cerebro.run()
注
下面解释了可能的\*\*kwargs
注
使用RollOver
时,使用dataname
分配名称。这是所有数据源用于传递名称/标记的标准参数。在这种情况下,它被重用以为所有滚动期货分配一个公共名称。
对于cerebro.rolloverdata
,名称使用name
分配给一个数据源,这已经是该方法的一个命名参数
底线:
- 数据源通常创建,但未添加到
cerebro
- 这些数据源作为输入提供给
bt.feeds.RollOver
也给出了一个dataname
,主要用于识别目的。 - 然后将这个滚动数据源添加到
cerebro
中
滚动的选项
提供了两个参数来控制滚动过程
checkdate
(默认值:None
)这必须是一个callable,具有以下签名:
checkdate(dt, d):`
- 其中:
dt
是一个datetime.datetime
对象d
是当前活跃期货的数据源
- 预期的返回值:
True
:只要可调用返回这个,就可以切换到下一个未来
如果某种商品在三月的第三个星期五到期,则checkdate
可以在到期发生的整个周返回True
。False
:到期无法发生
checkcondition
(默认值:None
)注意:只有当checkdate
返回True
时才会调用此函数如果是None
,这将内部求值为True
(执行滚动)否则,这必须是一个callable,具有以下签名:
checkcondition(d0, d1)`
- 其中:
d0
是当前活跃期货的数据源d1
是下一个到期的数据源
- 预期的返回值:
True
:滚动到下一个未来
继续使用checkdate
的示例,这可以说明如果d0
的volume已经小于d1
的 volume,则可以进行滚动False
:到期无法发生
子类化RollOver
如果指定可调用对象不够,总是可以通过子类化RollOver
。要子类化的方法:
def _checkdate(self, dt, d):
与上面同名参数的签名相匹配。预期的返回值也是相同的。def _checkcondition(self, d0, d1)
与上面同名参数的签名相匹配。预期的返回值也是相同的。
BackTrader 中文文档(二十一)(2)https://developer.aliyun.com/article/1505417