BackTrader 中文文档(五)

原文:www.backtrader.com/

策略

策略

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

Cerebro 实例是 backtrader 的泵心和控制大脑。对于平台用户来说,Strategy 是相同的。

策略 在方法中表达的生命周期

注意

策略可以通过从模块 backtrader.errors 中引发 StrategySkipError 异常来在出生期间中断

这将避免在回测过程中执行策略。请参阅Exceptions 部分

  1. 概念:__init__

    显然这是在实例化期间调用的:indicators 将在此处创建以及其他所需的属性。例如:

    def __init__(self):
        self.sma = btind.SimpleMovingAverage(period=15)` 
    
  2. 出生:start

    世界(cerebro)告诉策略是时候开始行动了。存在一个默认的空方法。

  3. 童年期:prenext

    在概念阶段声明的 indicators 将对策略需要成熟的时间施加限制:这称为最小周期。在 __init__ 中创建了一个带有 period=15SimpleMovingAverage

    只要系统看到的条数少于 15 条,将调用 prenext(默认实现是空操作)

  4. 成年期:next

    一旦系统看到了 15 条数据,并且 SimpleMovingAverage 有足够大的缓冲区开始产生值,策略就足够成熟以实际执行。

    有一个 nextstart 方法,它被调用一次,标记从 prenextnext 的切换。nextstart 的默认实现只是调用 next

  5. 繁殖:None

    好的,策略实际上并没有繁殖。但从某种意义上来说,它们确实如此,因为系统将会多次实例化它们(使用不同的参数)如果优化

  6. 死亡:stop

    系统告诉策略,已经到了整理事务并做好准备的时间。存在一个默认的空方法。

对于大多数情况和常规使用模式,这将是这样的:

class MyStrategy(bt.Strategy):

    def __init__(self):
        self.sma = btind.SimpleMovingAverage(period=15)

    def next(self):
        if self.sma > self.data.close:
            # Do something
            pass

        elif self.sma < self.data.close:
            # Do something else
            pass

在这个片段中:

  • __init__ 期间,一个属性被分配了一个指标

  • 默认的空 start 方法没有被覆盖

  • prenextnexstart 没有被覆盖

  • next 中,指标的值与收盘价进行比较以执行某些操作

  • 默认的空 stop 方法没有被覆盖

策略,就像现实世界中的交易员一样,会在事件发生时收到通知。实际上,每个next周期在回测过程中都会发生一次。策略将:

  • 通过 notify_order(order) 通知订单状态变化

  • 通过 notify_trade(trade) 通知任何开仓/更新/关闭交易

  • 通过 notify_cashvalue(cash, value) 通知经纪人当前现金和投资组合

  • 通过 notify_fund(cash, value, fundvalue, shares) 通知经纪人当前现金和投资组合以及基金价值和份额的交易跟踪

  • 通过 notify_store(msg, *args, **kwargs) 通知事件(具体实现)

    请参阅 Cerebro 以了解store通知的说明。即使它们也已经传递给了一个cerebro实例(通过覆盖notify_store方法或通过回调传递),这些通知也将传递给策略

而且策略也像交易员一样在next方法期间有机会在市场中操作,以尝试获得利润

  • buy方法,用于做多或减少/平仓空头头寸

  • sell方法,用于做空或减少/平仓多头头寸

  • close方法,显然关闭现有头寸

  • cancel方法,用于取消尚未执行的订单

如何购买/出售/平仓

BuySell方法生成订单。调用时,它们返回一个Order(或子类)实例,可用作引用。此订单具有唯一的ref标识符,可用于比较

注意

特定经纪人实现的Order子类可能携带经纪人提供的其他唯一标识符

要创建订单,请使用以下参数:

  • data(默认值:None

    必须为其创建订单的数据。如果是None,那么系统中的第一个数据,self.datas[0] or self.data0(又名self.data)将被使用

  • size(默认值:None

    要使用的数据单位(正数)的大小,用于订单

    如果是None,则将使用通过getsizer检索的sizer实例来确定大小。

  • price(默认值:None

    要使用的价格(现实经纪人可能对实际格式施加限制,如果不符合最低跳动大小要求)

    对于MarketClose订单,None是有效的(市场确定价格)

    对于LimitStopStopLimit订单,此值确定触发点(在Limit的情况下,触发显然是订单应该匹配的价格)

  • plimit(默认值:None

    仅适用于StopLimit订单。这是触发Limit订单的价格,一旦Stop被触发(使用了price

  • exectype(默认值:None

    可能的值:

    • Order.MarketNone。市价单将以下一个可用价格执行。在回测中,它将是下一个柱的开盘价

    • Order.Limit。只能以给定的price或更好的价格执行的订单

    • Order.Stop。当价格达到price时触发的订单,并像Order.Market订单一样执行

    • Order.StopLimit。当价格达到price时触发的订单,并作为隐式Limit订单以由pricelimit给出的价格执行

  • valid(默认值:None

    可能的值:

    • None:这将生成一个不会过期的订单(又称Good til cancel),并保持在市场上直到匹配或取消。实际上,经纪人倾向于强加一个时间限制,但通常是在很远的时间内,可以考虑它不会过期

    • datetime.datetimedatetime.date实例:将使用该日期生成订单,有效期直到给定的日期时间(又称截止日期

    • Order.DAY0timedelta(): 生成到 Session End(又名 day 订单)为止的一天有效订单

    • numeric value: 假设这是对应于 matplotlib 编码中的日期时间的值(backtrader 使用的日期时间编码),将用于生成直到该时间有效的订单(good til date

  • tradeid(默认值: 0

    这是 backtrader 应用的内部值,用于跟踪同一资产上的重叠交易。当通知订单状态变化时,此 tradeid 将发送回 strategy

  • **kwargs: 其他经纪人实现可能支持额外的参数。 backtrader 将这些 kwargs 传递给创建的订单对象

    例如: 如果 backtrader 直接支持的 4 种订单执行类型不够用,例如 Interactive Brokers 的情况下可以将以下内容作为 kwargs 传递:

    orderType='LIT', lmtPrice=10.0, auxPrice=9.8` 
    

    这将覆盖 backtrader 创建的设置,并生成一个 touched 价格为 9.8, limit 价格为 10.0 的 LIMIT IF TOUCHED 订单。

信息位:

  • 策略具有与主数据(datas[0])长度相等的 length,当然可以用 len(self) 获得

    如果正在重播数据或传递了实时数据,并且新的数据到达相同时间点(长度)的话,可以调用 next 而不更改 长度

成员属性:

  • env: 此策略所在的 cerebro 实体

  • datas: 已传递给 cerebro 的数据源数组

    • data/data0 是 datas[0] 的别名

    • dataX 是 datas[X] 的别名

    数据源 也可以通过名称访问(参见参考资料),如果已为其分配了名称

  • dnames: 通过名称访问数据源的另一种方式(可以使用 [name].name 符号)

    例如如果对数据进行重采样如下:

    ...
    data0 = bt.feeds.YahooFinanceData(datname='YHOO', fromdate=..., name='days')
    cerebro.adddata(data0)
    cerebro.resampledata(data0, timeframe=bt.TimeFrame.Weeks, name='weeks')
    ...` 
    

    策略中可以像这样为每个创建指标:

    ...
    smadays = bt.ind.SMA(self.dnames.days, period=30)  # or self.dnames['days']
    smaweeks = bt.ind.SMA(self.dnames.weeks, period=10)  # or self.dnames['weeks']
    ...` 
    
  • broker: 引用与此策略相关联的经纪人(从 cerebro 接收)

  • stats: 保存由 cerebro 为该策略创建的观察者的列表/类似命名元组序列

  • analyzers: 保存由 cerebro 为该策略创建的分析器的列表/类似命名元组序列

  • position: 实际上是一个属性,用于给出 data0 的当前持仓。

    有用于检索所有仓位的方法(参见参考资料)

成员属性(用于统计/观察者/分析器):

  • _orderspending: 将在调用 next 之前通知给策略的订单列表

  • _tradespending: 将在调用 next 之前通知给策略的交易列表

  • _orders: 已经通知的订单列表。订单可以以不同的状态和不同的执行位多次出现在列表中。此列表用于保留历史记录。

  • _trades: 已通知的订单列表。交易可以像订单一样多次出现在列表中。

注意

请记住,prenextnextstartnext 可能会针对同一时间点被多次调用(当使用日线时,每次 ticks 更新价格时)

参考:Strategy

类 backtrader.Strategy(*args, **kwargs)

用于用户定义策略的基类。

next()

此方法将在满足所有数据/指标的最小期限后,为所有剩余数据点调用。

nextstart()

此方法将被调用一次,确切地在所有数据/指标的最小期限已满足时。默认行为是调用 next

prenext()

此方法将在策略开始执行之前,所有数据/指标的最小期限已满足时调用

start()

在回测即将开始之前调用。

stop()

在回测即将停止之前调用

notify_order(order)

每当有变化时接收订单

notify_trade(trade)

每当有变化时接收交易

notify_cashvalue(cash, value)

接收策略经纪人的当前基金价值、价值状态

notify_fund(cash, value, fundvalue, shares)

接收当前现金、价值、基金价值和基金份额

notify_store(msg, *args, **kwargs)

接收来自存储提供程序的通知

buy(data=None, size=None, price=None, plimit=None, exectype=None, valid=None, tradeid=0, oco=None, trailamount=None, trailpercent=None, parent=None, transmit=True, **kwargs)

创建买入(做多)订单并发送到经纪人

  • data(默认为 None

    为哪个数据创建订单。如果为 None,则系统中的第一个数据,self.datas[0] 或 self.data0(又名 self.data)将被使用

  • size(默认为 None

    要使用的数据单位(正数)来用于订单。

    如果为 None,则将使用通过 getsizer 获取的 sizer 实例来确定大小。

  • price(默认为 None

    用于价格(实时经纪人可能对实际格式施加限制,如果不符合最小 tick 大小要求)

    对于 MarketClose 订单,None 是有效的(市场决定价格)

    对于 LimitStopStopLimit 订单,此值确定触发点(在 Limit 的情况下,触发显然是订单应匹配的价格)

  • plimit(默认为 None

    仅适用于 StopLimit 订单。这是触发隐含 Limit 订单的价格,一旦 Stop 被触发(price 已被使用)

  • trailamount(默认为 None

    如果订单类型是 StopTrailStopTrailLimit,这是一个绝对数值,用于确定距离价格的距离(对于卖单是下方,对于买单是上方)以保持跟踪止损

  • trailpercent(默认为 None

    如果订单类型是 StopTrailStopTrailLimit,这是一个百分比数值,用于确定距离价格的距离(对于卖单是下方,对于买单是上方)以保持跟踪止损(如果还指定了 trailamount,它将被使用)

  • exectype (默认值: None)

    可能的值:

    • Order.MarketNone. 一个市价订单将以下一个可用价格执行。 在回测中,它将是下一个 bar 的开盘价

    • Order.Limit. 一个只能以给定的price或更好的价格执行的订单

    • Order.Stop. 一个在price处触发并像Order.Market订单一样执行的订单

    • Order.StopLimit. 一个在price处触发并以隐式Limit订单形式执行的订单,价格由pricelimit给出

    • Order.Close. 一个只能在会话结束时(通常在收盘竞价时)执行的订单

    • Order.StopTrail. 一个在price减去trailamount(或trailpercent)处触发并在价格远离停止时更新的订单

    • Order.StopTrailLimit. 一个在price减去trailamount(或trailpercent)处触发并在价格远离停止时更新的订单

  • valid (默认值: None)

    可能的值:

    • None: 这将生成一个不会过期的订单(又称Good till cancel),并保持在市场上直到匹配或取消。 实际上,经纪人往往会强加一个时间限制,但这通常是如此遥远的时间,以至于可以视为不会过期。

    • datetime.datetimedatetime.date 实例:日期将用于生成一个直到给定日期时间(又称good till date)的有效订单

    • Order.DAY0timedelta(): 一个有效期至会话结束(又称day订单)的订单将被生成

    • numeric value: 这被假设为与matplotlib编码中的日期时间对应的值(backtrader使用的编码方式),并将用于生成一个有效订单,直到该时间(good till date

  • tradeid (默认值: 0)

    这是backtrader应用的内部值,用于跟踪同一资产上的重叠交易。 当通知订单状态变化时,此tradeid将发送回strategy

  • oco (默认值: None)

    另一个order实例。 这个订单将成为 OCO(Order Cancel Others)组的一部分。 一个订单的执行会立即取消同一组中的所有其他订单

  • parent (默认值: None)

    控制一组订单的关系,例如一个由高限价卖出和低止损卖出所包围的买入订单。高/低侧订单保持不活跃,直到父订单被执行(它们变为活动状态)或取消/过期(子订单也取消)bracket 订单具有相同的大小

  • transmit (默认值: True)

    指示订单是否必须发送,即不仅在经纪人处放置,而且发出。 例如,这用于控制 bracket 订单,其中一个禁用父订单和第 1 组子订单的传输,并激活最后一组子订单的传输,这会触发所有 bracket 订单的完全放置。

  • **kwargs:其他经纪人实现可能支持额外的参数。backtraderkwargs传递给创建的订单对象

    示例:如果直接由backtrader支持的 4 种订单执行类型不够,例如Interactive Brokers的情况下,可以将以下内容作为kwargs传递:

    orderType='LIT', lmtPrice=10.0, auxPrice=9.8` 
    

    这将覆盖backtrader创建的设置,并生成一个touched价格为 9.8,limit价格为 10.0 的LIMIT IF TOUCHED订单。

  • 返回

    • 提交的订单

sell(data=None, size=None, price=None, plimit=None, exectype=None, valid=None, tradeid=0, oco=None, trailamount=None, trailpercent=None, parent=None, transmit=True, **kwargs)

创建一个卖出(做空)订单并将其发送给经纪人

查看buy的文档以了解参数的解释

返回:提交的订单

close(data=None, size=None, **kwargs)

抵消长/短持仓

查看buy的文档以了解参数的解释

注意

  • size: 如果调用者没有提供,则自动从现有位置计算(默认值:None

返回:提交的订单

cancel(order)

在经纪人处取消订单

buy_bracket(data=None, size=None, price=None, plimit=None, exectype=2, valid=None, tradeid=0, trailamount=None, trailpercent=None, oargs={}, stopprice=None, stopexec=3, stopargs={}, limitprice=None, limitexec=2, limitargs={}, **kwargs)

创建一个支架订单组(低端 - 买单 - 高端)。默认行为如下:

  • 发出buy带有执行Limit的订单

  • 发出一个low side支架sell带有执行Stop的订单

  • 发出一个high side支架sell带有执行Limit的订单。

有关不同参数的详细信息,请参见下文

  • data(默认值:None

    要为哪些数据创建订单。如果为None,则将使用系统中的第一个数据,self.datas[0]或 self.data0(又名self.data

  • size(默认值:None

    要使用的大小(正)以用于订单的数据单位。

    如果为None,则将使用通过getsizer检索到的sizer实例确定大小。

    注意

    同样的大小适用于支架的所有 3 个订单

  • price(默认值:None

    使用价格(实时经纪人可能会对实际格式施加限制,如果不符合最小跳动大小要求)

    None适用于MarketClose订单(市场确定价格)

    对于LimitStopStopLimit订单,此值确定触发点(在Limit情况下,触发显然是订单应匹配的价格)

  • plimit(默认值:None

    仅适用于StopLimit订单。这是设置隐含Limit订单的价格,一旦Stop已被触发(已使用price

  • trailamount(默认值:None

    如果订单类型为 StopTrail 或 StopTrailLimit,则此值是一个绝对金额,用于确定保持跟踪止损的价格距离(对于卖出订单为下方,对于买入订单为上方)

  • trailpercent(默认值:None

    如果订单类型为 StopTrail 或 StopTrailLimit,则此处为一个百分比值,确定保持跟踪止损的距离(对于卖出订单下方,对于买入订单上方)(如果还指定了trailamount,则将使用它)

  • exectype(默认值:bt.Order.Limit

    可能的取值:(查看方法buy的文档)

  • valid(默认值:None

    可能的取值:(查看方法buy的文档)

  • tradeid(默认值:0

    可能的取值:(查看方法buy的文档)

  • oargs(默认值:{}

    将特定的关键字参数(在dict中)传递给主侧订单。默认的**kwargs中的参数将在此基础上应用。

  • **kwargs:其他经纪人实现可能支持额外的参数。backtrader将把kwargs传递给创建的订单对象

    可能的取值:(查看方法buy的文档)

    注意

    这个kwargs将应用于一个括号的 3 个订单。有关低侧和高侧订单的特定关键字参数,请参见下文

  • stopprice(默认值:None

    低侧停止订单的具体价格

  • stopexec(默认值:bt.Order.Stop

    低侧订单的具体执行类型

  • stopargs(默认值:{}

    将特定的关键字参数(在dict中)传递给低侧订单。默认的**kwargs中的参数将在此基础上应用。

  • limitprice(默认值:None

    高侧停止订单的具体价格

  • stopexec(默认值:bt.Order.Limit

    高侧订单的具体执行类型

  • limitargs(默认值:{}

    将特定的关键字参数(在dict中)传递给高侧订单。默认的**kwargs中的参数将在此基础上应用。

可以通过以下方式抑制高/低侧订单:

  • limitexec=None以抑制高侧

  • stopexec=None以抑制低侧

  • 返回

    • 包含 3 个订单的列表[订单,停止侧,限价侧]

    • 如果高/低订单已被抑制,则返回值仍将包含 3 个订单,但被抑制的订单将值为None

sell_bracket(data=None, size=None, price=None, plimit=None, exectype=2, valid=None, tradeid=0, trailamount=None, trailpercent=None, oargs={}, stopprice=None, stopexec=3, stopargs={}, limitprice=None, limitexec=2, limitargs={}, **kwargs)

创建一个括号订单组(低侧 - 买入订单 - 高侧)。默认行为如下:

  • 发出一个卖出订单,执行限价

  • 发出一个高侧括号买入订单,执行停止

  • 发出一个低侧括号买入订单,执行限价

查看bracket_buy以了解参数的含义

可以通过以下方式抑制高/低侧订单:

  • stopexec=None以抑制高侧

  • limitexec=None以抑制低侧

  • 返回

    • 包含 3 个订单的列表[订单,停止侧,限价侧]

    • 如果高/低订单已被抑制,则返回值仍将包含 3 个订单,但被抑制的订单将值为None

order_target_size(data=None, target=0, **kwargs)

下达订单以将头寸重新平衡为target的最终大小

当前position大小被视为实现target的起始点

  • 如果target > pos.size -> 买入 target - pos.size

  • 如果target < pos.size -> 卖出 pos.size - target

它返回以下之一:

  • 生成的订单

  • 如果没有下达订单则为Nonetarget == position.size)

order_target_value(data=None, target=0.0, price=None, **kwargs)

下达订单以将头寸重新平衡为target的最终价值

当前value被视为实现target的起始点

  • 如果没有target,则在数据上关闭头寸

  • 如果target > value则在数据上买入

  • 如果target < value则在数据上卖出

它返回以下之一:

  • 生成的订单

  • 如果没有下达订单则为None

order_target_percent(data=None, target=0.0, **kwargs)

下达订单以将头寸重新平衡为当前投资组合valuetarget百分比的最终价值

target以小数表示:0.05 -> 5%

它使用order_target_value来执行订单。

示例

  • target=0.05且投资组合价值为100

  • 要达到的value0.05 * 100 = 5

  • 5作为target值传递给order_target_value

当前value被视为实现target的起始点

使用position.size来确定头寸是多头/空头

  • 如果target > value

    • 如果pos.size >= 0则买入(增加多头头寸)

    • 如果pos.size < 0则卖出(增加空头头寸)

  • 如果target < value

    • 如果pos.size >= 0则卖出(减少多头头寸)

    • 如果pos.size < 0则买入(减少空头头寸)

它返回以下之一:

  • 生成的订单

  • 如果没有下达订单,则为Nonetarget == position.size

getsizer()

返回正在使用的 sizer,如果使用自动��注计算

也可用作sizer

setsizer(sizer)

替换默认(固定赌注)sizer

getsizing(data=None, isbuy=True)

返回由 sizer 实例计算的当前情况下的赌注

getposition(data=None, broker=None)

返回给定经纪人中给定数据的当前头寸。

如果两者都为 None,则将使用主要数据和默认经纪人

也可用的属性position

getpositionbyname(name=None, broker=None)

返回给定经纪人中给定名称的当前头寸。

如果两者都为 None,则将使用主要数据和默认经纪人

也可用的属性positionbyname

getpositionsbyname(broker=None)

直接从经纪人那里返回当前名称的头寸

如果给定的broker为 None,则将使用默认经纪人

也可用的属性positionsbyname

getdatanames()

返回现有数据名称的列表

getdatabyname(name)

通过名称使用环境(cerebro)返回给定数据

add_timer(when, offset=datetime.timedelta(0), repeat=datetime.timedelta(0), weekdays=[], weekcarry=False, monthdays=[], monthcarry=True, allow=None, tzdata=None, cheat=False, *args, **kwargs)

注意

可以在 __init__start 期间调用

安排一个计时器来调用一个或多个策略的指定回调函数或 notify_timer

  • 参数

    when (-) – 可以是

    • datetime.time 实例(参见下面的 tzdata

    • bt.timer.SESSION_START 用于引用会话开始

    • bt.timer.SESSION_END 用于引用会话结束

    • offset 必须是一个 datetime.timedelta 实例

    用于偏移值 when。与 SESSION_STARTSESSION_END 结合使用具有有意义的用途,以指示会话开始后 15 分钟 调用计时器的情况。

    • repeat 必须是一个 datetime.timedelta 实例

      指示在第一次调用后,进一步的调用是否会在同一会话中按计划的 repeat 间隔进行安排

      一旦计时器超过会话结束,它将被重置为 when 的原始值

    • weekdays:一个排序的可迭代对象,其中的整数表示可以实际调用计时器的日期(ISO 代码,星期一为 1,星期日为 7)

      如果未指定,则计时器将在所有日期都处于活动状态

    • weekcarry(默认:False)。如果为 True 并且没有看到周几(例如:交易假期),则计时器将在下一天执行(即使是在新的一周)

    • monthdays:一个排序的可迭代对象,其中的整数表示要执行计时器的月份中的哪些日期。例如,每月的第 15 天

      如果未指定,则计时器将在所有日期都处于活动状态

    • monthcarry(默认:True)。如果未看到该天(周末,交易假期),则计时器将在下一个可用日期执行。

    • allow(默认:None)。一个回调函数,接收一个 datetime.date 实例,如果日期允许计时器,则返回True,否则返回False

    • tzdata 可以是 None(默认值),一个 pytz 实例或一个数据源实例。

      Nonewhen按照面值解释(即使它不是 UTC 时间也会被处理为 UTC 时间)

      pytz 实例:when 将被解释为由时区实例指定的本地时间。

      data feed 实例:when 将被解释为数据源实例参数 tz 指定的本地时间。

      注意

      如果 whenSESSION_STARTSESSION_END,并且 tzdataNone,则系统中的第一个数据源(即 self.data0)将被用作查找会话时间的参考。

    • cheat(默认为 False)如果为 True,则在经纪人有机会评估订单之前将调用计时器。这打开了在会话开始之前根据开盘价发出订单的可能性

    • *args:任何额外的参数将被传递给 notify_timer

    • **kwargs:任何额外的 kwargs 将被传递给 notify_timer

返回值:

  • 创建的计时器

notify_timer(timer, when, *args, **kwargs)

接收定时器通知,其中timer是由add_timer返回的定时器,when是调用时间。argskwargs是传递给add_timer的任何额外参数。

实际的when时间可能会晚一些,但系统可能在之前无法调用定时器。这个值是定时器值,而不是系统时间。

带信号的策略

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

也可以操作backtrader而不必编写Strategy。尽管这是首选方法,但由于构成机器的对象层次结构,使用Signals也是可能的。

快速摘要:

  • 不是编写Strategy类,实例化Indicators,编写buy/sell逻辑...

  • 最终用户添加Signals(无论如何是指示器),其余操作由后台完成

快速示例:

import backtrader as bt

data = bt.feeds.OneOfTheFeeds(dataname='mydataname')
cerebro.adddata(data)

cerebro.add_signal(bt.SIGNAL_LONGSHORT, MySignal)
cerebro.run()

完成!。

当然,Signal本身是缺失的。让我们定义一个非常愚蠢的Signal,产生:

  • 如果close价格高于Simple Moving Average,则发出Long指示。

  • 如果close价格低于Simple Moving Average,则发出Short指示。

定义为:

class MySignal(bt.Indicator):
    lines = ('signal',)
    params = (('period', 30),)

    def __init__(self):
        self.lines.signal = self.data - bt.indicators.SMA(period=self.p.period)

现在真的完成了。当执行run时,Cerebro将负责实例化一个特殊的Strategy实例,该实例知道如何处理Signals

初始FAQ

  • /操作的数量如何确定?

    cerebro实例自动为策略添加了FixedSize大小器。最终用户可以使用cerebro.addsizer更改大小器以改变策略。

  • 订单是如何执行的?

    执行类型为Market,有效性为Good Until Canceled

Signals技术细节

从技术和理论角度描述如下:

  • 当被调用时返回另一个对象的可调用对象(仅一次)

    这在大多数情况下是类的实例化,但不一定是。

  • 支持__getitem__接口。唯一请求的key/index将为0

从实际角度和上述示例来看,Signal是:

  • 来自backtrader生态系统的lines对象,主要是Indicator

    当使用其他Indicators时,如示例中使用Simple Moving Average时,这很有帮助。

信号 指示

当用signal[0]查询signals时,signals提供指示,意思是:

  • > 0 -> long indication

  • < 0 -> short indication

  • == 0 -> 无指示

该示例对self.data - SMA进行了简单的算术运算,并且:

  • data高于SMA时发出long indication

  • data低于SMA时发出short indication

注意

data 没有指定特定的价格字段时,参考价格为 close 价格。

信号类型

如上例所示,下面指示的常量直接来自主要的backtrader模块,如下所示:

import backtrader as bt

bt.SIGNAL_LONG

共有 5 种Signals类型,分为 2 组。

主要组

  • LONGSHORT: 此信号的longshort指示均被采纳。

  • LONG

    • 采用long指示进入long

    • 采用short指示来close长头寸。但是:

    • 如果系统中存在LONGEXIT(见下文)信号,则将用于退出长头寸

    • 如果有SHORT信号可用,但没有LONGEXIT可用,则会在开放short之前关闭long

  • SHORT

    • short指示用于开空头寸

    • long指示用于关闭空头寸。但是:

    • 如果系统中存在一个SHORTEXIT(见下文)信号,它将被用于退出短头寸

    • 如果有LONG信号可用且没有SHORTEXIT可用,则将用于在开仓long之前关闭short

退出组

这两个信号旨在覆盖其他信号,并提供退出long / short头寸的标准

  • LONGEXITshort指示用于退出long头寸

  • SHORTEXIT指示被视为退出头寸

累积和订单并发

上面显示的示例信号会定期发出指示,因为它只是从close价格中减去SMA值,而这总是会是> 0< 0(数学上可能为0,但实际上不太可能发生)

这将导致连续生成订单,会产生 2 种情况:

  • Accumulation:即使已经在市场上,信号也会产生新订单,这将增加市场上的仓位

  • Concurrency:新订单将生成,而无需等待其他订单的执行

为了避免这种情况,默认行为是:

  • 不累积

  • 不允许并发

如果希望这两种行为之一,可以通过cerebro控制:

  • cerebro.signal_accumulate(True)(或False以重新禁用它)

  • cerebro.signal_concurrency(True)(或False以重新禁用它)

示例

backtrader源代码包含一个用于测试功能的示例。

要使用的主要信号。

class SMACloseSignal(bt.Indicator):
    lines = ('signal',)
    params = (('period', 30),)

    def __init__(self):
        self.lines.signal = self.data - bt.indicators.SMA(period=self.p.period)

并且在指定选项的情况下退出信号

class SMAExitSignal(bt.Indicator):
    lines = ('signal',)
    params = (('p1', 5), ('p2', 30),)

    def __init__(self):
        sma1 = bt.indicators.SMA(period=self.p.p1)
        sma2 = bt.indicators.SMA(period=self.p.p2)
        self.lines.signal = sma1 - sma2

第一次运行:长和短

$ ./signals-strategy.py --plot --signal longshort

输出

image

注意:

  • 信号被绘制出来。这是正常的,因为它只是一个指标,适用于它的绘图规则

  • 策略实际上是longshort的。这可以看出,因为现金水平从未回到水平

  • 旁注:即使是一个愚蠢的想法...(没有佣金)策略也没有亏钱...

第二次运行:仅限长头寸

$ ./signals-strategy.py --plot --signal longonly

输出

image

注意:

  • 在每次卖出后,现金水平都会回到水平,这意味着策略已经退出市场

  • 旁注:再次没有损失金钱……

第三次运行:仅限短头寸

$ ./signals-strategy.py --plot --signal shortonly

输出

image

注意:

  • 第 1 次操作是卖出,符合预期,并且发生在上述两个示例中的第 1 次操作之后。只有在close低于SMA并且简单的减法得到负数时才会发生

  • 在每次买入后,现金水平都会回到水平,这意味着策略已经退出市场

  • 旁注:最终系统亏损

第四次运行:长+长退出

$ ./signals-strategy.py --plot --signal longonly --exitsignal longexit

输出

image

注意:

  • 许多交易都是相同的,但有些会在快速移动平均线在退出信号中向下穿过慢速移动平均线时中断

  • 该系统展示了其仅做多属性,现金在每次交易结束时的价值。

  • 旁注:再次赚钱……甚至有些修改后的交易也能获利

使用方法

$ ./signals-strategy.py --help
usage: signals-strategy.py [-h] [--data DATA] [--fromdate FROMDATE]
                           [--todate TODATE] [--cash CASH]
                           [--smaperiod SMAPERIOD] [--exitperiod EXITPERIOD]
                           [--signal {longshort,longonly,shortonly}]
                           [--exitsignal {longexit,shortexit}]
                           [--plot [kwargs]]

Sample for Signal concepts

optional arguments:
  -h, --help            show this help message and exit
  --data DATA           Specific data to be read in (default:
                        ../../datas/2005-2006-day-001.txt)
  --fromdate FROMDATE   Starting date in YYYY-MM-DD format (default: None)
  --todate TODATE       Ending date in YYYY-MM-DD format (default: None)
  --cash CASH           Cash to start with (default: 50000)
  --smaperiod SMAPERIOD
                        Period for the moving average (default: 30)
  --exitperiod EXITPERIOD
                        Period for the exit control SMA (default: 5)
  --signal {longshort,longonly,shortonly}
                        Signal type to use for the main signal (default:
                        longshort)
  --exitsignal {longexit,shortexit}
                        Signal type to use for the exit signal (default: None)
  --plot [kwargs], -p [kwargs]
                        Plot the read data applying any kwargs passed For
                        example: --plot style="candle" (to plot candles)
                        (default: None)

该代码

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import argparse
import collections
import datetime

import backtrader as bt

MAINSIGNALS = collections.OrderedDict(
    (('longshort', bt.SIGNAL_LONGSHORT),
     ('longonly', bt.SIGNAL_LONG),
     ('shortonly', bt.SIGNAL_SHORT),)
)

EXITSIGNALS = {
    'longexit': bt.SIGNAL_LONGEXIT,
    'shortexit': bt.SIGNAL_LONGEXIT,
}

class SMACloseSignal(bt.Indicator):
    lines = ('signal',)
    params = (('period', 30),)

    def __init__(self):
        self.lines.signal = self.data - bt.indicators.SMA(period=self.p.period)

class SMAExitSignal(bt.Indicator):
    lines = ('signal',)
    params = (('p1', 5), ('p2', 30),)

    def __init__(self):
        sma1 = bt.indicators.SMA(period=self.p.p1)
        sma2 = bt.indicators.SMA(period=self.p.p2)
        self.lines.signal = sma1 - sma2

def runstrat(args=None):
    args = parse_args(args)

    cerebro = bt.Cerebro()
    cerebro.broker.set_cash(args.cash)

    dkwargs = dict()
    if args.fromdate is not None:
        fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
        dkwargs['fromdate'] = fromdate

    if args.todate is not None:
        todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
        dkwargs['todate'] = todate

    # if dataset is None, args.data has been given
    data = bt.feeds.BacktraderCSVData(dataname=args.data, **dkwargs)
    cerebro.adddata(data)

    cerebro.add_signal(MAINSIGNALS[args.signal],
                       SMACloseSignal, period=args.smaperiod)

    if args.exitsignal is not None:
        cerebro.add_signal(EXITSIGNALS[args.exitsignal],
                           SMAExitSignal,
                           p1=args.exitperiod,
                           p2=args.smaperiod)

    cerebro.run()
    if args.plot:
        pkwargs = dict(style='bar')
        if args.plot is not True:  # evals to True but is not True
            npkwargs = eval('dict(' + args.plot + ')')  # args were passed
            pkwargs.update(npkwargs)

        cerebro.plot(**pkwargs)

def parse_args(pargs=None):

    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description='Sample for Signal concepts')

    parser.add_argument('--data', required=False,
                        default='../../datas/2005-2006-day-001.txt',
                        help='Specific data to be read in')

    parser.add_argument('--fromdate', required=False, default=None,
                        help='Starting date in YYYY-MM-DD format')

    parser.add_argument('--todate', required=False, default=None,
                        help='Ending date in YYYY-MM-DD format')

    parser.add_argument('--cash', required=False, action='store',
                        type=float, default=50000,
                        help=('Cash to start with'))

    parser.add_argument('--smaperiod', required=False, action='store',
                        type=int, default=30,
                        help=('Period for the moving average'))

    parser.add_argument('--exitperiod', required=False, action='store',
                        type=int, default=5,
                        help=('Period for the exit control SMA'))

    parser.add_argument('--signal', required=False, action='store',
                        default=MAINSIGNALS.keys()[0], choices=MAINSIGNALS,
                        help=('Signal type to use for the main signal'))

    parser.add_argument('--exitsignal', required=False, action='store',
                        default=None, choices=EXITSIGNALS,
                        help=('Signal type to use for the exit signal'))

    # Plot options
    parser.add_argument('--plot', '-p', nargs='?', required=False,
                        metavar='kwargs', const=True,
                        help=('Plot the read data applying any kwargs passed\n'
                              '\n'
                              'For example:\n'
                              '\n'
                              '  --plot style="candle" (to plot candles)\n'))

    if pargs is not None:
        return parser.parse_args(pargs)

    return parser.parse_args()

if __name__ == '__main__':
    runstrat()

策略参考

原文:www.backtrader.com/docu/strategy-reference/

内置策略的参考

MA_CrossOver

别名:

* SMA_CrossOver

这是一个仅限开多头的策略,基于移动平均线交叉

注意:

* Although the default

买入逻辑:

* No position is open on the data

* The `fast` moving averagecrosses over the `slow` strategy to the
  upside.

卖出逻辑:

* A position exists on the data

* The `fast` moving average crosses over the `slow` strategy to the
  downside

订单执行类型:

* Market

线:

* datetime

参数:

* fast (10)

* slow (30)

* _movav (<class ‘backtrader.indicators.sma.SMA’>)

SignalStrategy

这个Strategy的子类旨在使用信号自动运行。

信号通常是指标,预期输出值为:

  • > 0是一个long指示

  • < 0是一个short指示

有 5 种信号类型,分为 2 组。

主要组

  • LONGSHORT:此信号的longshort指示都会被采纳

  • LONG

    • long指示被认为是开多头

    • short指示被认为是用于关闭多头仓位。但是:

    • 如果系统中存在LONGEXIT(见下文)信号,则会用于退出多头

    • 如果有SHORT信号可用且没有LONGEXIT可用,则会用于在开启short之前关闭long

  • SHORT

    • short指示被认为是开空头

    • long指示被认为是用于关闭空头仓位。但是:

    • 如果系统中存在SHORTEXIT(见下文)信号,则会用于退出空头

    • 如果有LONG信号可用且没有SHORTEXIT可用,则会用于在开启long之前关闭short

退出组

这两个信号旨在覆盖其他信号并提供退出long/short仓位的标准。

  • LONGEXITshort指示被认为是用于退出long仓位

  • SHORTEXITlong指示被认为是用于退出short仓位

订单发出

订单执行类型为Market,有效性为None直到取消为止

参数:

  • signals(默认:[]):一个列表/元组的列表/元组,允许实例化信号并分配到正确的类型

    预计通过cerebro.add_signal来管理此参数

  • _accumulate(默认:False):允许进入市场(多头/空头),即使已经在市场中

  • _concurrent(默认:False):允许即使有订单正在等待执行,也可以发出订单

  • _data(默认:None):如果系统中存在多个数据,这是订单的目标。这可以是

    • None:系统中的第一个数据将被使用

    • 一个int:表示在该位置插入的数据

    • 一个str:在创建数据时给定的名称(参数name)或在使用cerebro.adddata(..., name=)将其添加到 cerebro 时给定的名称

    • 一个data实例

线:

* datetime

参数:

* signals ([])

* _accumulate (False)

* _concurrent (False)

* _data (None)

指标

使用指标

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

在平台中,指标可以在两个地方使用:

  • 在策略内部

  • 在其他指标内部

指标的作用

  1. Indicators始终在Strategy中的__init__期间实例化

  2. next期间使用/检查指标值(或其派生的值)

有一个重要的公理需要考虑:

  • __init__期间声明的任何Indicator(或派生值)将在调用next之前预先计算。

让我们来看一下差异和操作模式。

__init__next

事情的工作方式如下:

  • __init__期间涉及lines对象的任何操作都会生成另一个lines对象

  • next期间涉及lines对象的任何操作都会产生常规的 Python 类型,如浮点数和布尔值。

__init__期间

__init__期间操作的示例:

hilo_diff = self.data.high - self.data.low

变量hilo_diff保存对在调用next之前预先计算的lines对象的引用,并且可以使用标准数组表示法[]访问

很明显,对于数据源的每个条数据,它包含了高低之间的差异。

这也适用于将简单的lines(如 self.data 数据源中的 lines)与复杂的指标混合使用时:

sma = bt.SimpleMovingAverage(self.data.close)
close_sma_diff = self.data.close - sma

现在close_sma_diff再次包含一个line对象。

使用逻辑运算符:

close_over_sma = self.data.close > sma

现在生成的lines对象将包含一个布尔数组。

next期间

操作示例(逻辑运算符):

close_over_sma = self.data.close > self.sma

使用等效数组(索引从 0 开始的表示法):

close_over_sma = self.data.close[0] > self.sma[0]

在这种情况下,close_over_sma产生一个布尔值,该值是通过将self.data.closeself.sma应用到[0]运算符返回的两个浮点值进行比较的结果

__init__next为什么

逻辑简化(以及随之的易用性)是关键。计算和大部分相关逻辑可以在__init__期间声明,在next期间将实际操作逻辑保持最小化。

实际上还有一个附加好处:速度(由于在开头解释的预计算)

一个完整的示例,在__init__期间生成一个buy信号:

class MyStrategy(bt.Strategy):

    def __init__(self):

        sma1 = btind.SimpleMovingAverage(self.data)
        ema1 = btind.ExponentialMovingAverage()

        close_over_sma = self.data.close > sma1
        close_over_ema = self.data.close > ema1
        sma_ema_diff = sma1 - ema1

        buy_sig = bt.And(close_over_sma, close_over_ema, sma_ema_diff > 0)

    def next(self):

        if buy_sig:
            self.buy()

注意

Python 的and运算符不能被重载,迫使平台定义自己的And。其他构造也是如此,比如OrIf

显然,__init__期间的“声明式”方法将next的膨胀(其中实际策略工作发生)最小化。

(不要忘记还有一个加速因素)

注意

当逻辑变得非常复杂并涉及多个操作时,通常更好的做法是将其封装在一个Indicator内部。

一些注意事项

在上面的示例中,与其他平台相比,backtrader中已经简化了两件事情:

  • 声明的Indicators既不会得到一个parent参数(就像它们被创建的策略一样),也不会调用任何类型的“register”方法/函数。

    尽管如此,策略仍将触发 Indicators 的计算和因操作而生成的任何 lines 对象(如 sma - ema

  • ExponentialMovingAverage 在没有 self.data 的情况下被实例化

    这是故意的。如果没有传递 data,则将自动传递父级的第一个数据(在本例中,正在创建的策略)

指标绘图

首先和最重要的是:

  • 声明的 Indicators 将自动绘制(如果调用了 cerebro.plot)

  • 来自操作的 lines 对象不会被绘制(如 close_over_sma = self.data.close > self.sma

    如果需要,有一个辅助的 LinePlotterIndicator,它可以使用以下方法绘制此类操作:

    close_over_sma = self.data.close > self.sma
    LinePlotterIndicator(close_over_sma, name='Close_over_SMA')` 
    

    name 参数为此指标持有的 单个 线条命名。

控制绘图

在开发 Indicator 时,可以添加一个 plotinfo 声明。它可以是元组的元组(2 个元素)、一个 dict 或一个 OrderedDict。它看起来像:

class MyIndicator(bt.Indicator):

    ....
    plotinfo = dict(subplot=False)
    ....

该值稍后可以按如下方式访问(和设置)(如果需要的话):

myind = MyIndicator(self.data, someparam=value)
myind.plotinfo.subplot = True

该值甚至可以在实例化期间设置:

myind = MyIndicator(self.data, someparams=value, subplot=True)

subplot=True 将传递给(幕后)实例化的成员变量 plotinfo,用于指标。

plotinfo 提供以下参数来控制绘图行为:

  • plot(默认值:True

    指标是否要绘制或不绘制

  • subplot(默认值:True

    是否在不同窗口中绘制指标。对于像移动平均这样的指标,默认值更改为 False

  • plotname(默认值:''

    将绘图名称设置为显示在图表上。空值意味着将使用指标的规范名称(class.__name__)。这有一些限制,因为 Python 标识符不能使用例如算术运算符。

    像 DI+ 这样的指标将声明如下:

    class DIPlus(bt.Indicator):
        plotinfo=dict(plotname='DI+')` 
    

    使图表“更美观”

  • plotabove(默认值:False

    通常将指标绘制在它们操作的数据下方(那些 subplot=True 的指标)。将此设置为 True 将使指标绘制在数据之上。

  • plotlinelabels(默认值:False

    用于“指标”上的“指标”。如果计算 RSI 的 SimpleMovingAverage,则绘图通常会显示相应绘制线的名称“SimpleMovingAverage”。这是“Indicator”的名称,而不是实际绘制的线的名称。

    这种默认行为是有意义的,因为用户通常希望看到使用 RSI 创建了 SimpleMovingAverage。

    如果将值设置为 True,则将使用 SimpleMovingAverage 中线的实际名称。

  • plotymargin(默认值:0.0

    要在指标的顶部和底部留下的边距量(0.15 -> 15%)。有时 matplotlib 绘图会延伸到轴的顶部/底部,可能希望有一些边距

  • plotyticks(默认值:[]

    用于控制绘制的 y 轴刻度

    如果传递了一个空列表,“y ticks”将自动计算。对于像随机指标这样的指标,将其设置为已知的行业标准可能是有意义的,例如:[20.0, 50.0, 80.0]

    一些指标提供诸如upperbandlowerband之类的参数,实际上用于操作 y ticks。

  • plothlines(默认值:[]

    用于控制沿指标轴绘制水平线。

    如果传递了一个空列表,将不会绘制任何水平线。

    对于像随机指标这样的指标,绘制已知的行业标准线可能是有意义的,例如:[20.0, 80.0]

    一些指标提供诸如upperbandlowerband之类的参数,实际上用于操作水平线。

  • plotyhlines(默认值:[]

    用于同时使用单个参数控制 plotyticks 和 plothlines。

  • plotforce(默认值:False

    如果由于某种原因你认为某个指标应该绘制但却没有绘制……将此设置为True作为最后的手段。

指标开发

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

如果除了一个或多个获胜策略之外,还必须开发其他内容,则此内容是自定义指标。

根据作者的说法,平台内的这种开发是简单的。

需要以下内容:

  • 从指标派生的类(直接或从已经存在的子类)

  • 定义它将保持的线条

    指标必须至少有一条线。如果是从现有指标派生的,则可能已经定义了线条(们)

  • 可选地定义可以更改行为的参数

  • 可选地提供/自定义一些元素,以便合理地绘制指标

  • __init__中提供完全定义的操作,并将其绑定(分配)到指标的线条(们)上,否则提供next和(可选)once方法

    如果可以在初始化期间使用逻辑/算术运算完全定义指标,并将结果分配给线条:完成

    如果不是这种情况,至少必须提供一个next,在该方法中指标必须将一个值分配给索引为 0 的线条(们)

    可以通过提供once方法来实现对runonce模式(批量操作)的计算优化。

重要提示:幂等性

指标为它们接收的每个柱状图生成输出。不必假设同一柱状图将被发送多少次。操作必须是幂等的。

其背后的原理是:

  • 同一柱状图(索引)可以多次发送,其值会发生变化(即变化的值是收盘价)

这使得例如,“重播”每日会话,但使用可能由 5 分钟柱状图组成的股票数据成为可能。

这也可以使平台从实时数据源获取值。

一个虚拟(但功能性的)指标

所以它可以是:

class DummyInd(bt.Indicator):
    lines = ('dummyline',)

    params = (('value', 5),)

    def __init__(self):
        self.lines.dummyline = bt.Max(0.0, self.params.value)

完成!指标将始终输出相同的值:如果大于 0.0,则为 0.0 或 self.params.value。

相同的指标,但使用了下一个方法:

class DummyInd(bt.Indicator):
    lines = ('dummyline',)

    params = (('value', 5),)

    def next(self):
        self.lines.dummyline[0] = max(0.0, self.params.value)

完成!相同的行为。

注意

注意在__init__版本中如何使用bt.Max将其分配给 Line 对象self.lines.dummyline

bt.Max返回一个lines对象,该对象会自动为传递给指标的每个柱状图迭代。

如果使用max,则赋值将毫无意义,因为指标将具有具有固定值的成员变量,而不是线条。

next过程中,直接使用浮点值进行工作,并且可以使用标准的max内置函数

让我们回顾一下self.lines.dummyline是长格式的,可以缩写为:

  • self.l.dummyline

甚至可以:

  • self.dummyline

后者仅在代码没有将其与成员属性混淆时才可能存在。

第三个版本提供了一个额外的once方法来优化计算:

class DummyInd(bt.Indicator):
    lines = ('dummyline',)

    params = (('value', 5),)

    def next(self):
        self.lines.dummyline[0] = max(0.0, self.params.value)

    def once(self, start, end):
       dummy_array = self.lines.dummyline.array

       for i in xrange(start, end):
           dummy_array[i] = max(0.0, self.params.value)

更有效,但开发once方法已迫使深入挖掘。实际上已经查看了内部情况。

__init__ 版本无论如何都是最好的:

  • 一切都限于初始化

  • nextonce(都经过优化,因为 bt.Max 已经包含了它们)会自动提供,无需操作索引和/或公式

如果需要开发,指标还可以覆盖与 nextonce 关联的方法:

  • prenextnexstart

  • preonceoncestart

手动/自动最小周期

如果可能的话,平台会计算,但可能需要手动操作。

这是一个简单移动平均的潜在实现:

class SimpleMovingAverage1(Indicator):
    lines = ('sma',)
    params = (('period', 20),)

    def next(self):
        datasum = math.fsum(self.data.get(size=self.p.period))
        self.lines.sma[0] = datasum / self.p.period

尽管看起来合理,但平台并不知道最小周期是多少,即使参数被命名为“period”(名称可能会误导,并且一些指标接收到多个具有不同用途的“period”)

在这种情况下,next将被调用,已经为第 1 个柱,一切都将爆炸,因为 get 不能返回所需的 self.p.period

在解决这种情况之前,必须考虑以下事项:

  • 提供给指标的数据源可能已经具有最小周期

例如,可以对样本SimpleMovingAverage进行处理:

  • 一个常规数据源

    这有一个默认的最小周期为 1(只需等待进入系统的第 1 个柱形图)

  • 另一个移动平均……而这又已经有一个周期

    如果这是 20,再次我们的示例移动平均值也是 20,我们最终会得到一个最小周期为 40 个柱的周期

    实际上,内部计算说 39 …… 因为一旦第一个移动平均产生了一个柱,这就计为下一个移动平均,从而创建了一个重叠的柱,因此需要 39 个。

  • 其他也携带周期的指标/对象

缓解情况的方法如下:

class SimpleMovingAverage1(Indicator):
    lines = ('sma',)
    params = (('period', 20),)

    def __init__(self):
        self.addminperiod(self.params.period)

    def next(self):
        datasum = math.fsum(self.data.get(size=self.p.period))
        self.lines.sma[0] = datasum / self.p.period

addminperiod 方法告诉系统考虑该指标所需的额外周期柱到任何可能存在的最小周期。

有时这绝对不是必要的,如果所有计算都是使用已经向系统传达其周期需求的对象完成的。

带有直方图的快速MACD实现:

from backtrader.indicators import EMA

class MACD(Indicator):
    lines = ('macd', 'signal', 'histo',)
    params = (('period_me1', 12), ('period_me2', 26), ('period_signal', 9),)

    def __init__(self):
        me1 = EMA(self.data, period=self.p.period_me1)
        me2 = EMA(self.data, period=self.p.period_me2)
        self.l.macd = me1 - me2
        self.l.signal = EMA(self.l.macd, period=self.p.period_signal)
        self.l.histo = self.l.macd - self.l.signal

完成!不需要考虑最小周期。

  • EMA代表指数移动平均(一个平台内置的别名)

    这个(已经在平台上)已经说明了它需要什么

  • 指标“macd”和“signal”的命名线被分配了已经携带声明的对象(在幕后)周期的对象

    • macd 取自“me1 - me2”操作的周期,这又取自 me1 和 me2 的周期的最大值(它们都是具有不同周期的指数移动平均值)

    • signal 直接取 Exponential Moving Average 在 macd 上的周期。这个 EMA 还考虑了已经存在的 macd 周期和计算自身所需样本(period_signal)的数量

    • histo 取两个操作数“signal - macd”的最大值。一旦两者准备好了,histo 也可以生成一个值

一个完全定制的指标

让我们开发一个简单的自定义指标,用于“指示”移动平均线(可以通过参数进行修改)是否高于给定数据:

import backtrader as bt
import backtrader.indicators as btind

class OverUnderMovAv(bt.Indicator):
    lines = ('overunder',)
    params = dict(period=20, movav=btind.MovAv.Simple)

    def __init__(self):
        movav = self.p.movav(self.data, period=self.p.period)
        self.l.overunder = bt.Cmp(movav, self.data)

完成!如果平均值高于数据,则指标将具有“1”值,如果低于数据,则为“-1”。

如果数据是定期数据源,与收盘价进行比较会产生 1 和-1。

尽管在绘图部分可以看到更多内容,并且为了在绘图世界中表现良好,可以添加一些内容:

import backtrader as bt
import backtrader.indicators as btind

class OverUnderMovAv(bt.Indicator):
    lines = ('overunder',)
    params = dict(period=20, movav=bt.ind.MovAv.Simple)

    plotinfo = dict(
        # Add extra margins above and below the 1s and -1s
        plotymargin=0.15,

        # Plot a reference horizontal line at 1.0 and -1.0
        plothlines=[1.0, -1.0],

        # Simplify the y scale to 1.0 and -1.0
        plotyticks=[1.0, -1.0])

    # Plot the line "overunder" (the only one) with dash style
    # ls stands for linestyle and is directly passed to matplotlib
    plotlines = dict(overunder=dict(ls='--'))

    def _plotlabel(self):
        # This method returns a list of labels that will be displayed
        # behind the name of the indicator on the plot

        # The period must always be there
        plabels = [self.p.period]

        # Put only the moving average if it's not the default one
        plabels += [self.p.movav] * self.p.notdefault('movav')

        return plabels

    def __init__(self):
        movav = self.p.movav(self.data, period=self.p.period)
        self.l.overunder = bt.Cmp(movav, self.data)

指标中的混合时间框架

原文:www.backtrader.com/docu/mixing-timeframes/indicators-mixing-timeframes/

发布 1.3.0.92 带来了可能性,使不同时间框架的数据(来自数据源和/或指标)混合。

背景:指标是聪明的愚蠢对象。

  • 他们之所以聪明,是因为他们能进行复杂的计算。

  • 他们之所以愚蠢,是因为他们在没有了解提供计算数据的来源的情况下进行操作

就这样:

  • 如果提供值的数据源具有不同的时间框架,在Cerebro引擎内部的不同长度,指标将会中断。

计算示例,在该示例中,data0 的时间框架为天,而 data1 的时间框架为

pivotpoint = btind.PivotPoint(self.data1)
sellsignal = self.data0.close < pivotpoint.s1

在这里寻找卖出信号当收盘价低于s1线时(1^(st) support

注意

PivotPoint的定义上在一个较大的时间框架内运行

这将导致以下错误:

return self.array[self.idx + ago]
IndexError: array index out of range

出于一个很好的原因:self.data.close 提供了来自第一个瞬间的值,但是PivotPoint(因此s1线)只会在一个完整的月份过去后才会提供值,大致相当于self.data0.close的 22 个值。在这 22 个 收盘价 中还没有s1的值,并且尝试从底层数组中获取它失败。

Lines 对象支持 (ago) 操作符(Python 中的 __call__ 特殊方法)以提供其自身的延迟版本:

close1 = self.data.close(-1)

在此示例中,对象 close1(通过[0]访问)始终包含由close交付的前一个值(-1)。语法已经被重用以适应不同的时间框架。让我们重写上面的pivotpoint片段:

pivotpoint = btind.PivotPoint(self.data1)
sellsignal = self.data0.close < pivotpoint.s1()

注意看() 是如何执行的,不带参数(在后台提供一个None)。以下是正在发生的:

  • pivotpoint.s1() 返回一个内部的 LinesCoupler 对象,该对象遵循较大范围的节奏。该耦合器用最新交付的真实s1值填充自己(从NaN的默认值开始)

但是要使魔术生效,需要一些额外的东西。 Cerebro 必须以以下方式创建:

cerebro = bt.Cerebro(runonce=False)

或者执行:

cerebro.run(runonce=False)

在这种模式下,指标和晚评估的自动 lines 对象是逐步执行而不是在紧密循环中执行。这使整个操作变慢,但这使其可能

上面断开的底部的示例脚本,现在可以运行:

$ ./mixing-timeframes.py

有输出:

0021,0021,0001,2005-01-31,2984.75,2935.96,0.00
0022,0022,0001,2005-02-01,3008.85,2935.96,0.00
...
0073,0073,0003,2005-04-15,3013.89,3010.76,0.00
0074,0074,0003,2005-04-18,2947.79,3010.76,1.00
...

在第 74 行,发生了close < s1的第 1 个实例。

脚本还提供了对其他可能性的见解:耦合指标的所有线。之前我们有:

self.sellsignal = self.data0.close < pp.s1()

作为替代:

pp1 = pp()
self.sellsignal = self.data0.close < pp1.s1

现在整个PivotPoint指标已经耦合,可以访问其任何线(即 p, r1, r2, s1, s2)。脚本只对s1感兴趣,并且访问是直接的。:

$ ./mixing-timeframes.py --multi

输出:

0021,0021,0001,2005-01-31,2984.75,2935.96,0.00
0022,0022,0001,2005-02-01,3008.85,2935.96,0.00
...
0073,0073,0003,2005-04-15,3013.89,3010.76,0.00
0074,0074,0003,2005-04-18,2947.79,3010.76,1.00
...

这里没有什么意外。与以前相同。甚至可以绘制“耦合”对象:

$ ./mixing-timeframes.py --multi --plot

image

完整的耦合语法

对于具有多行的lines对象(例如Indicators,如PivotPoint):

  • obj(clockref=None, line=-1)

    • clockref 如果clockrefNone,则周围的对象(在示例中为Strategy)将是调整较大时间框架(例如:Months)到较小/更快时间框架(例如:Days)的参考

    如果需要,可以使用另一个参考

    line

    * If the default `-1` is given, all *lines* are coupled.
    
    * If another integer (for example, `0` or `1`) a single line will be
      coupled and fetched by index (from `obj.lines[x]`)
    
    * If a string is passed, the line will be fetched by name.
    
      In the sample, the following could have been done:
    
      ```python
    
    coupled_s1 = pp(line='s1')
    
    ```py` 
    

对于具有单行的lines对象(例如来自指标PivotPoint的线s1):

  • obj(clockref=None)(参见上文的clockref

结论

在常规()语法中集成,来自不同时间框架的数据馈送可以在指标中混合使用,始终注意cerebro需要被实例化或者创建,并且runonce=False

脚本代码和用法

backtrader源代码中作为示例提供。用法:

$ ./mixing-timeframes.py --help
usage: mixing-timeframes.py [-h] [--data DATA] [--multi] [--plot]

Sample for pivot point and cross plotting

optional arguments:
  -h, --help   show this help message and exit
  --data DATA  Data to be read in (default: ../../datas/2005-2006-day-001.txt)
  --multi      Couple all lines of the indicator (default: False)
  --plot       Plot the result (default: False)

代码:

from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import argparse

import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind
import backtrader.utils.flushfile

class St(bt.Strategy):
    params = dict(multi=True)

    def __init__(self):
        self.pp = pp = btind.PivotPoint(self.data1)
        pp.plotinfo.plot = False  # deactivate plotting

        if self.p.multi:
            pp1 = pp()  # couple the entire indicators
            self.sellsignal = self.data0.close < pp1.s1
        else:
            self.sellsignal = self.data0.close < pp.s1()

    def next(self):
        txt = ','.join(
            ['%04d' % len(self),
             '%04d' % len(self.data0),
             '%04d' % len(self.data1),
             self.data.datetime.date(0).isoformat(),
             '%.2f' % self.data0.close[0],
             '%.2f' % self.pp.s1[0],
             '%.2f' % self.sellsignal[0]])

        print(txt)

def runstrat():
    args = parse_args()

    cerebro = bt.Cerebro()
    data = btfeeds.BacktraderCSVData(dataname=args.data)
    cerebro.adddata(data)
    cerebro.resampledata(data, timeframe=bt.TimeFrame.Months)

    cerebro.addstrategy(St, multi=args.multi)

    cerebro.run(stdstats=False, runonce=False)
    if args.plot:
        cerebro.plot(style='bar')

def parse_args():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description='Sample for pivot point and cross plotting')

    parser.add_argument('--data', required=False,
                        default='../../datas/2005-2006-day-001.txt',
                        help='Data to be read in')

    parser.add_argument('--multi', required=False, action='store_true',
                        help='Couple all lines of the indicator')

    parser.add_argument('--plot', required=False, action='store_true',
                        help=('Plot the result'))

    return parser.parse_args()

if __name__ == '__main__':
    runstrat()
posted @ 2024-04-15 10:54  绝不原创的飞龙  阅读(92)  评论(0编辑  收藏  举报