BackTrader 中文文档(二十三)(2)https://developer.aliyun.com/article/1505435
代码
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 class St(bt.Strategy): params = ( ('stakeperc', 10.0), ('opbreak', 10), ) def notify_order(self, order): print('-- NOTIFY ORDER BEGIN') print(order) print('-- NOTIFY ORDER END') print('-- ORDER REMSIZE:', order.executed.remsize) if order.status == order.Completed: print('++ ORDER COMPLETED at data.len:', len(order.data)) self.doop = -self.p.opbreak def __init__(self): pass def start(self): self.callcounter = 0 txtfields = list() txtfields.append('Len') txtfields.append('Datetime') txtfields.append('Open') txtfields.append('High') txtfields.append('Low') txtfields.append('Close') txtfields.append('Volume') txtfields.append('OpenInterest') print(','.join(txtfields)) self.doop = 0 def next(self): txtfields = list() txtfields.append('%04d' % len(self)) txtfields.append(self.data0.datetime.date(0).isoformat()) txtfields.append('%.2f' % self.data0.open[0]) txtfields.append('%.2f' % self.data0.high[0]) txtfields.append('%.2f' % self.data0.low[0]) txtfields.append('%.2f' % self.data0.close[0]) txtfields.append('%.2f' % self.data0.volume[0]) txtfields.append('%.2f' % self.data0.openinterest[0]) print(','.join(txtfields)) # Single order if self.doop == 0: if not self.position.size: stakevol = (self.data0.volume[0] * self.p.stakeperc) // 100 print('++ STAKE VOLUME:', stakevol) self.buy(size=stakevol) else: self.close() self.doop += 1 FILLERS = { 'FixedSize': bt.broker.filler.FixedSize, 'FixedBarPerc': bt.broker.filler.FixedBarPerc, 'BarPointPerc': bt.broker.filler.BarPointPerc, } def runstrat(): args = parse_args() datakwargs = dict() if args.fromdate: fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d') datakwargs['fromdate'] = fromdate if args.todate: fromdate = datetime.datetime.strptime(args.todate, '%Y-%m-%d') datakwargs['todate'] = todate data = bt.feeds.BacktraderCSVData(dataname=args.data, **datakwargs) cerebro = bt.Cerebro() cerebro.adddata(data) cerebro.broker.set_cash(args.cash) if args.filler is not None: fillerkwargs = dict() if args.filler_args is not None: fillerkwargs = eval('dict(' + args.filler_args + ')') filler = FILLERSargs.filler cerebro.broker.set_filler(filler) cerebro.addstrategy(St, stakeperc=args.stakeperc, opbreak=args.opbreak) cerebro.run() if args.plot: cerebro.plot(style='bar') def parse_args(): parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, description='Volume Filling Sample') parser.add_argument('--data', required=False, default='../../datas/2006-volume-day-001.txt', help='Data to be read in') parser.add_argument('--cash', required=False, action='store', default=500e6, type=float, help=('Starting cash')) parser.add_argument('--filler', required=False, action='store', default=None, choices=FILLERS.keys(), help=('Apply a volume filler for the execution')) parser.add_argument('--filler-args', required=False, action='store', default=None, help=('kwargs for the filler with format:\n' '\n' 'arg1=val1,arg2=val2...')) parser.add_argument('--stakeperc', required=False, action='store', type=float, default=10.0, help=('Percentage of 1st bar to use for stake')) parser.add_argument('--opbreak', required=False, action='store', type=int, default=10, help=('Bars to wait for new op after completing ' 'another')) 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', required=False, action='store_true', help=('Plot the result')) return parser.parse_args() if __name__ == '__main__': runstrat()
以步骤方式交易一天
原文:
www.backtrader.com/blog/posts/2016-07-13-day-in-steps/day-in-steps/
看起来世界上的某个地方存在一种可以总结如下的兴趣:
- 使用每日柱状图但使用开盘价引入订单
这来自于票证交流中的对话#105 Order execution logic with current day data和#101 Dynamic stake calculation
backtrader在处理每日柱状图时尽可能保持真实,并且在使用每日柱状图时适用以下前提:
- 当评估每日柱状图时,柱状图已经结束
这是有道理的,因为所有价格(open/high/low/close)组件都是已知的。当已知close
价格时允许在open
价格上采取行动似乎是不合逻辑的。
这个问题的明显解决方案是使用日内数据,在已知开盘价时进入。但是似乎日内数据并不那么普遍。
这就是在数据源中添加过滤器可以帮助的地方。一个过滤器:
- 将每日数据转换为类似日内数据的数据
碧海蓝天!!!好奇的读者会立即指出,例如从Minutes
到Days
的上采样是合乎逻辑且有效的,但是从Days
到Minutes
的下采样是不可能的。
而且百分百正确。下面呈现的过滤器不会尝试这样做,但是一个更加谦卑和简单的目标:
- 将每日柱状图分解为 2 部分
- 一个只有开盘价而没有成交量的柱状图
- 一个是正常的每日柱状图的副本
这仍然可以被视为一种合乎逻辑的方法:
- 看到开盘价时,交易员可以采取行动
- 订单在一天的其余时间匹配(实际上可能匹配也可能不匹配,取决于执行类型和价格限制)
下面呈现了完整的代码。让我们看一个使用255
个每日柱状图的众所周知的数据的示例运行:
$ ./daysteps.py --data ../../datas/2006-day-001.txt
输出:
Calls,Len Strat,Len Data,Datetime,Open,High,Low,Close,Volume,OpenInterest 0001,0001,0001,2006-01-02T23:59:59,3578.73,3578.73,3578.73,3578.73,0.00,0.00 - I could issue a buy order during the Opening 0002,0001,0001,2006-01-02T23:59:59,3578.73,3605.95,3578.73,3604.33,0.00,0.00 0003,0002,0002,2006-01-03T23:59:59,3604.08,3604.08,3604.08,3604.08,0.00,0.00 - I could issue a buy order during the Opening 0004,0002,0002,2006-01-03T23:59:59,3604.08,3638.42,3601.84,3614.34,0.00,0.00 0005,0003,0003,2006-01-04T23:59:59,3615.23,3615.23,3615.23,3615.23,0.00,0.00 - I could issue a buy order during the Opening 0006,0003,0003,2006-01-04T23:59:59,3615.23,3652.46,3615.23,3652.46,0.00,0.00 ... ... 0505,0253,0253,2006-12-27T23:59:59,4079.70,4079.70,4079.70,4079.70,0.00,0.00 - I could issue a buy order during the Opening 0506,0253,0253,2006-12-27T23:59:59,4079.70,4134.86,4079.70,4134.86,0.00,0.00 0507,0254,0254,2006-12-28T23:59:59,4137.44,4137.44,4137.44,4137.44,0.00,0.00 - I could issue a buy order during the Opening 0508,0254,0254,2006-12-28T23:59:59,4137.44,4142.06,4125.14,4130.66,0.00,0.00 0509,0255,0255,2006-12-29T23:59:59,4130.12,4130.12,4130.12,4130.12,0.00,0.00 - I could issue a buy order during the Opening 0510,0255,0255,2006-12-29T23:59:59,4130.12,4142.01,4119.94,4119.94,0.00,0.00
以下情况发生:
next
被调用:510 次
,即255 x 2
- 策略和数据的
len
总共达到了255
,这是预期的:数据只有这么多根柱状图 - 每当数据的
len
增加时,4 个价格组件具有相同的值,即open
价格
这里打印出一条备注,指示在这个开盘阶段可以采取行动,例如购买。
实际上:
- 每日数据源正在使用每天 2 步重播,在
open
和其余价格组件之间提供操作选项
过滤器将在下一个版本中添加到backtrader的默认分发中。
包括过滤器的示例代码。
from __future__ import (absolute_import, division, print_function, unicode_literals) import argparse from datetime import datetime, time import backtrader as bt class DayStepsFilter(object): def __init__(self, data): self.pendingbar = None def __call__(self, data): # Make a copy of the new bar and remove it from stream newbar = [data.lines[i][0] for i in range(data.size())] data.backwards() # remove the copied bar from stream openbar = newbar[:] # Make an open only bar o = newbar[data.Open] for field_idx in [data.High, data.Low, data.Close]: openbar[field_idx] = o # Nullify Volume/OpenInteres at the open openbar[data.Volume] = 0.0 openbar[data.OpenInterest] = 0.0 # Overwrite the new data bar with our pending data - except start point if self.pendingbar is not None: data._updatebar(self.pendingbar) self.pendingbar = newbar # update the pending bar to the new bar data._add2stack(openbar) # Add the openbar to the stack for processing return False # the length of the stream was not changed def last(self, data): '''Called when the data is no longer producing bars Can be called multiple times. It has the chance to (for example) produce extra bars''' if self.pendingbar is not None: data.backwards() # remove delivered open bar data._add2stack(self.pendingbar) # add remaining self.pendingbar = None # No further action return True # something delivered return False # nothing delivered here class St(bt.Strategy): params = () def __init__(self): pass def start(self): self.callcounter = 0 txtfields = list() txtfields.append('Calls') txtfields.append('Len Strat') txtfields.append('Len Data') txtfields.append('Datetime') txtfields.append('Open') txtfields.append('High') txtfields.append('Low') txtfields.append('Close') txtfields.append('Volume') txtfields.append('OpenInterest') print(','.join(txtfields)) self.lcontrol = 0 def next(self): self.callcounter += 1 txtfields = list() txtfields.append('%04d' % self.callcounter) txtfields.append('%04d' % len(self)) txtfields.append('%04d' % len(self.data0)) txtfields.append(self.data.datetime.datetime(0).isoformat()) txtfields.append('%.2f' % self.data0.open[0]) txtfields.append('%.2f' % self.data0.high[0]) txtfields.append('%.2f' % self.data0.low[0]) txtfields.append('%.2f' % self.data0.close[0]) txtfields.append('%.2f' % self.data0.volume[0]) txtfields.append('%.2f' % self.data0.openinterest[0]) print(','.join(txtfields)) if len(self.data) > self.lcontrol: print('- I could issue a buy order during the Opening') self.lcontrol = len(self.data) def runstrat(): args = parse_args() cerebro = bt.Cerebro() data = bt.feeds.BacktraderCSVData(dataname=args.data) data.addfilter(DayStepsFilter) cerebro.adddata(data) cerebro.addstrategy(St) cerebro.run(stdstats=False, runonce=False, preload=False) if args.plot: cerebro.plot(style='bar') def parse_args(): parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, description='Sample for pivot point and cross plotting') parser.add_argument('--data', required=False, default='../../datas/2005-2006-day-001.txt', help='Data to be read in') parser.add_argument('--plot', required=False, action='store_true', help=('Plot the result')) return parser.parse_args() if __name__ == '__main__': runstrat()
Visual Chart 实时数据/交易
原文:
www.backtrader.com/blog/posts/2016-07-12-visualchart-feed/visualchart-feed/
从版本 1.5.1.93 开始,backtrader 支持 Visual Chart 实时数据和实时交易。
需要的东西:
- Visual Chart 6(这个版本运行在 Windows 上)
comtypes
,具体来说是这个分支:github.com/mementum/comtypes
使用以下命令安装:pip install https://github.com/mementum/comtypes/archive/master.zip
Visual Chart 的 API 基于 COM,当前的comtypes
主分支不支持对VT_RECORD
的VT_ARRAYS
进行解包。而 Visual Chart 正是使用了这个。
Pull Request #104 已经提交但尚未集成。一旦集成,就可以使用主分支了。pytz
(可选但强烈建议)
在许多情况下,数据提供的内部SymbolInfo.TimeOffset
就足以返回市场时间的数据流(即使默认配置是 Visual Chart 中的LocalTime
)
如果您不知道什么是 Visual Chart 和/或其当前关联的经纪商 Esfera Capital,请访问以下网站:
初始声明:
- 如往常一样,在冒险之前测试,测试,测试 和 再次测试 一千次。
从这个软件中的 bugs,到您自己的软件中的 bug 以及处理意外情况的管理:任何事情都可能出错
关于此的一些说明:
- 数据提供非常好,并支持内置重采样。好处在于无需进行重采样。
- 数据流不支持 Seconds 分辨率。这不太好,但可以通过 backtrader 的内置重采样解决。
- 内置了回填功能
- 一些国际指数市场(在交易所
096
)具有奇怪的时区和市场偏移。
对此进行了一些工作,例如以预期的US/Eastern
时区提供096.DJI
。 - 数据提供了 continuous futures,非常方便拥有大量历史数据。
因此,可以向数据传递第二个参数,指示实际的交易资产。 - Good Til Date 订单的日期时间只能指定为 日期。时间 部分会被忽略。
- 没有直接的方法可以找到本地设备到数据服务器的偏移量,需要通过会话开始时的 实时数据点 进行启发式分析来找出这一点。
- 传递带有 时间 组件的 datetime(而不是默认的 00:00:00)似乎会在 COM API 中创建一个 时间过滤器。例如,如果您想要 Minute 数据,从 3 天前开始到 14:30,您可以这样做:
dt = datetime.now() - timedelta(days=3) dt.replace(hour=14, minute=30) vcstore.getdata(dataname='001ES', fromdate=dt)`
- 数据会一直跳过直到 14:30 不仅是 3 天前,而是以后每一天
因此,请只传递完整日期,即默认的时间部分不受影响。 - 经纪人 支持 Positions 的概念,但仅在它们是 open 时。 关于 Position 的最后事件(其 size 为 0)不会发送。
因此,Position 记账完全由 backtrader 完成。 - 经纪人 不报告佣金。
解决方法是在实例化经纪人时提供自己的CommissionInfo
派生类。 请参阅 backtader 文档以创建自己的类。 这相当容易。 Cancelled
与Expired
订单。 此区别不存在,需要启发式方法来尝试清除这种区别。
因此,只有Cancelled
将被报告。
一些额外的说明:
- 实时ticks 大部分情况下不被使用。 它们为backtrader目的产生大量不需要的信息。 在被backtrader完全断开连接之前,它们有两个主要目的。
- 查找符号是否存在。
- 计算到数据服务器的偏移量。
- 当然,价格信息是实时收集的,但是来自 DataSource 对象,它们同时提供历史数据。
尽可能多地记录并在通常的文档链接处提供。
从样本 vctest.pye
对 Visual Chart 和 Demo Broker 进行了一些运行。
首先:015ES
(EuroStoxx50 连续)重新采样为 1 分钟,并具有断开连接和重新连接:
$ ./vctest.py --data0 015ES --timeframe Minutes --compression 1 --fromdate 2016-07-12
输出:
-------------------------------------------------- Strategy Created -------------------------------------------------- Datetime, Open, High, Low, Close, Volume, OpenInterest, SMA ***** DATA NOTIF: CONNECTED ***** DATA NOTIF: DELAYED 0001, 2016-07-12T08:01:00.000000, 2871.0, 2872.0, 2869.0, 2872.0, 1915.0, 0.0, nan 0002, 2016-07-12T08:02:00.000000, 2872.0, 2872.0, 2870.0, 2871.0, 479.0, 0.0, nan 0003, 2016-07-12T08:03:00.000000, 2871.0, 2871.0, 2869.0, 2870.0, 518.0, 0.0, nan 0004, 2016-07-12T08:04:00.000000, 2870.0, 2871.0, 2870.0, 2871.0, 248.0, 0.0, nan 0005, 2016-07-12T08:05:00.000000, 2870.0, 2871.0, 2870.0, 2871.0, 234.0, 0.0, 2871.0 ... ... 0639, 2016-07-12T18:39:00.000000, 2932.0, 2933.0, 2932.0, 2932.0, 1108.0, 0.0, 2932.8 0640, 2016-07-12T18:40:00.000000, 2931.0, 2932.0, 2931.0, 2931.0, 65.0, 0.0, 2932.6 ***** DATA NOTIF: LIVE 0641, 2016-07-12T18:41:00.000000, 2932.0, 2932.0, 2930.0, 2930.0, 2093.0, 0.0, 2931.8 ***** STORE NOTIF: (u'VisualChart is Disconnected', -65520) ***** DATA NOTIF: CONNBROKEN ***** STORE NOTIF: (u'VisualChart is Connected', -65521) ***** DATA NOTIF: CONNECTED ***** DATA NOTIF: DELAYED 0642, 2016-07-12T18:42:00.000000, 2931.0, 2931.0, 2931.0, 2931.0, 137.0, 0.0, 2931.2 0643, 2016-07-12T18:43:00.000000, 2931.0, 2931.0, 2931.0, 2931.0, 432.0, 0.0, 2931.0 ... 0658, 2016-07-12T18:58:00.000000, 2929.0, 2929.0, 2929.0, 2929.0, 4.0, 0.0, 2930.0 0659, 2016-07-12T18:59:00.000000, 2929.0, 2930.0, 2929.0, 2930.0, 353.0, 0.0, 2930.0 ***** DATA NOTIF: LIVE 0660, 2016-07-12T19:00:00.000000, 2930.0, 2930.0, 2930.0, 2930.0, 376.0, 0.0, 2930.0 0661, 2016-07-12T19:01:00.000000, 2929.0, 2930.0, 2929.0, 2930.0, 35.0, 0.0, 2929.8
注意
执行环境安装了 pytz
。
注意
注意没有 --resample
:对于 Minutes
,重新采样是内置于 Visual Chart 中的。
最后一些交易,购买 015ES
的 2 个合约,单个 Market
订单,并将它们卖出为 1 个合约的 2 个订单。
执行:
$ ./vctest.py --data0 015ES --timeframe Minutes --compression 1 --fromdate 2016-07-12 2>&1 --broker --account accname --trade --stake 2
输出相当冗长,显示了订单执行的所有部分。 简要总结一下:
-------------------------------------------------- Strategy Created -------------------------------------------------- Datetime, Open, High, Low, Close, Volume, OpenInterest, SMA ***** DATA NOTIF: CONNECTED ***** DATA NOTIF: DELAYED 0001, 2016-07-12T08:01:00.000000, 2871.0, 2872.0, 2869.0, 2872.0, 1915.0, 0.0, nan ... 0709, 2016-07-12T19:50:00.000000, 2929.0, 2930.0, 2929.0, 2930.0, 11.0, 0.0, 2930.4 ***** DATA NOTIF: LIVE 0710, 2016-07-12T19:51:00.000000, 2930.0, 2930.0, 2929.0, 2929.0, 134.0, 0.0, 2930.0 -------------------------------------------------- ORDER BEGIN 2016-07-12 19:52:01.629000 Ref: 1 OrdType: 0 OrdType: Buy Status: 1 Status: Submitted Size: 2 Price: None Price Limit: None ExecType: 0 ExecType: Market CommInfo: <backtrader.brokers.vcbroker.VCCommInfo object at 0x000000001100CE10> End of Session: 736157.916655 Info: AutoOrderedDict() Broker: <backtrader.brokers.vcbroker.VCBroker object at 0x000000000475D400> Alive: True -------------------------------------------------- ORDER END -------------------------------------------------- ORDER BEGIN 2016-07-12 19:52:01.629000 Ref: 1 OrdType: 0 OrdType: Buy Status: 2 Status: Accepted Size: 2 Price: None Price Limit: None ExecType: 0 ExecType: Market CommInfo: <backtrader.brokers.vcbroker.VCCommInfo object at 0x000000001100CE10> End of Session: 736157.916655 Info: AutoOrderedDict() Broker: None Alive: True -------------------------------------------------- ORDER END -------------------------------------------------- ORDER BEGIN 2016-07-12 19:52:01.629000 Ref: 1 OrdType: 0 OrdType: Buy Status: 4 Status: Completed Size: 2 Price: None Price Limit: None ExecType: 0 ExecType: Market CommInfo: <backtrader.brokers.vcbroker.VCCommInfo object at 0x000000001100CE10> End of Session: 736157.916655 Info: AutoOrderedDict() Broker: None Alive: False -------------------------------------------------- ORDER END -------------------------------------------------- TRADE BEGIN 2016-07-12 19:52:01.629000 ref:1 data:<backtrader.feeds.vcdata.VCData object at 0x000000000475D9E8> tradeid:0 size:2.0 price:2930.0 value:5860.0 commission:0.0 pnl:0.0 pnlcomm:0.0 justopened:True isopen:True isclosed:0 baropen:710 dtopen:736157.74375 barclose:0 dtclose:0.0 barlen:0 historyon:False history:[] status:1 -------------------------------------------------- TRADE END ...
以下发生了:
- 数据正常接收。
- 发出了一个执行类型为
Market
的BUY
,数量为2
。
- 收到
Submitted
和Accepted
通知(仅显示了Submitted
)。 - 一连串的
Partial
执行(仅显示了 1 个)直到收到Completed
。
- 实际执行没有显示,但在
order.executed
下收到的order
实例中可用。 - 虽然没有显示,但发出了 2 x
Market
SELL
订单来撤销操作。
屏幕截图显示了在一个晚上使用015ES
(EuroStoxx 50)和034EURUS
(EUR.USD 外汇对)进行两次不同运行后,在 Visual Chart 中的日志。
示例可以做得更多,旨在彻底测试设施,如果可能的话,揭示任何粗糙的边缘。
BackTrader 中文文档(二十三)(4)https://developer.aliyun.com/article/1505438