BackTrader 中文文档(五)
策略
策略
Cerebro
实例是 backtrader
的泵心和控制大脑。对于平台用户来说,Strategy
是相同的。
策略 在方法中表达的生命周期
注意
策略可以通过从模块 backtrader.errors
中引发 StrategySkipError
异常来在出生期间中断
这将避免在回测过程中执行策略。请参阅Exceptions
部分
-
概念:
__init__
显然这是在实例化期间调用的:
indicators
将在此处创建以及其他所需的属性。例如:def __init__(self): self.sma = btind.SimpleMovingAverage(period=15)`
-
出生:
start
世界(
cerebro
)告诉策略是时候开始行动了。存在一个默认的空方法。 -
童年期:
prenext
在概念阶段声明的
indicators
将对策略需要成熟的时间施加限制:这称为最小周期
。在__init__
中创建了一个带有period=15
的 SimpleMovingAverage。只要系统看到的条数少于
15
条,将调用prenext
(默认实现是空操作) -
成年期:
next
一旦系统看到了
15
条数据,并且SimpleMovingAverage
有足够大的缓冲区开始产生值,策略就足够成熟以实际执行。有一个
nextstart
方法,它被调用一次,标记从prenext
到next
的切换。nextstart
的默认实现只是调用next
-
繁殖:
None
好的,策略实际上并没有繁殖。但从某种意义上来说,它们确实如此,因为系统将会多次实例化它们(使用不同的参数)如果优化
-
死亡:
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
方法没有被覆盖 -
prenext
和nexstart
没有被覆盖 -
在
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
方法,用于取消尚未执行的订单
如何购买/出售/平仓
Buy
和Sell
方法生成订单。调用时,它们返回一个Order
(或子类)实例,可用作引用。此订单具有唯一的ref
标识符,可用于比较
注意
特定经纪人实现的Order
子类可能携带经纪人提供的其他唯一标识符。
要创建订单,请使用以下参数:
-
data
(默认值:None
)必须为其创建订单的数据。如果是
None
,那么系统中的第一个数据,self.datas[0] or self.data0
(又名self.data
)将被使用 -
size
(默认值:None
)要使用的数据单位(正数)的大小,用于订单
如果是
None
,则将使用通过getsizer
检索的sizer
实例来确定大小。 -
price
(默认值:None
)要使用的价格(现实经纪人可能对实际格式施加限制,如果不符合最低跳动大小要求)
对于
Market
和Close
订单,None
是有效的(市场确定价格)对于
Limit
、Stop
和StopLimit
订单,此值确定触发点(在Limit
的情况下,触发显然是订单应该匹配的价格) -
plimit
(默认值:None
)仅适用于
StopLimit
订单。这是触发Limit订单的价格,一旦Stop被触发(使用了price
) -
exectype
(默认值:None
)可能的值:
-
Order.Market
或None
。市价单将以下一个可用价格执行。在回测中,它将是下一个柱的开盘价 -
Order.Limit
。只能以给定的price
或更好的价格执行的订单 -
Order.Stop
。当价格达到price
时触发的订单,并像Order.Market
订单一样执行 -
Order.StopLimit
。当价格达到price
时触发的订单,并作为隐式Limit订单以由pricelimit
给出的价格执行
-
-
valid
(默认值:None
)可能的值:
-
None
:这将生成一个不会过期的订单(又称Good til cancel),并保持在市场上直到匹配或取消。实际上,经纪人倾向于强加一个时间限制,但通常是在很远的时间内,可以考虑它不会过期 -
datetime.datetime
或datetime.date
实例:将使用该日期生成订单,有效期直到给定的日期时间(又称截止日期) -
Order.DAY
或0
或timedelta()
: 生成到 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
: 已通知的订单列表。交易可以像订单一样多次出现在列表中。
注意
请记住,prenext
、nextstart
和 next
可能会针对同一时间点被多次调用(当使用日线时,每次 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 大小要求)
对于
Market
和Close
订单,None
是有效的(市场决定价格)对于
Limit
、Stop
和StopLimit
订单,此值确定触发点(在Limit
的情况下,触发显然是订单应匹配的价格) -
plimit
(默认为None
)仅适用于
StopLimit
订单。这是触发隐含 Limit 订单的价格,一旦 Stop 被触发(price
已被使用) -
trailamount
(默认为None
)如果订单类型是
StopTrail
或StopTrailLimit
,这是一个绝对数值,用于确定距离价格的距离(对于卖单是下方,对于买单是上方)以保持跟踪止损 -
trailpercent
(默认为None
)如果订单类型是
StopTrail
或StopTrailLimit
,这是一个百分比数值,用于确定距离价格的距离(对于卖单是下方,对于买单是上方)以保持跟踪止损(如果还指定了trailamount
,它将被使用) -
exectype
(默认值:None
)可能的值:
-
Order.Market
或None
. 一个市价订单将以下一个可用价格执行。 在回测中,它将是下一个 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.datetime
或datetime.date
实例:日期将用于生成一个直到给定日期时间(又称good till date)的有效订单 -
Order.DAY
或0
或timedelta()
: 一个有效期至会话结束(又称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
:其他经纪人实现可能支持额外的参数。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
订单。 -
返回
- 提交的订单
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
适用于Market
和Close
订单(市场确定价格)对于
Limit
、Stop
和StopLimit
订单,此值确定触发点(在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
它返回以下之一:
- 生成的订单
或
- 如果没有下达订单则为
None
(target == 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)
下达订单以将头寸重新平衡为当前投资组合value
的target
百分比的最终价值
target
以小数表示:0.05
-> 5%
它使用order_target_value
来执行订单。
示例
-
target=0.05
且投资组合价值为100
-
要达到的
value
为0.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
则买入(减少空头头寸)
-
它返回以下之一:
- 生成的订单
或
- 如果没有下达订单,则为
None
(target == 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_START
和SESSION_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
实例或一个数据源实例。None
:when
按照面值解释(即使它不是 UTC 时间也会被处理为 UTC 时间)pytz
实例:when
将被解释为由时区实例指定的本地时间。data feed
实例:when
将被解释为数据源实例参数tz
指定的本地时间。注意
如果
when
是SESSION_START
或SESSION_END
,并且tzdata
是None
,则系统中的第一个数据源(即self.data0
)将被用作查找会话时间的参考。 -
cheat
(默认为False
)如果为True
,则在经纪人有机会评估订单之前将调用计时器。这打开了在会话开始之前根据开盘价发出订单的可能性 -
*args
:任何额外的参数将被传递给notify_timer
-
**kwargs
:任何额外的 kwargs 将被传递给notify_timer
-
返回值:
- 创建的计时器
notify_timer(timer, when, *args, **kwargs)
接收定时器通知,其中timer
是由add_timer
返回的定时器,when
是调用时间。args
和kwargs
是传递给add_timer
的任何额外参数。
实际的when
时间可能会晚一些,但系统可能在之前无法调用定时器。这个值是定时器值,而不是系统时间。
带信号的策略
也可以操作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
: 此信号的long
和short
指示均被采纳。 -
LONG
:-
采用
long
指示进入long
-
采用
short
指示来close长头寸。但是: -
如果系统中存在
LONGEXIT
(见下文)信号,则将用于退出长头寸 -
如果有
SHORT
信号可用,但没有LONGEXIT
可用,则会在开放short
之前关闭long
。
-
-
SHORT
:-
short
指示用于开空头寸 -
long
指示用于关闭空头寸。但是: -
如果系统中存在一个
SHORTEXIT
(见下文)信号,它将被用于退出短头寸 -
如果有
LONG
信号可用且没有SHORTEXIT
可用,则将用于在开仓long
之前关闭short
-
退出组:
这两个信号旨在覆盖其他信号,并提供退出long
/ short
头寸的标准
-
LONGEXIT
:short
指示用于退出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
输出
注意:
-
信号被绘制出来。这是正常的,因为它只是一个指标,适用于它的绘图规则
-
策略实际上是
long
和short
的。这可以看出,因为现金水平从未回到值水平 -
旁注:即使是一个愚蠢的想法...(没有佣金)策略也没有亏钱...
第二次运行:仅限长头寸
$ ./signals-strategy.py --plot --signal longonly
输出
注意:
-
在每次卖出后,现金水平都会回到值水平,这意味着策略已经退出市场
-
旁注:再次没有损失金钱……
第三次运行:仅限短头寸
$ ./signals-strategy.py --plot --signal shortonly
输出
注意:
-
第 1 次操作是卖出,符合预期,并且发生在上述两个示例中的第 1 次操作之后。只有在
close
低于SMA
并且简单的减法得到负数时才会发生 -
在每次买入后,现金水平都会回到值水平,这意味着策略已经退出市场
-
旁注:最终系统亏损
第四次运行:长+长退出
$ ./signals-strategy.py --plot --signal longonly --exitsignal longexit
输出
注意:
-
许多交易都是相同的,但有些会在快速移动平均线在退出信号中向下穿过慢速移动平均线时中断
-
该系统展示了其仅做多属性,现金在每次交易结束时的价值。
-
旁注:再次赚钱……甚至有些修改后的交易也能获利
使用方法
$ ./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()
策略参考
内置策略的参考
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
:此信号的long
和short
指示都会被采纳 -
LONG
:-
long
指示被认为是开多头 -
short
指示被认为是用于关闭多头仓位。但是: -
如果系统中存在
LONGEXIT
(见下文)信号,则会用于退出多头 -
如果有
SHORT
信号可用且没有LONGEXIT
可用,则会用于在开启short
之前关闭long
-
-
SHORT
:-
short
指示被认为是开空头 -
long
指示被认为是用于关闭空头仓位。但是: -
如果系统中存在
SHORTEXIT
(见下文)信号,则会用于退出空头 -
如果有
LONG
信号可用且没有SHORTEXIT
可用,则会用于在开启long
之前关闭short
-
退出组:
这两个信号旨在覆盖其他信号并提供退出long
/short
仓位的标准。
-
LONGEXIT
:short
指示被认为是用于退出long
仓位 -
SHORTEXIT
:long
指示被认为是用于退出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)
指标
使用指标
在平台中,指标可以在两个地方使用:
-
在策略内部
-
在其他指标内部
指标的作用
-
Indicators
始终在Strategy中的__init__
期间实例化 -
在
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.close
和self.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
。其他构造也是如此,比如Or
和If
显然,__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]
。一些指标提供诸如
upperband
和lowerband
之类的参数,实际上用于操作 y ticks。 -
plothlines
(默认值:[]
)用于控制沿指标轴绘制水平线。
如果传递了一个空列表,将不会绘制任何水平线。
对于像随机指标这样的指标,绘制已知的行业标准线可能是有意义的,例如:
[20.0, 80.0]
。一些指标提供诸如
upperband
和lowerband
之类的参数,实际上用于操作水平线。 -
plotyhlines
(默认值:[]
)用于同时使用单个参数控制 plotyticks 和 plothlines。
-
plotforce
(默认值:False
)如果由于某种原因你认为某个指标应该绘制但却没有绘制……将此设置为
True
作为最后的手段。
指标开发
如果除了一个或多个获胜策略之外,还必须开发其他内容,则此内容是自定义指标。
根据作者的说法,平台内的这种开发是简单的。
需要以下内容:
-
从指标派生的类(直接或从已经存在的子类)
-
定义它将保持的线条
指标必须至少有一条线。如果是从现有指标派生的,则可能已经定义了线条(们)
-
可选地定义可以更改行为的参数
-
可选地提供/自定义一些元素,以便合理地绘制指标
-
在
__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__
版本无论如何都是最好的:
-
一切都限于初始化
-
next
和once
(都经过优化,因为bt.Max
已经包含了它们)会自动提供,无需操作索引和/或公式
如果需要开发,指标还可以覆盖与 next
和 once
关联的方法:
-
prenext
和nexstart
-
preonce
和oncestart
手动/自动最小周期
如果可能的话,平台会计算,但可能需要手动操作。
这是一个简单移动平均的潜在实现:
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
完整的耦合语法
对于具有多行的lines对象(例如Indicators,如PivotPoint
):
-
obj(clockref=None, line=-1)
clockref
如果clockref
是None
,则周围的对象(在示例中为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()
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验
2023-04-15 Python 人工智能:21~23
2023-04-15 Python 人工智能:16~20
2023-04-15 Python 人工智能:11~15
2023-04-15 Python 人工智能:6~10
2023-04-15 Python 人工智能:1~5
2023-04-15 TensorFlow 卷积神经网络实用指南:6~10
2023-04-15 TensorFlow 卷积神经网络实用指南:1~5