与 Visual Chart 的集成支持两者:
Visual Chart是完整的交易解决方案:
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
(可选,但真的很推荐)
确保每个数据都在市场时间内返回。
对大多数市场而言这是真实的,但有些市场确实是例外情况(全球指数
是一个很好的例子)
Visual Chart内部的时间管理及其与COM传递的时间的关系是复杂的,并且使用pytz
倾向于简化事情。
源代码包含一个完整的示例:
samples/vctest/vctest.py
示例无法涵盖每种可能的用例,但它试图提供广泛的见解,并应强调在使用回测模块或实时数据模块时没有真正的区别。
可以指出一件事:
data.LIVE
数据状态通知。
这可能是任何实时策略中需要考虑的事情。
存储库是实时数据源/交易支持的关键,为COM API 和数据源以及经纪人代理的需求之间提供一层适应性。
VCStore.getbroker(*args, **kwargs)
VCStore.getedata(*args, **kwargs)
在这种情况下,许多**kwargs
对于数据源都是常见的,如dataname
,fromdate
,todate
,sessionstart
,sessionend
,timeframe
,compression
数据可能提供其他参数。请查看下面的参考资料。
VCStore
将尝试:
注意
即使可以通过扫描文件系统找到 DLL,Visual Chart本身也必须正在运行。backtrader 不会启动Visual Chart
VCStore
的其他职责:
Visual Chart 提供的数据源具有一些有趣的特性:
重新采样由平台完成
并非所有情况:秒不受支持,仍需由 backtrader 完成
因此,只有在处理秒数时,最终用户才需要执行:
vcstore = bt.stores.VCStore()
vcstore.getdata(dataname='015ES', timeframe=bt.TimeFrame.Ticks)
cerebro.resampledata(data, timeframe=bt.TimeFrame.Seconds, compression=5)`
在所有其他情况下,仅需要:
vcstore = bt.stores.VCStore()
data = vcstore.getdata(dataname='015ES', timeframe=bt.TimeFrame.Minutes, compression=2)
cerebro.addata(data)`
数据将通过比较内部设备时钟和平台提供的ticks
来内部计算timeoffset
,以便在没有新的ticks
到达时尽快提供自动重新采样的柱状图。
实例化数据:
将在 VisualChart 左上方看到的符号传递,不包括空格。 例如:
001 ES
。 实例化它为:data = vcstore.getdata(dataname='001ES', ...)`
015 ES
。 实例化它为:data = vcstore.getdata(dataname='015ES', ...)`
注意
backtrader 将尽力清除位于名称直接从 Visual Chart 粘贴的第四个位置的空格。
时间管理遵循 backtrader 的一般规则
这适用于 Visual Chart 中的大多数市场,但对于某些市场进行了特定的管理:
096
中的数据被命名为 International Indices
。
理论上这些被报告为位于Europe/London
时区,但测试表明这似乎只是部分正确,某些内部管理措施已经覆盖了它。
可以通过传递参数 usetimezones=True
来启用实际时区的时间管理。 如果可用,会尝试使用 pytz
。 并不需要,因为对于大多数市场,Visual Chart 提供的内部时间偏移量允许无缝转换到市场时间。
在任何情况下,报告096.DJI
位于Europe/London
时间似乎是毫无意义的,当实际上它位于US/Eastern
时。 因此,backtrader
将在后者中报告它。 在这种情况下,强烈推荐使用 pytz
。
注意
道琼斯工业指数(不是全球版本)位于 099I-DJI
注意
所有这些时间管理都在 DST 转换期间进行真正的测试,期间本地和远程市场发生了与 DST 相关的不同步现象。
VCDATA
中定义了输出时区的International Indices
列表:
'096.FTSE': 'Europe/London',
'096.FTEU3': 'Europe/London',
'096.MIB30': 'Europe/Berlin',
'096.SSMI': 'Europe/Berlin',
'096.HSI': 'Asia/Hong_Kong',
'096.BVSP': 'America/Sao_Paulo',
'096.MERVAL': 'America/Argentina/Buenos_Aires',
'096.DJI': 'US/Eastern',
'096.IXIC': 'US/Eastern',
'096.NDX': 'US/Eastern',
使用给定的时间而不是默认的 00:00:00
传递 fromdate
或 todate
似乎会在 COM API 中创建一个过滤器,并且任何日期的柱状图只会在给定时间之后交付。
因此:
请仅向 VCData
传递完整日期,如下所示:
data = vcstore.getdata(dataname='001ES', fromdate=datetime(2016, 5, 15))`
并非::
data = vcstore.getdata(dataname=‘001ES’, fromdate=datetime(2016, 5, 15, 8, 30))`
如果最终用户未指定fromdate
,平台将自动尝试回填,然后继续实时数据。回填是时间框架相关的,如下:
Ticks
,MicroSeconds
,Seconds
:1 天
对于给定的 3 个时间框架,秒和微秒不受Visual Chart直接支持,通过Ticks的重新采样完成
分钟
:2 天
天数
:1 年
周
:2 年
月份
:5 年
月份
:20 年
定义的回填期将乘以请求的压缩
,即:如果时间框架是分钟
,压缩是 5,则最终回填期将是:2 天 * 5 -> 10 天
Visual Chart提供连续未来。无需手动管理,您可以跟踪所选的未来而无需中断。这是一个优势,也提出了一个小挑战:
ES-Mini
是001ES
,但实际交易资产(例如:Sep-2016)是ESU16
。为了克服这一点,并允许跟踪连续未来并交易真实资产的策略,在数据实例化期间可以指定以下内容:
data = vcstore.getdata(dataname='001ES', tradename='ESU16')
交易将在ESU16
上进行,但数据源将来自001ES
(数据相同,为期 3 个月)
qcheck
(默认:0.5
秒)控制唤醒与内部重新采样器/重播器交流的频率,以避免延迟传递条形图。
将应用以下逻辑来使用此参数:
数据源仍将唤醒以检查Visual Chart内置的重新采样器,但这是自动控制的。
数据源将通过以下一种或多种方式报告当前状态(检查Cerebro和Strategy参考)
Cerebro.notify_data
(如果被覆盖)
Cerebro.adddatacb
添加的回调
Strategy.notify_data
(如果被覆盖)
策略内的一个示例:
class VCStrategy(bt.Strategy):
def notify_data(self, data, status, *args, **kwargs):
if status == data.LIVE: # the data has switched to live data
# do something
pass
系统发生变化后将发送以下通知:
CONNECTED
成功初始连接时发送
DISCONNECTED
在这种情况下,不再可能检索数据,数据将指示系统无法执行任何操作。可能的情况:
CONNBROKEN
连接已丢失,无论是到 TWS 还是到数据中心。数据源将尝试(通过存储)重新连接和回填,必要时,并恢复操作
NOTSUBSCRIBED
合同和连接正常,但由于权限不足,无法检索数据。
数据将指示系统无法检索数据
DELAYED
表示历史/回填操作正在进行中,并且策略处理的数据不是实时数据
LIVE
表示从这一点开始要处理的数据是实时数据
策略的开发人员应考虑在发生断开连接或接收延迟数据时采取哪些行动。
要使用VCBroker,必须替换由cerebro创建的标准经纪人模拟实例。
使用Store模型(首选):
import backtrader as bt
cerebro = bt.Cerebro()
vcstore = bt.stores.VCStore()
cerebro.broker = vcstore.getbroker() # or cerebro.setbroker(...)
无论是直接还是通过getbroker
,VCBroker
经纪人都不支持参数。 这是因为经纪人只是对真实经纪人的代理。 真正的经纪人给出的,不应被拿走。
Visual Chart报告持仓。 这在大多数情况下可以用来控制实际仓位,但缺少指示仓位已关闭的最终事件。
这使得backtrader必须对Position进行完整的会计核算,并与您帐户中任何先前存在的仓位分开
COM交易界面不报告佣金。 backtrader没有机会做出合理猜测,除非:
Visual Chart在一个经纪人上同时支持多个帐户。 可以使用以下参数控制选择的帐户:
account
(默认值:None
)
VisualChart 支持同时在经纪人上使用多个帐户。 如果使用默认值None
,则将使用 ComTrader Accounts
集合中的第一个帐户。
如果提供了帐户名称,则将检查并使用Accounts
集合(如果存在)
关于标准用法没有变化。 只需使用策略中可用的方法(有关详细说明,请参阅Strategy
参考)
buy
sell
close
cancel
Order
对象Visual Chart支持backtrader所需的最小订单执行类型,因此,任何经过回测的内容都可以实时执行。
因此,订单执行类型仅限于经纪人模拟中可用的类型:
Order.Market
Order.Close
Order.Limit
Order.Stop
(当Stop触发时,Market订单随后进行)
Order.StopLimit
(当Stop触发时,Limit订单随后进行)
在回测期间可用的相同有效性概念(用于valid
到buy
和sell
)可用,并具有相同含义。 因此,对于以下值,valid
参数转换如下以用于Visual Chart Orders:
None
翻译为Good Til Cancelled
因为未指定有效期,因此理解订单必须有效直至取消
datetime/date
翻译为Good Til Date
注意
注意:Visual Chart仅支持“完整日期”,并且忽略了时间部分。
timedelta(x)
翻译为 有效期至(这里 timedelta(x) != timedelta()
)
注释
注意:Visual Chart 仅支持完整日期,并且会将时间部分丢弃。
这被解释为指示订单从 now
+ timedelta(x)
开始有效
timedelta() or 0
翻译为 会话
已传递一个值(而不是 None
),但是值为 Null,并被解释为仅在当前 日期(会话)有效的订单
标准 Order
状态将通过方法 notify_order
(如果被覆盖)通知到一个 策略 上
已提交
- 订单已发送至 TWS
已接受
- 订单已下达
已拒绝
- 订单放置失败或在其生命周期内被系统取消
部分完成
- 部分执行已经发生
已完成
- 订单已完全执行
已取消
(或 取消
)
已过期
- 目前尚未报告。需要一种启发式方法来区分此状态与 取消
的区别
包装 ibpy ibConnection 实例的单例类。
这些参数也可以在使用该存储的类中指定,例如 VCData
和 VCBroker
VisualChart 的经纪商实现。
此类将 VisualChart 的订单/持仓映射到 backtrader
的内部 API。
参数:
账户
(默认值:None)
VisualChart 支持在经纪商上同时使用多个账户。如果默认值为 None
,则将使用 ComTrader Accounts
集合中的第 1 个账户。
如果提供了账户名称,则将检查并使用 Accounts
集合(如果存在)
佣金
(默认值:None)
如果未传递佣金方案,则将自动生成对象
有关更多解释,请参阅下面的注释
VisualChart 通过 ComTrader 接口报告“OpenPositions”更新,但仅当持仓具有“大小”时。指示持仓已移至零的更新通过不存在此类持仓来报告。这强制通过观察执行事件来保持持仓的会计,就像模拟经纪商一样。
VisualChart 的 ComTrader 接口不报告佣金,因此自动生成的 CommissionInfo 对象不能使用不存在的佣金来正确计算它们。为了支持佣金,必须传递带有适当佣金方案的 commission
参数。
佣金方案的文档详细介绍了如何实现这一点
ComTrader 接口(或者是 comtypes 模块?)从 datetime
对象中丢弃了 time
信息,并且到期日期始终是完整日期。
目前还没有启发式方法来确定取消的订单何时因过期而取消。因此,过期订单被报告为已取消。
VisualChart 数据源。
参数:
qcheck
(默认值:0.5
)唤醒以便让重采样器/重播器检查当前柱是否可以进行交付的默认超时时间
该值仅在数据中插入了重采样/重播过滤器时才会使用
historical
(默认值:False
)如果没有提供todate
参数(在基类中定义),则将强制仅进行历史下载(如果设置为True
)
如果提供了todate
,则可以实现相同的效果
milliseconds
(默认值:True
)由Visual Chart构建的柱状图具有如下外观:HH:MM:59.999000
如果该参数设置为True
,将会在时间上增加一毫秒,使其看起来像是:HH::MM + 1:00.000000
tradename
(默认值:None
)连续期货无法交易,但非常适合数据跟踪。如果提供了该参数,它将是当前期货的名称,该名称将成为交易资产。示例:
dataname
tradename
中提供了此信息,它将成为交易资产。
usetimezones
(默认值:True
)对于大多数市场,Visual Chart提供的时间偏移信息允许将日期时间转换为市场时间(backtrader选择的表示方式)
一些市场是特殊的(096
),需要特殊的内部覆盖和时区支持以显示用户预期的市场时间。
如果该参数设置为True
,将尝试导入pytz
以使用时区(默认值)
禁用它将取消时区使用(可能有助于减轻负载过重的情况)
尽管回测是基于数学计算的自动化过程,但通常情况下,人们希望实际可视化正在发生的情况。 无论是使用经过回测运行的现有算法,还是查看真正的指标(内置或自定义)与数据一起提供了什么。
而且因为一切都有人类的背后,绘制数据源、指标、操作、现金和组合价值的发展可以帮助人类更好地理解正在发生的事情,丢弃/修改/创建想法以及查看图表的人类可以用视觉信息做的任何事情。
这就是为什么backtrader,利用matplotlib
提供的便利设施,提供了内置的图表功能。
任何回测运行都可以通过调用单个方法绘制:
cerebro.plot()
当然,这通常是像这样的最后一个命令,这个简单的代码使用了backtrader源之一的样本数据。
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import backtrader as bt
class St(bt.Strategy):
def __init__(self):
self.sma = bt.indicators.SimpleMovingAverage(self.data)
data = bt.feeds.BacktraderCSVData(dataname='../../datas/2005-2006-day-001.txt')
cerebro = bt.Cerebro()
cerebro.adddata(data)
cerebro.addstrategy(St)
cerebro.run()
cerebro.plot()
这产生了以下图表。
图表包括 3 个观察器,在这种情况下,考虑到没有任何交易,它们基本上是没有意义的
CashValue
观察器,如其名称所示,在回测运行的生命周期内跟踪Cash
和总投资组合Value
(包括现金)
Trade
观察器,在一次交易结束时显示实际的盈亏
交易被定义为开仓并将仓位调回0
(直接或从多头到空头或空头到多头)
BuySell
观察器,在价格之上绘制买入和卖出操作的位置
这3 个观察器是由cerebro
自动添加的,并且通过stdstats
参数(默认为True
)进行控制。 如果希望禁用它们,请执行以下操作:
cerebro = bt.Cerebro(stdstats=False)
或者稍后运行时,例如:
cerebro = bt.Cerebro()
...
cerebro.run(stdstats=False)
尽管在介绍中已经提到了Observers
,但它们并不是唯一要绘制的元素。 这三个东西被绘制出来:
adddata
、replaydata
和resampledata
将数据源添加到 Cerebro
addindicator
将其添加到 cerebro,这纯粹是为了实验目的,并将指标添加到虚拟策略中)
addobserver
将观察器添加到 cerebro 的选项
观察器是lines对象,它们与strategy同步运行,并且可以访问整个生态系统,以便跟踪Cash
和Value
等情况
指标和观察器有几个选项,控制它们在图表上的绘制方式。 有三大组:
这些由 Indicators 和 Observers 中的数据集控制:
plotinfo = dict(plot=True,
subplot=True,
plotname='',
plotskip=False,
plotabove=False,
plotlinelabels=False,
plotlinevalues=True,
plotvaluetags=True,
plotymargin=0.0,
plotyhlines=[],
plotyticks=[],
plothlines=[],
plotforce=False,
plotmaster=None,
plotylimited=True,
)
尽管在类定义期间 plotinfo
显示为 dict
,但 backtrader 的元类机制将其转换为一个对象,该对象被继承并且可以进行多重继承。这意味着:
subplot=True
这样的值为 subplot=False
,则层级结构下面的子类将以后者作为 subplot
的默认值。给这些参数赋值有两种方法。让我们看一下第 1 种方法的 SimpleMovingAverage
实例化:
sma = bt.indicators.SimpleMovingAverage(self.data, period=15, plotname='mysma')
如示例所示,SimpleMovingAverage
构造函数未使用的任何 **kwargs
将被解析(如果可能)为 plotinfo
的值。SimpleMovingAverage
仅定义了一个名为 period
的参数。这意味着 plotname
将与 plotinfo
中同名的参数相匹配。
第 2 种方法:
sma = bt.indicators.SimpleMovingAverage(self.data, period=15)
sma.plotinfo.plotname = 'mysma'
可以访问沿着 SimpleMovingAverage 实例化的 plotinfo
对象,也可以使用标准的 Python 点符号访问其中的参数。简单且可能比上述语法更清晰。
plot
:对象是否需要绘制
subplot
:是否沿数据绘制或在独立的子图上绘制。移动平均线 是在数据上绘制的示例。随机指标 和 RSI 是在不同比例尺上的子图中绘制的示例。
plotname
:在图表上使用的名称,而不是 类 名称。如上例中的 mysma
而不是 SimpleMovingAverage
。
plotskip
(已弃用):plot
的旧别名。
plotabove
:是否在数据的上方绘制。否则在下方绘制。只有当 subplot=True
时才有效。
plotlinelabels
:当 subplot=False
时,是否在图表上绘制单个线条名称的图例。
示例:布林带 有 3 条线,但指标是绘制在数据的上方。在图例中仅显示单个名称如 BollingerBands
看起来更合理,而不是显示 3 条单独线的名称(mid
、top
、bot
)。
对于 BuySell
观察者的用例,将显示 2 条线和其标记的名称是有意义的:Buy
和 Sell
,以便清楚地告诉最终用户什么是什么。
plotlinevalues
:控制指标和观察者中的线条图例是否具有最后绘制的值。可以使用每条线的 _plotvalue
控制单个线的显示方式。
plotvaluetags
:控制是否在线条右侧绘制带有最后值的值标签。可以使用每条线的 _plotvaluetag
控制单个线的显示方式。
plotymargin
:在图表上单个子图的顶部和底部添加的边距
这是一个基于 1 的百分比。例如:0.05 -> 5%
plothlines
:包含在比例尺内需要绘制 水平线 的值的 可迭代对象。
例如,这有助于经典指标,如RSI
,通常在70
和30
处绘制线条的超买,超卖区域。
plotyticks
:包含在比例尺上必须特别放置值刻度的可迭代对象
例如,为了强制比例尺具有50
来识别比例尺的中点。尽管这似乎很明显,指标使用自动缩放机制,如果一个具有0-100
比例尺的指标在 30-95 之间定期移动,50
可能不明显位于中心。
plotyhlines
:包含在比例尺上必须绘制水平线的值(在比例尺内)。
这可以同时控制plothlines
和plotyticks
。
如果上述选项都未定义,则水平线和刻度的放置完全由此值控制
如果上述任何选项被定义,它们将优先于此选项中的值
plotforce
:有时候,通过匹配数据源和指标等复杂过程,自定义指标可能无法绘制。这是一种最后的手段机制,试图强制绘制。
如果其他方法都失败,请使用它
plotmaster
:指标/观察者有一个主数据,即其工作的数据。在某些情况下,可能希望使用不同的主数据来绘制它。
一个用例是PivotPoint
指标,它是在月度数据上计算的,但是适用于每日数据。只有在每日数据上绘制它才有意义,这是指标有意义的地方。
plotylimited
:目前仅适用于数据源。如果设置为True
(默认值),数据图上的其他线条不会改变比例尺。例如:布林带(顶部和底部)可能远离实际数据源的绝对最小值/最大值。如果设置为\
plotlimited=True,这些带子将保持在图表之外,因为数据控制着比例尺。如果设置为
False`,这些带子会影响 y 轴比例尺,并在图表上可见。
一个用例是PivotPoint
指标,它是在月度数据上计算的,但是适用于每日数据。只有在每日数据上绘制它才有意义,这是指标有意义的地方。
指标/观察者有线条,如何绘制这些线条可以通过plotlines
对象进行影响。plotlines
中指定的大多数选项意味着在绘图时直接传递给matplotlib
。因此,文档依赖于已完成的示例。
重要:选项是基于每行指定的。
一些选项由backtrader直接控制。所有这些选项都以下划线(_
)开头:
_plotskip
(布尔值)指示如果设置为True
,则必须跳过特定线条的绘制
_plotvalue
(布尔值)控制是否在此线条的图例中包含最后绘制的值(默认值为True
)
_plotvaluetag
(布尔值)控制是否绘制带有最后值的右侧标签(默认为True
)
_name
(字符串)用于更改特定线路的绘图名称
_skipnan
(布尔值,默认值:False):在绘图时跳过NaN
值,例如允许绘制由指标生成的两个远点之间的线,其中所有中间值都为NaN
(新创建的数据点的默认值)
_samecolor
(布尔值)这会强制下一行具有与前一行相同的颜色,避免了matplotlib
默认的循环遍历颜色映射以绘制每个新绘制的元素的机制
_method
(字符串)选择matplotlib
将用于元素的绘图方法。如果未指定,则将选择最基本的plot
方法。
来自MACDHisto
的示例。这里histo
线路被绘制为bar
,这是行业的事实标准。在MACDHisto
的定义中可以找到以下定义:
lines = ('histo',)
plotlines = dict(histo=dict(_method='bar', alpha=0.50, width=1.0))`
alpha
和width
是matplotlib的选项
_fill_gt
/ _fill_lt
允许在给定线路和之间填充:
参数是一个包含 2 个元素的可迭代对象,其中:
或
0.20
,由绘图方案中的fillalpha
控制)示例:
# Fill for myline when above other_line with colour red
plotlines = dict(
myline=dict(_fill_gt('other_line', 'red'))
)
# Fill for myline when above 50 with colour red
plotlines = dict(
myline=dict(_fill_gt(50, 'red))
)
# Fill for myline when above other_line with colour red and 50%
# transparency (1.0 means "no transparency")
plotlines = dict(
myline=dict(_fill_gt('other_line', ('red', 0.50)))
)`
_X
,其中X
代表零为基础索引中的数字。这意味着选项适用于线路X
OscillatorMixIn
的一个用例:
plotlines = dict(_0=dict(_name='osc'))
如名称所示,这是一个混合类,旨在在多重继承方案(特别是在右侧)中使用。 mixin对来自将成为多重继承混合的其他指标的第一个线路的实际名称(索引从零开始)没有任何了解。
这就是为什么选项被指定为:_0
。子类化完成后,结果类的第一行将在图中具有名称osc
。
BuySell
观察器包括以下内容:
plotlines = dict(
buy=dict(marker='^', markersize=8.0, color='lime', fillstyle='full'),
sell=dict(marker='v', markersize=8.0, color='red', fillstyle='full')
)
buy
和sell
线路有一些选项,直接传递给matplotlib
以定义marker、markersize、color和fillstyle。所有这些选项都在matplotlib中定义。
Trades
观察器包括以下内容:
...
lines = ('pnlplus', 'pnlminus')
...
plotlines = dict(
pnlplus=dict(_name='Positive',
marker='o', color='blue',
markersize=8.0, fillstyle='full'),
pnlminus=dict(_name='Negative',
marker='o', color='red',
markersize=8.0, fillstyle='full')
)
这里使用_name
将线路名称重新定义为例如pnlplus
变为Positive
。其余选项用于matplotlib
DrawDown
观察器:
lines = ('drawdown', 'maxdrawdown',)
...
plotlines = dict(maxdrawdown=dict(_plotskip='True',))
这个定义了两条线,让最终用户不仅可以访问当前 drawdown
的值,还可以访问其最大值 (maxdrawdown
)。但由于 _plotskip=True
,后者不会绘制出来
BollingerBands
指标:
plotlines = dict(
mid=dict(ls='--'),
top=dict(_samecolor=True),
bot=dict(_samecolor=True),
)
这里 mid
线将以 虚线 样式绘制,而 top
和 bot
线将与 mid
线具有相同的颜色。
Stochastic
(在 _StochasticBase
中定义并继承):
lines = ('percK', 'percD',)
...
plotlines = dict(percD=dict(_name='%D', ls='--'),
percK=dict(_name='%K'))
较慢的线 percD
以 虚线 样式绘制。并且线的名称更改为包含花哨的 %
符号(%K
和 %D
),在 Python 中无法在名称定义中使用
处理 Indicators 和 Observers 时,支持以下方法以进一步控制绘图:
_plotlabel(self)
应该返回一列东西,以符合将在 Indicators 或 Observer 名称后放置在括号中的标签
从 RSI
指标的一个例子:
def _plotlabel(self):
plabels = [self.p.period]
plabels += [self.p.movav] * self.p.notdefault('movav')
return plabels`
可以看到这个方法返回:
int
,表示为 RSI
配置的周期,如果默认移动平均线已更改,则是特定的类
在后台,两者都将转换为字符串。在 class 的情况下,将努力只打印类的名称,而不是完整的 module.name
组合。
_plotinit(self)
在绘图开始时调用,执行指标可能需要的任何特定初始化。再次,来自 RSI
的一个例子:
def _plotinit(self):
self.plotinfo.plotyhlines = [self.p.upperband, self.p.lowerband]`
这里的代码为 plotyhlines
赋值,以在特定 y
值处绘制水平线(hlines
部分)。
参数 upperband
和 lowerband
的值将用于此操作,因为参数可以由最终用户更改,所以无法事先知道
首先是在 cerebro 中的 plot
的签名:
def plot(self, plotter=None, numfigs=1, iplot=True, **kwargs):
这意味着:
plotter
:包含作为属性的选项,控制系统范围内绘图的对象/类
如果传递了 None
,则将实例化一个默认的 PlotScheme
对象(见下文)
numfigs
:将绘图分解为多少个独立图表
有时图表包含太多条形,如果在单个图中打包,将不容易阅读。这将根据请求的数量将其分解为相同数量的部分
iplot
:如果在 Jupyter Notebook 中运行,则自动绘制内联
**kwargs
:args 将用于更改 plotter
或默认 PlotScheme
对象的属性值,如果没有传递 plotter
则会创建默认的 PlotScheme
对象。
此对象包含控制系统范围内绘图的所有选项。选项在代码中有文档:
class PlotScheme(object):
def __init__(self):
# to have a tight packing on the chart wether only the x axis or also
# the y axis have (see matplotlib)
self.ytight = False
# y-margin (top/bottom) for the subcharts. This will not overrule the
# option plotinfo.plotymargin
self.yadjust = 0.0
# Each new line is in z-order below the previous one. change it False
# to have lines paint above the previous line
self.zdown = True
# Rotation of the date labes on the x axis
self.tickrotation = 15
# How many "subparts" takes a major chart (datas) in the overall chart
# This is proportional to the total number of subcharts
self.rowsmajor = 5
# How many "subparts" takes a minor chart (indicators/observers) in the
# overall chart. This is proportional to the total number of subcharts
# Together with rowsmajor, this defines a proportion ratio betwen data
# charts and indicators/observers charts
self.rowsminor = 1
# Distance in between subcharts
self.plotdist = 0.0
# Have a grid in the background of all charts
self.grid = True
# Default plotstyle for the OHLC bars which (line -> line on close)
# Other options: 'bar' and 'candle'
self.style = 'line'
# Default color for the 'line on close' plot
self.loc = 'black'
# Default color for a bullish bar/candle (0.75 -> intensity of gray)
self.barup = '0.75'
# Default color for a bearish bar/candle
self.bardown = 'red'
# Level of transparency to apply to bars/cancles (NOT USED)
self.bartrans = 1.0
# Wether the candlesticks have to be filled or be transparent
self.barupfill = True
self.bardownfill = True
# Wether the candlesticks have to be filled or be transparent
self.fillalpha = 0.20
# Wether to plot volume or not. Note: if the data in question has no
# volume values, volume plotting will be skipped even if this is True
self.volume = True
# Wether to overlay the volume on the data or use a separate subchart
self.voloverlay = True
# Scaling of the volume to the data when plotting as overlay
self.volscaling = 0.33
# Pushing overlay volume up for better visibiliy. Experimentation
# needed if the volume and data overlap too much
self.volpushup = 0.00
# Default colour for the volume of a bullish day
self.volup = '#aaaaaa' # 0.66 of gray
# Default colour for the volume of a bearish day
self.voldown = '#cc6073' # (204, 96, 115)
# Transparency to apply to the volume when overlaying
self.voltrans = 0.50
# Transparency for text labels (NOT USED CURRENTLY)
self.subtxttrans = 0.66
# Default font text size for labels on the chart
self.subtxtsize = 9
# Transparency for the legend (NOT USED CURRENTLY)
self.legendtrans = 0.25
# Wether indicators have a leged displaey in their charts
self.legendind = True
# Location of the legend for indicators (see matplotlib)
self.legendindloc = 'upper left'
# Plot the last value of a line after the Object name
self.linevalues = True
# Plot a tag at the end of each line with the last value
self.valuetags = True
# Default color for horizontal lines (see plotinfo.plothlines)
self.hlinescolor = '0.66' # shade of gray
# Default style for horizontal lines
self.hlinesstyle = '--'
# Default width for horizontal lines
self.hlineswidth = 1.0
# Default color scheme: Tableau 10
self.lcolors = tableau10
# strftime Format string for the display of ticks on the x axis
self.fmt_x_ticks = None
# strftime Format string for the display of data points values
self.fmt_x_data = None
PlotScheme
类定义了一个方法,可以在子类中重写,该方法返回下一个要使用的颜色:
def color(self, idx)
其中idx
是当前正在绘制的单个子图上的行的索引。例如,MACD
绘制了 3 条线,因此idx
变量只会有以下值:0
、1
和2
。下一个图表(可能是另一个指标)将从0
开始重新计数。
默认颜色方案使用的是backtrader中的(如上所示)Tableau 10 Color Palette
,其索引修改为:
tab10_index = [3, 0, 2, 1, 2, 4, 5, 6, 7, 8, 9]
通过覆盖color
方法或将lcolors
变量传递给plot
(或在PlotScheme
的子类中),可以完全改变着色方式。
源代码还包含了Tableau 10 Light
和Tableau 20
颜色调色板的定义。
原文:
www.backtrader.com/docu/plotting/ranges/plotting-date-ranges/
发布的1.9.31.x
版本增加了制作部分图表的功能。
datetime.date
或datetime.datetime
实例来限制需要绘制的内容。
仍然在标准的cerebro.plot
上。示例:
cerebro.plot(start=datetime.date(2005, 7, 1), end=datetime.date(2006, 1, 31))
作为人类直接执行的方式。具有扩展能力的人类实际上可以尝试将datetime
时间戳作为索引,如下所示:
cerebro.plot(start=75, end=185)
一个非常标准的示例包含简单移动平均线(在数据绘图中)、随机指标(独立绘图)和随机指标线的交叉点,如下所示。cerebro.plot
的参数作为命令行参数传递。
使用date
方法执行:
./partial-plot.py --plot 'start=datetime.date(2005, 7, 1),end=datetime.date(2006, 1, 31)'
Python 中的eval
魔法允许直接在命令行中编写datetime.date
,并将其映射到有意义的内容。输出图表
让我们将其与完整图表进行比较,以查看数据实际上是从两端跳过的:
./partial-plot.py --plot
Python 中的eval
魔法允许直接在命令行中编写datetime.date
,并将其映射到有意义的内容。输出图表
$ ./partial-plot.py --help
usage: partial-plot.py [-h] [--data0 DATA0] [--fromdate FROMDATE]
[--todate TODATE] [--cerebro kwargs] [--broker kwargs]
[--sizer kwargs] [--strat kwargs] [--plot [kwargs]]
Sample for partial plotting
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: )
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import backtrader as bt
class St(bt.Strategy):
params = (
)
def __init__(self):
bt.ind.SMA()
stoc = bt.ind.Stochastic()
bt.ind.CrossOver(stoc.lines.percK, stoc.lines.percD)
def next(self):
pass
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 for partial plotting'
)
)
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()
前一篇帖子中的未来点,是在同一空间上绘制了原始数据和略微(随机)修改的数据,但没有在同一轴上。
从那篇帖子中恢复第一张图片。
人们可以看到:
+- 50
点时,这一点最为明显。
在图表上的视觉印象是,这些随机数据大多数时候都在原始数据之上。这只是由于不同的比例尺造成的视觉印象。
尽管发行版 1.9.32.116
已经初步支持在同一轴上完全绘制,但图例标签会重复(只有标签,没有数据),这真的很令人困惑。
发行版 1.9.33.116
解决了这个效果,并允许完全在同一轴上绘制。用法模式与决定与哪些其他数据一起绘制的模式相似。从前一篇帖子中。
import backtrader as bt
cerebro = bt.Cerebro()
data0 = bt.feeds.MyFavouriteDataFeed(dataname='futurename')
cerebro.adddata(data0)
data1 = bt.feeds.MyFavouriteDataFeed(dataname='spotname')
data1.compensate(data0) # let the system know ops on data1 affect data0
data1.plotinfo.plotmaster = data0
data1.plotinfo.sameaxis = True
cerebro.adddata(data1)
...
cerebro.run()
data1
获得一些 plotinfo
值以:
plotmaster
(即 data0
)相同的空间中绘制
sameaxis
的指示
这种指示的原因是平台无法预先知道每个数据的比例尺是否兼容。这就是为什么它会在独立的比例尺上绘制它们的原因。
前面的示例获取了一个额外的选项,以在 sameaxis
上绘制。一个示例执行:
$ ./future-spot.py --sameaxis
以及产生的图表
要注意:
$ ./future-spot.py --help
usage: future-spot.py [-h] [--no-comp] [--sameaxis]
Compensation example
optional arguments:
-h, --help show this help message and exit
--no-comp
--sameaxis
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import random
import backtrader as bt
# The filter which changes the close price
def close_changer(data, *args, **kwargs):
data.close[0] += 50.0 * random.randint(-1, 1)
return False # length of stream is unchanged
# override the standard markers
class BuySellArrows(bt.observers.BuySell):
plotlines = dict(buy=dict(marker='$\u21E7$', markersize=12.0),
sell=dict(marker='$\u21E9$', markersize=12.0))
class St(bt.Strategy):
def __init__(self):
bt.obs.BuySell(self.data0, barplot=True) # done here for
BuySellArrows(self.data1, barplot=True) # different markers per data
def next(self):
if not self.position:
if random.randint(0, 1):
self.buy(data=self.data0)
self.entered = len(self)
else: # in the market
if (len(self) - self.entered) >= 10:
self.sell(data=self.data1)
def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()
dataname = '../../datas/2006-day-001.txt' # data feed
data0 = bt.feeds.BacktraderCSVData(dataname=dataname, name='data0')
cerebro.adddata(data0)
data1 = bt.feeds.BacktraderCSVData(dataname=dataname, name='data1')
data1.addfilter(close_changer)
if not args.no_comp:
data1.compensate(data0)
data1.plotinfo.plotmaster = data0
if args.sameaxis:
data1.plotinfo.sameaxis = True
cerebro.adddata(data1)
cerebro.addstrategy(St) # sample strategy
cerebro.addobserver(bt.obs.Broker) # removed below with stdstats=False
cerebro.addobserver(bt.obs.Trades) # removed below with stdstats=False
cerebro.broker.set_coc(True)
cerebro.run(stdstats=False) # execute
cerebro.plot(volume=False) # and plot
def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description=('Compensation example'))
parser.add_argument('--no-comp', required=False, action='store_true')
parser.add_argument('--sameaxis', required=False, action='store_true')
return parser.parse_args(pargs)
if __name__ == '__main__':
runstrat()
直到1.5.0版本发布之前,backtrader使用直接方法管理时间,即数据源计算的任何日期时间都会直接使用。
并且对于任何用户输入,例如参数 fromdate
(或sessionstart
),都可以提供给任何数据源
在进行回测时,直接控制冻结数据源是可以的。在数据进入系统之前,很容易假设输入的日期时间已经得到处理。
但是在 1.5.0 版本中,实时 数据源被支持了,这就需要考虑日期时间管理。如果以下情况始终为true,则不需要进行此类管理:
US/Eastern
(或其中一个别名)
CET
(或Europe/Berling
)时区
上述的直接输入输出日期时间方法将奏效,例如柏林的交易员可以始终这样做:
class Strategy(bt.Strategy):
def next(self):
# The DAX future opens at 08:00 CET
if self.data.datetime.time() < datetime.time(8, 30):
# don't operate until the market has been running 30 minutes
return #
直接方法的问题在于当同一位柏林的交易员决定交易ES-Mini
时会出现。因为夏令时的变化在一年中的不同时间发生,这导致一年中有几周的时间差异不同步。以下情况不会总是奏效:
class Strategy(bt.Strategy):
def next(self):
# The SPX opens at 09:30 US/Eastern all year long
# This is most of the year 15:30 CET
# But it is sometimes 16:30 CET or 14:30 CET if a DST switch on-off
# has happened in the USA and not in Europe
# That's why the code below is unreliable
if self.data.datetime.time() < datetime.time(16, 0):
# don't operate until the market has been running 30 minutes
return #
为了解决上述情况并仍然与直接输入输出时间方法兼容,backtrader
为最终用户提供了以下选项
tzinput
参数。这必须是与datetime.tzinfo
接口兼容的对象。最有可能用户会提供一个pytz.timezone
实例
做出这个决定后,backtrader
内部使用的时间被认为是以UTC-like
格式,即:
UTC
格式存储它
tzinput
转换后
UTC
,但它是用户的参考,因此是UTC-like
CET
时区)交易与US/Eastern
时区的产品的情况下。
因为交易员始终获得正确的时间,在上面的示例中,开盘时间保持恒定为09:30 US/Eastern
,而不是大部分时间为15:30 CET
,但有时为16:30 CET
,有时为14:30 CET
。
UTC-like
)时间
tz
参数。这必须是一个与datetime.tzinfo
接口兼容的对象。最有可能的是用户会提供一个pytz.timezone
实例。注意
用户输入,比如例如fromdate
或sessionstart
参数,预计会与实际的tz
同步,无论是由数据源自动计算、用户提供还是保持默认值(None
,这意味着datetime的直接输入输出)。
考虑到这一切,让我们回想一下在US/Eastern
时区进行交易的柏林交易员:
import pytz
import bt
data = bt.feeds.MyFeed('ES-Mini', tz=pytz.timezone('US/Eastern'))
class Strategy(bt.Strategy):
def next(self):
# This will work all year round.
# The data source will return in the frame of the 'US/Eastern' time
# zone and the user is quoting '10:00' as reference time
# Because in the 'US/Eastern' timezone the SPX index always starts
# trading at 09:30, this will always work
if self.data.datetime.time() < datetime.time(10, 0):
# don't operate until the market has been running 30 minutes
return #
对于数据源可以自动确定输出时区的情况:
import bt
data = bt.feeds.MyFeedAutoTZ('ES-Mini')
class Strategy(bt.Strategy):
def next(self):
# This will work all year round.
# The data source will return in the frame of the 'US/Eastern' time
# zone and the user is quoting '10:00' as reference time
# Because in the 'US/Eastern' timezone the SPX index always starts
# trading at 09:30, this will always work
if self.data.datetime.time() < datetime.time(10, 0):
# don't operate until the market has been running 30 minutes
return #
比以上工作还要少。
显然,上面示例中的MyFeed
和MyFeedAuto
只是虚拟名称。
注意
在撰写本文时,分发中唯一能够自动确定时区的数据源是连接到交互经纪商的那个。
发布1.9.44.116
添加了计时器到backtrader可用工具的工具库中。此功能允许在给定时间点获得对notify_timer
(在Cerebro
和Strategy
中可用)的回调,用户可以对其进行细粒度的控制。
注意
在1.9.46.116
中进行了一些更正
在Cerebro
和Strategy
子类中,计时器回调将在以下方法中收到。
def notify_timer(self, timer, when, *args, **kwargs):
'''Receives a timer notification where ``timer`` is the timer which was
returned by ``add_timer``, and ``when`` is the calling time. ``args``
and ``kwargs`` are any additional arguments passed to ``add_timer``
The actual ``when`` time can be later, but the system may have not be
able to call the timer before. This value is the timer value and not the
system time.
'''
用这种方法完成
def add_timer(self, when,
offset=datetime.timedelta(), repeat=datetime.timedelta(),
weekdays=[], weekcarry=False,
monthdays=[], monthcarry=True,
allow=None,
tzdata=None, cheat=False,
*args, **kwargs):
'''
它返回创建的Timer
实例。
有关参数的解释,请参见下文。
与相同的方法完成,并只添加参数strats
。如果设置为True
,则不仅将通知计时器给cerebro,还将通知给系统中运行的所有策略。
def add_timer(self, when,
offset=datetime.timedelta(), repeat=datetime.timedelta(),
weekdays=[], weekcarry=False,
monthdays=[], monthcarry=True,
allow=None,
tzdata=None, cheat=False, strats=False,
*args, **kwargs):
'''
它返回创建的Timer
实例。
cheat=False
这是默认情况。在这种情况下将调用计时器:
next
方法之前
cheat=True
在这种情况下将调用计时器:
next
方法之前
这允许例如以下具有每日条的情景:
next
评估期间可能已经设置了标志)
open
的良好指示。
示例scheduled.py
默认使用backtrader发行版中提供的标准每日条运行。策略的参数
class St(bt.Strategy):
params = dict(
when=bt.timer.SESSION_START,
timer=True,
cheat=False,
offset=datetime.timedelta(),
repeat=datetime.timedelta(),
weekdays=[],
)
数据的会话时间如下:
仅使用时间运行
$ ./scheduled.py --strat when='datetime.time(15,30)'
strategy notify_timer with tid 0, when 2005-01-03 15:30:00 cheat False
1, 2005-01-03 17:30:00, Week 1, Day 1, O 2952.29, H 2989.61, L 2946.8, C 2970.02
strategy notify_timer with tid 0, when 2005-01-04 15:30:00 cheat False
2, 2005-01-04 17:30:00, Week 1, Day 2, O 2969.78, H 2979.88, L 2961.14, C 2971.12
strategy notify_timer with tid 0, when 2005-01-05 15:30:00 cheat False
3, 2005-01-05 17:30:00, Week 1, Day 3, O 2969.0, H 2969.0, L 2942.69, C 2947.19
strategy notify_timer with tid 0, when 2005-01-06 15:30:00 cheat False
...
如指定的,计时器在15:30
时在跳动。没有什么意外。让我们添加一个偏移量为 30 分钟。
$ ./scheduled.py --strat when='datetime.time(15,30)',offset='datetime.timedelta(minutes=30)'
strategy notify_timer with tid 0, when 2005-01-03 16:00:00 cheat False
1, 2005-01-03 17:30:00, Week 1, Day 1, O 2952.29, H 2989.61, L 2946.8, C 2970.02
strategy notify_timer with tid 0, when 2005-01-04 16:00:00 cheat False
2, 2005-01-04 17:30:00, Week 1, Day 2, O 2969.78, H 2979.88, L 2961.14, C 2971.12
strategy notify_timer with tid 0, when 2005-01-05 16:00:00 cheat False
...
时间已从15:30
更改为16:00
以进行计时。没有什么意外。让我们做同样的事情,但参考会话开始。
$ ./scheduled.py --strat when='bt.timer.SESSION_START',offset='datetime.timedelta(minutes=30)'
strategy notify_timer with tid 0, when 2005-01-03 09:30:00 cheat False
1, 2005-01-03 17:30:00, Week 1, Day 1, O 2952.29, H 2989.61, L 2946.8, C 2970.02
strategy notify_timer with tid 0, when 2005-01-04 09:30:00 cheat False
2, 2005-01-04 17:30:00, Week 1, Day 2, O 2969.78, H 2979.88, L 2961.14, C 2971.12
...
Et voilá!回调被调用的时间是09:30
。而会话开始,见上文,是09:00
。这使得我们能够简单地说,在会话开始后30 分钟执行某个动作。
让我们添加一个重复:
$ ./scheduled.py --strat when='bt.timer.SESSION_START',offset='datetime.timedelta(minutes=30)',repeat='datetime.timedelta(minutes=30)'
strategy notify_timer with tid 0, when 2005-01-03 09:30:00 cheat False
1, 2005-01-03 17:30:00, Week 1, Day 1, O 2952.29, H 2989.61, L 2946.8, C 2970.02
strategy notify_timer with tid 0, when 2005-01-04 09:30:00 cheat False
2, 2005-01-04 17:30:00, Week 1, Day 2, O 2969.78, H 2979.88, L 2961.14, C 2971.12
strategy notify_timer with tid 0, when 2005-01-05 09:30:00 cheat False
...
没有重复。原因是价格的分辨率是每日的。计时器像在先前的例子中那样在09:30
第 1 次被调用。但当系统获取下一批价格时,它们发生在下一天。显然,计时器只能被调用一次。需要更低的分辨率。
但在转到较低分辨率之前,让我们通过在会话结束前调用计时器来欺骗一下。
$ ./scheduled.py --strat when='bt.timer.SESSION_START',cheat=True
strategy notify_timer with tid 1, when 2005-01-03 09:00:00 cheat True
-- 2005-01-03 Create buy order
strategy notify_timer with tid 0, when 2005-01-03 09:00:00 cheat False
1, 2005-01-03 17:30:00, Week 1, Day 1, O 2952.29, H 2989.61, L 2946.8, C 2970.02
strategy notify_timer with tid 1, when 2005-01-04 09:00:00 cheat True
strategy notify_timer with tid 0, when 2005-01-04 09:00:00 cheat False
-- 2005-01-04 Buy Exec @ 2969.78
2, 2005-01-04 17:30:00, Week 1, Day 2, O 2969.78, H 2979.88, L 2961.14, C 2971.12
strategy notify_timer with tid 1, when 2005-01-05 09:00:00 cheat True
strategy notify_timer with tid 0, when 2005-01-05 09:00:00 cheat False
...
策略添加了一个带有cheat=True
的第 2 个计时器。这是第二个添加的,因此将收到第二个tid(计时器 id),即1
(请在上面的示例中查看分配的tid为0
)
而1
在0
之前被调用,因为该计时器是作弊的,且在系统中许多事件发生之前被调用(有关说明,请参见上文)
由于价格的每日分辨率,这并没有太大的区别,除了:
相同,但经纪人处于coo=True
模式
$ ./scheduled.py --strat when='bt.timer.SESSION_START',cheat=True --broker coo=True
strategy notify_timer with tid 1, when 2005-01-03 09:00:00 cheat True
-- 2005-01-03 Create buy order
strategy notify_timer with tid 0, when 2005-01-03 09:00:00 cheat False
-- 2005-01-03 Buy Exec @ 2952.29
1, 2005-01-03 17:30:00, Week 1, Day 1, O 2952.29, H 2989.61, L 2946.8, C 2970.02
strategy notify_timer with tid 1, when 2005-01-04 09:00:00 cheat True
strategy notify_timer with tid 0, when 2005-01-04 09:00:00 cheat False
2, 2005-01-04 17:30:00, Week 1, Day 2, O 2969.78, H 2979.88, L 2961.14, C 2971.12
strategy notify_timer with tid 1, when 2005-01-05 09:00:00 cheat True
strategy notify_timer with tid 0, when 2005-01-05 09:00:00 cheat False
...
有些事情已经改变了。
2005-01-03
发布
2005-01-03
以开盘价执行
事实上,就像在市场真正开放之前几秒钟就已经行动一样。
示例scheduled-min.py
默认以backtrader分发的标准 5 分钟柱形图运行。策略的参数被扩展以包括monthdays
和carry选项
class St(bt.Strategy):
params = dict(
when=bt.timer.SESSION_START,
timer=True,
cheat=False,
offset=datetime.timedelta(),
repeat=datetime.timedelta(),
weekdays=[],
weekcarry=False,
monthdays=[],
monthcarry=True,
)
数据具有相同的会话时间:
让我们做一些实验。首先是单个计时器。
$ ./scheduled-min.py --strat when='datetime.time(15, 30)'
1, 2006-01-02 09:05:00, Week 1, Day 1, O 3578.73, H 3587.88, L 3578.73, C 3582.99
2, 2006-01-02 09:10:00, Week 1, Day 1, O 3583.01, H 3588.4, L 3583.01, C 3588.03
...
77, 2006-01-02 15:25:00, Week 1, Day 1, O 3599.07, H 3599.68, L 3598.47, C 3599.68
strategy notify_timer with tid 0, when 2006-01-02 15:30:00 cheat False
78, 2006-01-02 15:30:00, Week 1, Day 1, O 3599.64, H 3599.73, L 3599.0, C 3599.67
...
179, 2006-01-03 15:25:00, Week 1, Day 2, O 3634.72, H 3635.0, L 3634.06, C 3634.87
strategy notify_timer with tid 0, when 2006-01-03 15:30:00 cheat False
180, 2006-01-03 15:30:00, Week 1, Day 2, O 3634.81, H 3634.89, L 3634.04, C 3634.23
...
计时器按要求在15:30
启动。日志显示它在前两天的第 1 次中如何做到这一点。
将15 分钟
的重复加入混合中
$ ./scheduled-min.py --strat when='datetime.time(15, 30)',repeat='datetime.timedelta(minutes=15)'
...
74, 2006-01-02 15:10:00, Week 1, Day 1, O 3596.12, H 3596.63, L 3595.92, C 3596.63
75, 2006-01-02 15:15:00, Week 1, Day 1, O 3596.36, H 3596.65, L 3596.19, C 3596.65
76, 2006-01-02 15:20:00, Week 1, Day 1, O 3596.53, H 3599.13, L 3596.12, C 3598.9
77, 2006-01-02 15:25:00, Week 1, Day 1, O 3599.07, H 3599.68, L 3598.47, C 3599.68
strategy notify_timer with tid 0, when 2006-01-02 15:30:00 cheat False
78, 2006-01-02 15:30:00, Week 1, Day 1, O 3599.64, H 3599.73, L 3599.0, C 3599.67
79, 2006-01-02 15:35:00, Week 1, Day 1, O 3599.61, H 3600.29, L 3599.52, C 3599.92
80, 2006-01-02 15:40:00, Week 1, Day 1, O 3599.96, H 3602.06, L 3599.76, C 3602.05
strategy notify_timer with tid 0, when 2006-01-02 15:45:00 cheat False
81, 2006-01-02 15:45:00, Week 1, Day 1, O 3601.97, H 3602.07, L 3601.45, C 3601.83
82, 2006-01-02 15:50:00, Week 1, Day 1, O 3601.74, H 3602.8, L 3601.63, C 3602.8
83, 2006-01-02 15:55:00, Week 1, Day 1, O 3602.53, H 3602.74, L 3602.33, C 3602.61
strategy notify_timer with tid 0, when 2006-01-02 16:00:00 cheat False
84, 2006-01-02 16:00:00, Week 1, Day 1, O 3602.58, H 3602.75, L 3601.81, C 3602.14
85, 2006-01-02 16:05:00, Week 1, Day 1, O 3602.16, H 3602.16, L 3600.86, C 3600.96
86, 2006-01-02 16:10:00, Week 1, Day 1, O 3601.2, H 3601.49, L 3600.94, C 3601.27
...
strategy notify_timer with tid 0, when 2006-01-02 17:15:00 cheat False
99, 2006-01-02 17:15:00, Week 1, Day 1, O 3603.96, H 3603.96, L 3602.89, C 3603.79
100, 2006-01-02 17:20:00, Week 1, Day 1, O 3603.94, H 3605.95, L 3603.87, C 3603.91
101, 2006-01-02 17:25:00, Week 1, Day 1, O 3604.0, H 3604.76, L 3603.85, C 3604.64
strategy notify_timer with tid 0, when 2006-01-02 17:30:00 cheat False
102, 2006-01-02 17:30:00, Week 1, Day 1, O 3604.06, H 3604.41, L 3603.95, C 3604.33
103, 2006-01-03 09:05:00, Week 1, Day 2, O 3604.08, H 3609.6, L 3604.08, C 3609.6
104, 2006-01-03 09:10:00, Week 1, Day 2, O 3610.34, H 3617.31, L 3610.34, C 3617.31
105, 2006-01-03 09:15:00, Week 1, Day 2, O 3617.61, H 3617.87, L 3616.03, C 3617.51
106, 2006-01-03 09:20:00, Week 1, Day 2, O 3617.24, H 3618.86, L 3616.09, C 3618.42
...
179, 2006-01-03 15:25:00, Week 1, Day 2, O 3634.72, H 3635.0, L 3634.06, C 3634.87
strategy notify_timer with tid 0, when 2006-01-03 15:30:00 cheat False
180, 2006-01-03 15:30:00, Week 1, Day 2, O 3634.81, H 3634.89, L 3634.04, C 3634.23
...
如预期的那样,第 1 次调用在15:30
触发,然后每 15 分钟重复一次,直到会话结束于17:30
。当新会话开始时,计时器再次被重置为15:30
。
现在在会话开始前作弊
$ ./scheduled-min.py --strat when='bt.timer.SESSION_START',cheat=True
strategy notify_timer with tid 1, when 2006-01-02 09:00:00 cheat True
-- 2006-01-02 09:05:00 Create buy order
strategy notify_timer with tid 0, when 2006-01-02 09:00:00 cheat False
1, 2006-01-02 09:05:00, Week 1, Day 1, O 3578.73, H 3587.88, L 3578.73, C 3582.99
-- 2006-01-02 09:10:00 Buy Exec @ 3583.01
2, 2006-01-02 09:10:00, Week 1, Day 1, O 3583.01, H 3588.4, L 3583.01, C 3588.03
...
订单创建于09:05:00
,执行于09:10:00
,因为经纪人不处于开盘欺骗模式。让我们设置它…
$ ./scheduled-min.py --strat when='bt.timer.SESSION_START',cheat=True --broker coo=True
strategy notify_timer with tid 1, when 2006-01-02 09:00:00 cheat True
-- 2006-01-02 09:05:00 Create buy order
strategy notify_timer with tid 0, when 2006-01-02 09:00:00 cheat False
-- 2006-01-02 09:05:00 Buy Exec @ 3578.73
1, 2006-01-02 09:05:00, Week 1, Day 1, O 3578.73, H 3587.88, L 3578.73, C 3582.99
2, 2006-01-02 09:10:00, Week 1, Day 1, O 3583.01, H 3588.4, L 3583.01, C 3588.03
...
下达时间和执行时间为09:05:00
,执行价格为09:05:00
的开盘价。
定时器允许通过传递一周的日期列表(遵循 iso 规范的整数,其中星期一为 1,星期日为 7)来指定它们应该在哪些日期执行,如下所示:
weekdays=[5]
,这将要求定时器仅在星期五有效
如果星期五是非交易日,并且定时器应在下一个交易日触发,则可以添加 weekcarry=True
类似于它,可以决定每月的第 15 天采取行动:
monthdays=[15]
如果第 15 天碰巧是非交易日,并且定时器应在下一个交易日触发,则可以添加 monthcarry=True
没有实现像 三月、六月、九月和十二月的第 3 个星期五(期货/期权到期日)的规则,但可以通过传递实现规则的可能性来实现:
allow=callable
,可调用的接受 datetime.date
实例。请注意,这不是 datetime.datetime
实例,因为 allow 可调用仅用于决定某一天是否适合用于定时器。
要实现类似上述规则的东西:
class FutOpExp(object):
def __init__(self):
self.fridays = 0
self.curmonth = -1
def __call__(self, d):
_, _, isowkday = d.isocalendar()
if d.month != self.curmonth:
self.curmonth = d.month
self.fridays = 0
# Mon=1 ... Sun=7
if isowkday == 5 and self.curmonth in [3, 6, 9, 12]:
self.fridays += 1
if self.friday == 3: # 3rd Friday
return True # timer allowed
return False # timer disallowed`
并且可以将 allow=FutOpeExp()
传递给定时器的创建
这将允许定时器在这些月份的第 3 个星期五触发,并在期货到期前可能平仓。
add_timer
的参数* `when`: can be
* `datetime.time` instance (see below `tzdata`)
* `bt.timer.SESSION_START` to reference a session start
* `bt.timer.SESSION_END` to reference a session end
offset
必须是 datetime.timedelta
实例
用于偏移值 when
。它在与 SESSION_START
和 SESSION_END
结合使用时有实际用途,以指示定时器在会话开始后 15 分钟
被调用。
repeat
必须是 datetime.timedelta
实例在第 1 次调用之后指示是否在同一会话中按照预定的 repeat
间隔安排进一步调用
一旦定时器超过会话结束,它将被重置为 when
的原始值
weekdays
:一个排序的整数迭代器,指示定时器实际上可以在哪些日期(iso 代码,星期一为 1,星期日为 7)被调用如果未指定,定时器将在所有日期上都活动
weekcarry
(默认值:False
)。如果为 True
并且未见到工作日(例如:交易假期),则定时器将在下一天执行(即使在新的一周中)
monthdays
:一个排序的整数迭代器,指示定时器应在每月的哪些日子执行。例如,总是在月份的 15 号执行
如果未指定,定时器将在所有日期上都活动
monthcarry
(默认值:True
)。如果当天没有见过(周末,交易假日),则定时器将在下一个可用的日期执行。
allow
(默认值:None
)。一个回调,接收 datetime.date
实例并返回 True
(如果日期适用于定时器)或返回 False
tzdata
可以是 None
(默认值),一个 pytz
实例或一个 data feed
实例。
None
:when
按照字面值解释(即使不是),这意味着将其视为 UTC 处理
pytz
实例:when
将被解释为指定时区实例指定的本地时间。
数据源
实例:when
将被解释为在数据源实例的 tz
参数指定的本地时间。
!!! 注意
If `when` is either `SESSION_START` or `SESSION_END` and `tzdata` is
`None`, the 1st *data feed* in the system (aka `self.data0`) will be
used as the reference to find out the session times.`
strats
(默认为:False
)还要调用策略的 notify_timer
cheat
(默认为 False
)如果设为 True
,则计时器将在经纪人有机会评估订单之前被调用。这样可以在会话开始之前,例如根据开盘价发出订单的机会。
*args
:任何额外的参数都将传递给 notify_timer
**kwargs
:任何额外的关键字参数都将传递给 notify_timer
scheduled.py
$ ./scheduled.py --help
usage: scheduled.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: )
scheduled-min.py
$ ./scheduled-min.py --help
usage: scheduled-min.py [-h] [--data0 DATA0] [--fromdate FROMDATE]
[--todate TODATE] [--cerebro kwargs] [--broker kwargs]
[--sizer kwargs] [--strat kwargs] [--plot [kwargs]]
Timer Test Intraday
optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data to read in (default: ../../datas/2006-min-005.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: )
scheduled.py
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import backtrader as bt
class St(bt.Strategy):
params = dict(
when=bt.timer.SESSION_START,
timer=True,
cheat=False,
offset=datetime.timedelta(),
repeat=datetime.timedelta(),
weekdays=[],
)
def __init__(self):
bt.ind.SMA()
if self.p.timer:
self.add_timer(
when=self.p.when,
offset=self.p.offset,
repeat=self.p.repeat,
weekdays=self.p.weekdays,
)
if self.p.cheat:
self.add_timer(
when=self.p.when,
offset=self.p.offset,
repeat=self.p.repeat,
cheat=True,
)
self.order = None
def prenext(self):
self.next()
def next(self):
_, isowk, isowkday = self.datetime.date().isocalendar()
txt = '{}, {}, Week {}, Day {}, O {}, H {}, L {}, C {}'.format(
len(self), self.datetime.datetime(),
isowk, isowkday,
self.data.open[0], self.data.high[0],
self.data.low[0], self.data.close[0])
print(txt)
def notify_timer(self, timer, when, *args, **kwargs):
print('strategy notify_timer with tid {}, when {} cheat {}'.
format(timer.p.tid, when, timer.p.cheat))
if self.order is None and timer.p.cheat:
print('-- {} Create buy order'.format(self.data.datetime.date()))
self.order = self.buy()
def notify_order(self, order):
if order.status == order.Completed:
print('-- {} Buy Exec @ {}'.format(
self.data.datetime.date(), order.executed.price))
def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()
# Data feed kwargs
kwargs = dict(
timeframe=bt.TimeFrame.Days,
compression=1,
sessionstart=datetime.time(9, 0),
sessionend=datetime.time(17, 30),
)
# 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()
scheduled-min.py
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
import datetime
import backtrader as bt
class St(bt.Strategy):
params = dict(
when=bt.timer.SESSION_START,
timer=True,
cheat=False,
offset=datetime.timedelta(),
repeat=datetime.timedelta(),
weekdays=[],
weekcarry=False,
monthdays=[],
monthcarry=True,
)
def __init__(self):
bt.ind.SMA()
if self.p.timer:
self.add_timer(
when=self.p.when,
offset=self.p.offset,
repeat=self.p.repeat,
weekdays=self.p.weekdays,
weekcarry=self.p.weekcarry,
monthdays=self.p.monthdays,
monthcarry=self.p.monthcarry,
# tzdata=self.data0,
)
if self.p.cheat:
self.add_timer(
when=self.p.when,
offset=self.p.offset,
repeat=self.p.repeat,
weekdays=self.p.weekdays,
weekcarry=self.p.weekcarry,
monthdays=self.p.monthdays,
monthcarry=self.p.monthcarry,
# tzdata=self.data0,
cheat=True,
)
self.order = None
def prenext(self):
self.next()
def next(self):
_, isowk, isowkday = self.datetime.date().isocalendar()
txt = '{}, {}, Week {}, Day {}, O {}, H {}, L {}, C {}'.format(
len(self), self.datetime.datetime(),
isowk, isowkday,
self.data.open[0], self.data.high[0],
self.data.low[0], self.data.close[0])
print(txt)
def notify_timer(self, timer, when, *args, **kwargs):
print('strategy notify_timer with tid {}, when {} cheat {}'.
format(timer.p.tid, when, timer.p.cheat))
if self.order is None and timer.params.cheat:
print('-- {} Create buy order'.format(
self.data.datetime.datetime()))
self.order = self.buy()
def notify_order(self, order):
if order.status == order.Completed:
print('-- {} Buy Exec @ {}'.format(
self.data.datetime.datetime(), order.executed.price))
def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()
# Data feed kwargs
kwargs = dict(
timeframe=bt.TimeFrame.Minutes,
compression=5,
sessionstart=datetime.time(9, 0),
sessionend=datetime.time(17, 30),
)
# 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=(
'Timer Test Intraday'
)
)
parser.add_argument('--data0', default='../../datas/2006-min-005.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()