BackTrader 中文文档(八)(1)https://developer.aliyun.com/article/1505277
执行类型:StopLimit
设置了比信号价格高 1%的停止价格。但限价设置在信号(收盘)价格的 0.5%以上,这可能被解释为:等待力量显现,但不要买入顶峰。等待下跌。
有效期限制为 20(日历)天
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))
输出图表。
命令行和输出:
$ ./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
中详细说明:
$ ./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
完整代码
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()
目标订单
直到版本1.8.10.96
,通过Strategy方法buy
和sell
在backtrader上实现了智能的投注。这一切都是关于在方程中添加一个Sizer
,负责赌注的大小。
Sizer无法决定操作是买入还是卖出。这意味着需要一个新概念,其中添加了一个小智能层来做出这样的决定。
这就是Strategy中的order_target_xxx
方法家族发挥作用的地方。受到zipline
中方法的启发,这些方法提供了简单指定最终target的机会,无论目标是什么:
size
-> 特定资产组合中的股票、合约数量value
-> 组合中资产的货币单位价值percent
-> 百分比(来自当前组合)资产在当前组合中的价值
注意
方法的参考可以在 Strategy 中找到。总结是,这些方法使用与buy
和sell
相同的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
来设定目标值时,会考虑组合中资产的当前value和position size,以决定最终的基础操作。推理如下:
- 如果position size为负(空头)且target value必须大于当前值,这意味着:卖出更多
因此,逻辑如下:
- 如果
target > value
且size >=0
-> 买入 - 如果
target > value
且size < 0
-> 卖出 - 如果
target < value
且size >= 0
-> 卖出 - 如果
target < value
且size < 0
-> 买入
order_target_percent
的逻辑与order_target_value
相同。该方法仅考虑组合的当前总价值,以确定资产的目标值。
示例
backtrader尝试为每个新功能提供一个示例,这不例外。没有花里胡哨,只是为了测试结果是否符合预期。这个示例在order_target
目录中。
示例中的逻辑相当愚蠢,只是用于测试:
- 在奇数月(一月,三月,…),使用日作为目标(对于
order_target_value
,将日乘以1000
)
这模仿了一个递增的目标。 - 在偶数月(二月,四月,…)使用
31 - day
作为目标。
这模仿了一个递减的目标。
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 ...
在一月,目标从第一交易日的3
开始增加。初始时,仓位大小从0
到3
,然后以1
的增量递增。
完成一月时,最后一个order_target为31
,当进入二月的第一天时,报告了该仓位大小,新的目标方向要求为30
,并随着仓位递减。
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
作为执行订单的方式,并让价格成为收盘价格(如果没有提供其他内容,则由该方法选择)或者甚至提供具体定价。
BackTrader 中文文档(八)(3)https://developer.aliyun.com/article/1505281