BackTrader 中文文档(十二)

原文:www.backtrader.com/

Visual Chart

原文:www.backtrader.com/docu/live/vc/vc/

与 Visual Chart 的集成支持两者:

  • 实时数据提供

  • 实时交易

Visual Chart是完整的交易解决方案:

  • 在单个平台上集成图表、数据源和经纪功能

    更多信息,请访问:www.visualchart.com

需求

  • VisualChart 6

  • Windows - VisualChart 正在运行的平台

  • comtypes分支:github.com/mementum/comtypes

    使用以下命令安装:pip install https://github.com/mementum/comtypes/archive/master.zip

    Visual Chart API 基于COM

    当前的comtypes主分支不支持对VT_RECORDVT_ARRAYS的解包。这是由Visual Chart使用的

    Pull Request #104已提交但尚未集成。一旦集成,就可以使用主分支。

  • pytz(可选,但真的很推荐)

    确保每个数据都在市场时间内返回。

    对大多数市场而言这是真实的,但有些市场确实是例外情况(全球指数是一个很好的例子)

    Visual Chart内部的时间管理及其与COM传递的时间的关系是复杂的,并且使用pytz倾向于简化事情。

示例代码

源代码包含一个完整的示例:

  • samples/vctest/vctest.py

示例无法涵盖每种可能的用例,但它试图提供广泛的见解,并应强调在使用回测模块或实时数据模块时没有真正的区别。

可以指出一件事:

  • 在进行任何交易活动之前,示例等待data.LIVE数据状态通知。

    这可能是任何实时策略中需要考虑的事情。

VCStore - 存储库

存储库是实时数据源/交易支持的关键,为COM API 和数据源以及经纪人代理的需求之间提供一层适应性。

  • 提供获取经纪人实例的方法:

    • VCStore.getbroker(*args, **kwargs)
  • 提供对获取器数据源实例的访问

    • VCStore.getedata(*args, **kwargs)

    在这种情况下,许多**kwargs对于数据源都是常见的,如datanamefromdatetodatesessionstartsessionendtimeframecompression

    数据可能提供其他参数。请查看下面的参考资料。

VCStore将尝试:

  • 使用Windows Registry自动定位VisualChart在系统中的位置

    • 如果找到,将扫描安装目录以查找COM DLL 以创建COM typelibs,并能够实例化适当的对象。

    • 如果未找到,则将尝试使用已知的和硬编码的CLSIDs执行相同操作。

注意

即使可以通过扫描文件系统找到 DLL,Visual Chart本身也必须正在运行。backtrader 不会启动Visual Chart

VCStore的其他职责:

  • 保持对 Visual Chart 与服务器的连接状态的一般跟踪

VCData feeds

通用

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 左上方看到的符号传递,不包括空格。 例如:

    • ES-Mini 显示为 001 ES。 实例化它为:
    data = vcstore.getdata(dataname='001ES', ...)` 
    
    • EuroStoxx 50 显示为 015 ES。 实例化它为:
    data = vcstore.getdata(dataname='015ES', ...)` 
    

注意

backtrader 将尽力清除位于名称直接从 Visual Chart 粘贴的第四个位置的空格。

时间管理

时间管理遵循 backtrader 的一般规则

  • 给出市场时间,以确保代码不依赖于在不同时间发生 DST 转换,并使本地时间对时间比较不可靠。

这适用于 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 传递 fromdatetodate 似乎会在 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,平台将自动尝试回填,然后继续实时数据。回填是时间框架相关的,如下:

  • TicksMicroSecondsSeconds1 天

    对于给定的 3 个时间框架,微秒不受Visual Chart直接支持,通过Ticks的重新采样完成

  • 分钟2 天

  • 天数1 年

  • 2 年

  • 月份5 年

  • 月份20 年

定义的回填期将乘以请求的压缩,即:如果时间框架分钟压缩是 5,则最终回填期将是:2 天 * 5 -> 10 天

交易数据

Visual Chart提供连续未来。无需手动管理,您可以跟踪所选的未来而无需中断。这是一个优势,也提出了一个小挑战:

  • ES-Mini001ES,但实际交易资产(例如:Sep-2016)是ESU16

为了克服这一点,并允许跟踪连续未来并交易真实资产的策略,在数据实例化期间可以指定以下内容:

data = vcstore.getdata(dataname='001ES', tradename='ESU16')

交易将在ESU16上进行,但数据源将来自001ES(数据相同,为期 3 个月)

其他参数

  • qcheck(默认:0.5秒)控制唤醒与内部重新采样器/重播器交流的频率,以避免延迟传递条形图。

    将应用以下逻辑来使用此参数:

    • 如果检测到内部重新采样/重播,则将使用该值。

    • 如果未检测到内部重新采样/重播,数据源将不会唤醒,因为没有要报告的内容。

    数据源仍将唤醒以检查Visual Chart内置的重新采样器,但这是自动控制的。

数据通知

数据源将通过以下一种或多种方式报告当前状态(检查CerebroStrategy参考)

  • 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

    在这种情况下,不再可能检索数据,数据将指示系统无法执行任何操作。可能的情况:

    • 指定了错误的合同

    • 在历史下载期间中断

    • 尝试重新连接到 TWS 的次数超过了

  • CONNBROKEN

    连接已丢失,无论是到 TWS 还是到数据中心。数据源将尝试(通过存储)重新连接和回填,必要时,并恢复操作

  • NOTSUBSCRIBED

    合同和连接正常,但由于权限不足,无法检索数据。

    数据将指示系统无法检索数据

  • DELAYED

    表示历史/回填操作正在进行中,并且策略处理的数据不是实时数据

  • LIVE

    表示从这一点开始要处理的数据是实时数据

策略的开发人员应考虑在发生断开连接或接收延迟数据时采取哪些行动。

VCBroker - 实时交易

使用经纪人

要使用VCBroker,必须替换由cerebro创建的标准经纪人模拟实例。

使用Store模型(首选):

import backtrader as bt

cerebro = bt.Cerebro()
vcstore = bt.stores.VCStore()
cerebro.broker = vcstore.getbroker()  # or cerebro.setbroker(...)

经纪人参数

无论是直接还是通过getbrokerVCBroker经纪人都不支持参数。 这是因为经纪人只是对真实经纪人的代理。 真正的经纪人给出的,不应被拿走。

限制

仓位

Visual Chart报告持仓。 这在大多数情况下可以用来控制实际仓位,但缺少指示仓位已关闭的最终事件。

这使得backtrader必须对Position进行完整的会计核算,并与您帐户中任何先前存在的仓位分开

佣金

COM交易界面不报告佣金。 backtrader没有机会做出合理猜测,除非:

  • 经纪人与指示实际发生的佣金的佣金实例一起实例化。

与其进行交易

账户

Visual Chart在一个经纪人上同时支持多个帐户。 可以使用以下参数控制选择的帐户:

  • account(默认值:None

    VisualChart 支持同时在经纪人上使用多个帐户。 如果使用默认值None,则将使用 ComTrader Accounts集合中的第一个帐户。

    如果提供了帐户名称,则将检查并使用Accounts集合(如果存在)

操作

关于标准用法没有变化。 只需使用策略中可用的方法(有关详细说明,请参阅Strategy参考)

  • buy

  • sell

  • close

  • cancel

返回的订单对象

  • 标准backtrader Order对象

订单执行类型

Visual Chart支持backtrader所需的最小订单执行类型,因此,任何经过回测的内容都可以实时执行。

因此,订单执行类型仅限于经纪人模拟中可用的类型:

  • Order.Market

  • Order.Close

  • Order.Limit

  • Order.Stop(当Stop触发时,Market订单随后进行)

  • Order.StopLimit(当Stop触发时,Limit订单随后进行)

订单有效性

在回测期间可用的相同有效性概念(用于validbuysell)可用,并具有相同含义。 因此,对于以下值,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

  • 已接受 - 订单已下达

  • 已拒绝 - 订单放置失败或在其生命周期内被系统取消

  • 部分完成 - 部分执行已经发生

  • 已完成 - 订单已完全执行

  • 已取消(或 取消

  • 已过期 - 目前尚未报告。需要一种启发式方法来区分此状态与 取消 的区别

参考

VCStore

类 backtrader.stores.VCStore()

包装 ibpy ibConnection 实例的单例类。

这些参数也可以在使用该存储的类中指定,例如 VCDataVCBroker

VCBroker

类 backtrader.brokers.VCBroker(**kwargs)

VisualChart 的经纪商实现。

此类将 VisualChart 的订单/持仓映射到 backtrader 的内部 API。

参数:

  • 账户(默认值:None)

    VisualChart 支持在经纪商上同时使用多个账户。如果默认值为 None,则将使用 ComTrader Accounts 集合中的第 1 个账户。

    如果提供了账户名称,则将检查并使用 Accounts 集合(如果存在)

  • 佣金(默认值:None)

    如果未传递佣金方案,则将自动生成对象

    有关更多解释,请参阅下面的注释

注释

  • 持仓

VisualChart 通过 ComTrader 接口报告“OpenPositions”更新,但仅当持仓具有“大小”时。指示持仓已移至零的更新通过不存在此类持仓来报告。这强制通过观察执行事件来保持持仓的会计,就像模拟经纪商一样。

  • 佣金

VisualChart 的 ComTrader 接口不报告佣金,因此自动生成的 CommissionInfo 对象不能使用不存在的佣金来正确计算它们。为了支持佣金,必须传递带有适当佣金方案的 commission 参数。

佣金方案的文档详细介绍了如何实现这一点

  • 过期时间

ComTrader 接口(或者是 comtypes 模块?)从 datetime 对象中丢弃了 time 信息,并且到期日期始终是完整日期。

  • 过期报告

目前还没有启发式方法来确定取消的订单何时因过期而取消。因此,过期订单被报告为已取消。

VCData

类 backtrader.feeds.VCData(**kwargs)

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)连续期货无法交易,但非常适合数据跟踪。如果提供了该参数,它将是当前期货的名称,该名称将成为交易资产。示例:

    • 001ES -> ES-Mini 连续提供作为dataname

    • ESU16 -> ES-Mini 2016-09. 如果在tradename中提供了此信息,它将成为交易资产。

  • usetimezones(默认值:True)对于大多数市场,Visual Chart提供的时间偏移信息允许将日期时间转换为市场时间(backtrader选择的表示方式)

    一些市场是特殊的(096),需要特殊的内部覆盖和时区支持以显示用户预期的市场时间。

    如果该参数设置为True,将尝试导入pytz以使用时区(默认值)

    禁用它将取消时区使用(可能有助于减轻负载过重的情况)

绘图

绘图

www.backtrader.com/docu/plotting/plotting/的原文:

尽管回测是基于数学计算的自动化过程,但通常情况下,人们希望实际可视化正在发生的情况。 无论是使用经过回测运行的现有算法,还是查看真正的指标(内置或自定义)与数据一起提供了什么。

而且因为一切都有人类的背后,绘制数据源、指标、操作、现金和组合价值的发展可以帮助人类更好地理解正在发生的事情,丢弃/修改/创建想法以及查看图表的人类可以用视觉信息做的任何事情。

这就是为什么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()

这产生了以下图表。

image

图表包括 3 个观察器,在这种情况下,考虑到没有任何交易,它们基本上是没有意义的

  • 一个CashValue观察器,如其名称所示,在回测运行的生命周期内跟踪Cash和总投资组合Value(包括现金)

  • 一个Trade观察器,在一次交易结束时显示实际的盈亏

    交易被定义为开仓并将仓位调回0(直接或从多头到空头或空头到多头)

  • 一个BuySell观察器,在价格之上绘制买入卖出操作的位置

3 个观察器是由cerebro自动添加的,并且通过stdstats参数(默认为True)进行控制。 如果希望禁用它们,请执行以下操作:

cerebro = bt.Cerebro(stdstats=False)

或者稍后运行时,例如:

cerebro = bt.Cerebro()
...
cerebro.run(stdstats=False)

绘制的元素

尽管在介绍中已经提到了Observers,但它们并不是唯一要绘制的元素。 这三个东西被绘制出来:

  • 使用adddatareplaydataresampledata数据源添加到 Cerebro

  • 在策略级别声明的指标(或者使用addindicator将其添加到 cerebro,这纯粹是为了实验目的,并将指标添加到虚拟策略中)

  • 使用addobserver观察器添加到 cerebro 的选项

    观察器是lines对象,它们与strategy同步运行,并且可以访问整个生态系统,以便跟踪CashValue等情况

绘图选项

指标观察器有几个选项,控制它们在图表上的绘制方式。 有三大组:

  • 影响整个对象绘图行为的选项

  • 影响单个线条绘图行为的选项

  • 影响系统范围绘图选项的选项

对象范围的绘图选项

这些由 IndicatorsObservers 中的数据集控制:

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 条单独线的名称(midtopbot)。

    对于 BuySell 观察者的用例,将显示 2 条线和其标记的名称是有意义的:BuySell,以便清楚地告诉最终用户什么是什么。

  • plotlinevalues:控制指标和观察者中的线条图例是否具有最后绘制的值。可以使用每条线的 _plotvalue 控制单个线的显示方式。

  • plotvaluetags:控制是否在线条右侧绘制带有最后值的值标签。可以使用每条线的 _plotvaluetag 控制单个线的显示方式。

  • plotymargin:在图表上单个子图的顶部和底部添加的边距

    这是一个基于 1 的百分比。例如:0.05 -> 5%

  • plothlines:包含在比例尺内需要绘制 水平线 的值的 可迭代对象

    例如,这有助于经典指标,如RSI,通常在7030处绘制线条的超买超卖区域。

  • plotyticks:包含在比例尺上必须特别放置值刻度的可迭代对象

    例如,为了强制比例尺具有50来识别比例尺的中点。尽管这似乎很明显,指标使用自动缩放机制,如果一个具有0-100比例尺的指标在 30-95 之间定期移动,50可能不明显位于中心。

  • plotyhlines:包含在比例尺上必须绘制水平线的值(在比例尺内)。

    这可以同时控制plothlinesplotyticks

    如果上述选项都未定义,则水平线和刻度的放置完全由此值控制

    如果上述任何选项被定义,它们将优先于此选项中的值

  • 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))` 
    

    alphawidthmatplotlib的选项

  • _fill_gt / _fill_lt

    允许在给定线路和之间填充:

    • 另一条线路

    • 一个数值

    参数是一个包含 2 个元素的可迭代对象,其中:

    • 第一个参数是一个字符串(参考线路名称)或数值

      填充将在自身值和线路或数值的值之间完成

    • 第二个参数是:

      • 一个字符串,带有颜色名称(matplotlib兼容)或十六进制规范(参见matloplit示例)

      • 一个可迭代对象,其中第一个元素是颜色的字符串/十六进制值,第二个元素是指定 alpha 透明度的数值(默认值: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')
)

buysell线路有一些选项,直接传递给matplotlib以定义markermarkersizecolorfillstyle。所有这些选项都在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 线将以 虚线 样式绘制,而 topbot 线将与 mid 线具有相同的颜色。

Stochastic(在 _StochasticBase 中定义并继承):

lines = ('percK', 'percD',)
...
plotlines = dict(percD=dict(_name='%D', ls='--'),
                 percK=dict(_name='%K'))

较慢的线 percD虚线 样式绘制。并且线的名称更改为包含花哨的 % 符号(%K%D),在 Python 中无法在名称定义中使用

控制绘图的方法

处理 IndicatorsObservers 时,支持以下方法以进一步控制绘图:

  • _plotlabel(self)

    应该返回一列东西,以符合将在 IndicatorsObserver 名称后放置在括号中的标签

    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 部分)。

    参数 upperbandlowerband 的值将用于此操作,因为参数可以由最终用户更改,所以无法事先知道

系统范围内的绘图选项

首先是在 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 对象。

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 中的颜色

PlotScheme 类定义了一个方法,可以在子类中重写,该方法返回下一个要使用的颜色:

def color(self, idx)

其中idx是当前正在绘制的单个子图上的行的索引。例如,MACD绘制了 3 条线,因此idx变量只会有以下值:012。下一个图表(可能是另一个指标)将从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 LightTableau 20颜色调色板的定义。

绘制日期范围

原文:www.backtrader.com/docu/plotting/ranges/plotting-date-ranges/

发布的1.9.31.x版本增加了制作部分图表的功能。

  • 要么使用策略实例中保留的时间戳完整数组的索引

  • 或者使用实际的datetime.datedatetime.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()

在同一轴上绘制

原文:www.backtrader.com/docu/plotting/sameaxis/plot-sameaxis/

前一篇帖子中的未来点,是在同一空间上绘制了原始数据和略微(随机)修改的数据,但没有在同一轴上。

从那篇帖子中恢复第一张图片。

image

人们可以看到:

  • 图表的左右两侧有不同的比例尺

  • 当观察摆动的红线(随机数据)围绕原始数据振荡 +- 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

以及产生的图表

image

要注意:

  • 右侧只有一个比例尺

  • 现在随机数据似乎明显围绕着原始数据振荡,这是预期的视觉行为

示例用法

$ ./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()

日期时间

日期时间管理

原文:www.backtrader.com/docu/timemgmt/

直到1.5.0版本发布之前,backtrader使用直接方法管理时间,即数据源计算的任何日期时间都会直接使用。

并且对于任何用户输入,例如参数 fromdate(或sessionstart),都可以提供给任何数据源

在进行回测时,直接控制冻结数据源是可以的。在数据进入系统之前,很容易假设输入的日期时间已经得到处理。

但是在 1.5.0 版本中,实时 数据源被支持了,这就需要考虑日期时间管理。如果以下情况始终为true,则不需要进行此类管理:

  • 纽约的交易员交易 ES-Mini。时区为US/Eastern(或其中一个别名)

  • 柏林的交易员交易 DAX 期货。在这种情况下,适用于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实例。

注意

用户输入,比如例如fromdatesessionstart参数,预计会与实际的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  #

比以上工作还要少。

显然,上面示例中的MyFeedMyFeedAuto只是虚拟名称。

注意

在撰写本文时,分发中唯一能够自动确定时区的数据源是连接到交互经纪商的那个。

计时器

原文:www.backtrader.com/docu/timers/timers/

发布1.9.44.116添加了计时器backtrader可用工具的工具库中。此功能允许在给定时间点获得对notify_timer(在CerebroStrategy中可用)的回调,用户可以对其进行细粒度的控制。

注意

1.9.46.116中进行了一些更正

选项

  • 基于绝对时间输入或与会话开始/结束时间相关的计时器

  • 时区规范用于时间规范,无论是直接还是通过pytz兼容对象或通过数据源会话结束时间

  • 与指定时间相关的起始偏移量

  • 重复间隔

  • 星期过滤器(带有继续选项)

  • 月份过滤器(带有继续选项)

  • 自定义回调过滤器

使用模式

CerebroStrategy子类中,计时器回调将在以下方法中收到。

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实例。

有关参数的解释,请参见下文。

添加计时器 - 通过 Cerebro

与相同的方法完成,并只添加参数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=[],
    )

数据的会话时间如下:

  • 开始:09:00

  • 结束:17:30

仅使用时间运行

$ ./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(请在上面的示例中查看分配的tid0

10之前被调用,因为该计时器是作弊的,且在系统中许多事件发生之前被调用(有关说明,请参见上文)

由于价格的每日分辨率,这并没有太大的区别,除了:

  • 策略还在开盘前发布了订单…并且它与次日的开盘价匹配

    即使在开盘前作弊,这仍然是正常行为,因为经纪人中也没有激活开盘欺骗

相同,但经纪人处于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以开盘价执行

    事实上,就像在市场真正开放之前几秒钟就已经行动一样。

使用 5 分钟柱形图运行

示例scheduled-min.py默认以backtrader分发的标准 5 分钟柱形图运行。策略的参数被扩展以包括monthdayscarry选项

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

数据具有相同的会话时间:

  • 开始:09:00

  • 结束:17:30

让我们做一些实验。首先是单个计时器。

$ ./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_STARTSESSION_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 实例。

    Nonewhen 按照字面值解释(即使不是),这意味着将其视为 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()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/552005.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【在线OJ系统】自定义注解实现自增ID的无感插入

实现思路 首先自定义参数注解&#xff0c;然后根据AOP思想&#xff0c;找到该注解作用的切点&#xff0c;也就是mapper层对于mapper层的接口在执行前都会执行该aop操作&#xff1a;获取到对于的方法对象&#xff0c;根据方法对象获取参数列表&#xff0c;根据参数列表判断某个…

时序分解 | Matlab实现WOA-VMD鲸鱼算法WOA优化VMD变分模态分解

时序分解 | Matlab实现WOA-VMD鲸鱼算法WOA优化VMD变分模态分解 目录 时序分解 | Matlab实现WOA-VMD鲸鱼算法WOA优化VMD变分模态分解效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现WOA-VMD鲸鱼算法WOA优化VMD变分模态分解&#xff08;完整源码和数据) 1.利用鲸…

Semaphore信号量源码解读与使用

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Java全栈-专栏 &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正 目录 1. 前言 2. 什么是Semaphore&#xff1f; 3. Semaphore源码解读 3.1 acquire…

Linux系统的引导过程与服务控制

目录 一、Linux操作系统引导过程 二、Linux系统服务控制 系统初始化进程 三、运行级别切换 *运行级别及切换 Linux系统的运行级别 四、优化开机自动加载服务 五、修复MBR扇区故障 一、Linux操作系统引导过程 主要步骤 开机自检&#xff1a; 检测硬件设备&#…

C++从入门到精通——const与取地址重载

const与取地址重载 前言一、const正常用法const成员函数问题const对象可以调用非const成员函数吗非const对象可以调用const成员函数吗const成员函数内可以调用其它的非const成员函数吗非const成员函数内可以调用其它的const成员函数吗总结 二、取地址及const取地址操作符重载概…

小米汽车SU7隐藏款曝光!新配色和透明车身亮了 coreldraw教程入门零基础 coreldraw下载 coreldraw2024

刘强东说&#xff0c;论营销&#xff0c;没有任何人能比得过小米。 小米SU7发布会24小时&#xff0c;下定量就超过了蔚来汽车2023年四季度的交付量。 ▲雷军发布的小米SU7 24小时订单量 小米SU7发布会后五天&#xff0c;雷军在北京亦庄工厂亲自交付了第一批创世版本小米SU7&a…

黑马点评(四) -- 分布式锁

1 . 分布式锁基本原理和实现方式对比 分布式锁&#xff1a;满足分布式系统或集群模式下多进程可见并且互斥的锁。 分布式锁的核心思想就是让大家都使用同一把锁&#xff0c;只要大家使用的是同一把锁&#xff0c;那么我们就能锁住线程&#xff0c;不让线程进行&#xff0c;让…

gpt4.0人工智能网页版

在最新的AI基准测试中&#xff0c;OpenAI几天前刚刚发布的GPT-4-Turbo-2024-04-09版本&#xff0c;大幅超越了Claude3 Opus&#xff0c;重新夺回了全球第一的AI王座。 GPT-4-Turbo-2024-04-09版本是目前国内外最强的大模型&#xff0c;官网需要20美元每月才能使用&#xff0c;…

【UE5.1】使用MySQL and MariaDB Integration插件——(3)表格形式显示数据

在上一篇&#xff08;【UE5.1】使用MySQL and MariaDB Integration插件——&#xff08;2&#xff09;查询&#xff09;基础上继续实现以表格形式显示查询到的数据的功能 效果 步骤 1. 在“WBP_Query”中将多行文本框替换未网格面板控件&#xff0c;该控件可以用表格形式布局…

Pytest测试用例中的mark用法(包含代码示例与使用场景详解)

在软件开发中&#xff0c;测试是确保代码质量和功能稳定性的重要环节。Python作为一门流行的编程语言&#xff0c;拥有丰富的测试工具和框架&#xff0c;其中pytest是其中之一。pytest提供了丰富的功能来简化测试用例的编写&#xff0c;其中的mark功能允许我们对测试用例进行标…

理解思维链Chain of Thought(CoT)

Chain of Thought&#xff08;CoT&#xff09;&#xff0c;即“思维链”&#xff0c;是人工智能领域中的一个概念&#xff0c;特别是在自然语言处理和推理任务中。它指的是一种推理过程&#xff0c;其中模型在生成最终答案之前&#xff0c;先逐步推导出一系列的中间步骤或子目标…

【日常记录】【CSS】SASS循环的使用

文章目录 1、引言2、安装3、举例4、参考链接 1、引言 目前在任何项目框架中&#xff0c;都会有css 预处理器&#xff0c;目前一般使用 sass、less 这俩其中之一&#xff0c;它可以简化css的书写 Sass 是一款强化 CSS 的辅助工具&#xff0c;它在 CSS 语法的基础上增加了变量 (v…

推动企业档案数字化转型的措施

推动企业档案数字化转型的措施有以下几点&#xff1a; 1. 制定数字化转型战略&#xff1a;企业应该制定明确的数字化转型战略&#xff0c;明确企业数字化转型的目标、步骤和时间表&#xff0c;并将档案数字化转型作为其中的重要内容。 2. 投资数字化技术&#xff1a;企业应该投…

代码随想录:二叉树5(层序遍历全解)

目录 102.二叉树的层序遍历 107.二叉树的层序遍历II 199.二叉树的右视图 637.二叉树的层平均值 429.N叉树的层序遍历 501.在每个树行中找最大值 116.填充每个节点的下一个右侧节点指针 117.填充每个节点的下一个右侧节点指针II 104.二叉树的最大深度 111.二叉树的最大…

UE5 HLSL 详细学习笔记

这里的POSITION是变量Position的语义&#xff0c;告诉寄存器&#xff0c;此变量的保存位置&#xff0c;通常语义用于着色器的输入和输出&#xff0c;以冒号“&#xff1a;”的方式进一步说明此变量&#xff0c;COLOR也类似 还有什么语义呢&#xff1f; HLSL核心函数&#xff1a…

CAN的底层驱动

框架图 拆解链路模型 CAN子系统 can_controller Core 包含协议控制器和接收/发送移位寄存器。它可处理所有 ISO 11898-1: 2015 协议功能,并支持 11 位和 29 位标识符。

【数据结构】树与森林(树的存储结构、森林与二叉树的转化、树与森林的遍历)

目录 树和森林树的存储结构一、树的双亲表示法&#xff1a;二、树的孩子表示法方法一&#xff1a;定长结点的多重链表方法二&#xff1a;不定长结点的多重链表方法三&#xff1a;孩子单链表表示法 三、树的二叉链表(孩子-兄弟)存储表示法 森林与二叉树的转换树和森林的遍历先根…

Java中的容器

Java中的容器主要包括以下几类&#xff1a; Collection接口及其子接口/实现类&#xff1a; List 接口及其实现类&#xff1a; ArrayList&#xff1a;基于动态数组实现的列表&#xff0c;支持随机访问&#xff0c;插入和删除元素可能导致大量元素移动。LinkedList&#xff1a;基…

前端常见面试题:HTML+CSS

1. title与h1的区别、b与strong的区别、i与em的区别&#xff1f; title与h1的区别&#xff1a; title标签用于定义整个HTML文档的标题&#xff0c;它显示在浏览器窗口的标题栏或者标签页上。每个HTML文档只应该有一个title标签&#xff0c;它对搜索引擎优化&#xff08;SEO&a…

C语言结构体与公用体

结构体 概述 有时我们需要将不同类型的数据组合成一个有机的整体&#xff0c;如&#xff1a;一个学生有学号/姓名/性别/年龄/地址等属性这时候可通过结构体实现结构体(struct)可以理解为用户自定义的特殊的复合的“数据类型” 可以理解为其他语言的object类型 结构体变量的定…