BackTrader 中文文档(二十九)
开发一个指标
原文:
www.backtrader.com/blog/posts/2015-07-18-developing-an-indicator/developing-an-indicator/
在对 backtrader 进行了大量的微调之后(因为它已经运行了一段时间),我决定不仅通过 GitHub 分享它,还要告诉世界它的存在,并在"Reddit"上发布了关于其存在的帖子。
在关于为什么交易/算法交易平台会突然出现的评论和关于平台是否支持多个同时交易的实时交易的私人问题之后,我得出了结论,我的孩子应该有自己的博客。
这就是我们所在的地方。但让我们专注于业务。
backtrader
旨在让我快速尝试想法,并检查我的眼睛可能告诉我的是否可能存在机会。
backtrader
(目前)完全是关于回测的,尚未连接到任何实时交易平台,甚至可能不会(尽管我确实相信技术实现是允许的)
当我使用表达式"尝试想法"时,我的意思是两件事:
-
能够快速起草一个指标,并能够直观评估其行为
-
无论如何,都要参与开发围绕该指标或与其他指标组合的潜在策略
我个人的交易是 100%判断性的,没有一个决定是由自动化系统做出的。但我会看看指标所说的内容。无论"指标"是否真的是一个信号,都留给了我有缺陷的人类思维。
但让我们来尝试一下。在第一次发布到 Reddit 后,我添加了一个著名的指标:
- Trix
Stockcharts 在这里有关于 Trix 的很好的讨论:ChartSchool - Trix
让我们尝试如何做到,用尽可能少的行:
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import backtrader as bt
import backtrader.indicators as btind
class MyTrix(bt.Indicator):
lines = ('trix',)
params = (('period', 15),)
def __init__(self):
ema1 = btind.EMA(self.data, period=self.p.period)
ema2 = btind.EMA(ema1, period=self.p.period)
ema3 = btind.EMA(ema2, period=self.p.period)
self.lines.trix = 100.0 * (ema3 - ema3(-1)) / ema3(-1)
Trix 指标已经投入使用。看着这一切,作为该平台的作者,我真的相信我的目标是能够快速地轻松尝试新的想法……已经实现了。
开发的详细过程:
-
lines = (‘trix’,)
这个元组定义了指标的输出线(在本例中只有一个)。类声明开始时的这个语句在类创建和对象实例化过程中产生了大量的背景操作。
可以说,该对象具有一个名为"lines"的属性,其中包含"trix"。
作为额外的奖励,如果指标本身没有使用名为"trix"的名称,则可以通过"self.trix"来获得"line"。但为了清晰起见,我更喜欢"self.lines.trix"
附加访问方法:
-
self.l.trix
-
self.lines[0] … 作为索引对应于元组中的位置
-
-
params = ((‘period’, 15),)
这个元组的元组(也可以是 dict 或 OrderedDict)定义了指标接受的参数并声明了默认值。
解析 kwargs 的负担从用户的肩上卸下来了。
参数可以通过“self.params.xxxxx”表示法或通过速记“self.p.xxxxx”访问。
-
计算(其中 EMA 代表指数移动平均)
-
ema1 = btind.EMA(self.data, period=self.p.period)
新的奖金显示出来了…“self.data”。这似乎突然冒出来,但这又是指标背后进行的预处理。
传递给指标进行计算的任何“数据”都会被拦截并放置在一个
self.datas
数组中,通常可以使用self.datas[0]
来访问第一个数据。缩写确实存在,看起来像:self.data 和 self.data0 用于数组中的第一个数据。从那时起,self.data1,self.data2。
Trix 只需要一个数据
-
ema2 = btind.EMA(ema1, period=self.p.period)
没有太多可说的。EMA 使用 ema1 作为输入数据。
-
ema3 = btind.EMA(ema2, period=self.p.period)
更少的话可说
-
self.lines.trix = 100.0 * (ema3 - ema3(-1)) / ema3(-1)
首先,首先进行了简单的 1 周期百分比差值计算。
魔术 ema3(-1)是一种表示:ema 的上一个值的记法。
计算的结果被分配给了在类创建过程中定义的输出“line”“trix”。
-
轻而易举。但是如果我没有得到 Trix 正在做什么的视觉反馈(即使 Stockcharts 上有一篇很好的文章),我就不会进行“实验”。
注意
实际的 Trix 实现具有一些额外的花哨功能,主要用于美化绘图,对于本帖子没有相关性。
假设我们已经将MyTrix
指标放在了一个 mytrix.py 文件中。
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import backtrader as bt
import backtrader.feeds as btfeeds
from mytrix import MyTrix
class NoStrategy(bt.Strategy):
params = (('trixperiod', 15),)
def __init__(self):
MyTrix(self.data, period=self.p.trixperiod)
if __name__ == '__main__':
# Create a cerebro entity
cerebro = bt.Cerebro()
# Add a strategy
cerebro.addstrategy(NoStrategy, trixperiod=15)
# Create a Data Feed
datapath = ('../datas/2006-day-001.txt')
data = bt.feeds.BacktraderCSVData(dataname=datapath)
# Add the Data Feed to Cerebro
cerebro.adddata(data)
# Run over everything
cerebro.run()
# Plot the result
cerebro.plot()
并且视觉输出如下(在新窗口/标签中打开图表以获取全尺寸图像),希望显示出指标可以多快地创建和通过backtrader
进行视觉评估的简单性。
食谱/资源
介绍
本节包含可直接应用于backtrader的配方和资源,例如指标或第三方商店、经纪人或数据源。
指标
简介
本节收集了一些指标实现,它们不是backtrader核心的一部分。
使用它们:
-
将代码复制到你选择的文件中或直接放在你的策略上方
-
如果复制到文件中,请从文件中导入它
from myfile import TheIndicator`
-
并将其纳入你的策略中
class MyStrategy(bt.Strategy): def __init__(self): self.myind = TheIndicator(self.data, param1=value1, ...)`
绝对强度直方图
这是一个看起来起源于外汇世界的指标,可能在 fxcodebase.com
,但似乎很难追踪实际起源是什么。
潜在地首先在 LUA 中实现:
同样还提到,更新和修订的 LUA 版本可在以下位置找到:
同一站点托管了一个 MQL4
版本:
另一个 MQL5
版本可以在这里找到:
所有版本都似乎很复杂,由于语言和平台实现的原因,MQL5
版本有 227 行代码(当然包括一些注释),并且有轻微的差异,因此有必要确定一个合适的定义。在查看不同版本后的伪代码定义:
# p0 is the current price and p1 is the previous price
if mode is RSI:
bulls = 0.5 * abs(p0 - p1) + p0 - p1
bears = 0.5 * abs(p0 - p1) - p0 + p1
elif mode is STOCH:
bulls = p0 - lowest(period)
bears = highest(period - p1
avgbulls = moving_average(bulls, period)
avgbears = moving_average(bears, period)
smoothedbulls = moving_average(bulls, smoothing_period) / pointsize
smoothedbears = moving_average(bears, smoothing_period) / pointsize
ash = smoothedbulls - smoothedbears
RSI
/ STOCH
命名是原始实现者选择的,pointsize
也被称为 pipsize
,反映了其 外汇 起源。目前尚不清楚 平滑平均值 是否必须与以前的移动平均值相同,一些版本似乎使用已经平均化的价格而不是标准价格。移动平均线是使用 整数 选择的。
一些决定
-
该指标将使用数据提供的原始价格进行计算。如果用户希望在平均价格上运行指标,可以传递它,而不是传递原始价格。
-
RSI
模式中的 0.5 倍乘数将作为参数 -
移动平均线不会被任何类型的 整数 选择。在 backtrader 中,可以将实际所需的移动平均线作为参数传递。
-
平滑移动平均线,除非作为参数指定,否则将与指标中已使用的移动平均线相同
-
除非用户将值指定为参数,否则将不使用
pointsize
。
这里是实现
class ASH(bt.Indicator):
alias = ('AbsoluteStrengthOscilator',)
lines = ('ash', 'bulls', 'bears',) # output lines
# customize the plotting of the *ash* line
plotlines = dict(ash=dict(_method='bar', alpha=0.33, width=0.66))
RSI, STOCH = range(0, 2) # enum values for the parameter mode
params = dict(
period=9,
smoothing=2,
mode=RSI,
rsifactor=0.5,
movav=bt.ind.WMA, # WeightedMovingAverage
smoothav=None, # use movav if not specified
pointsize=None, # use only if specified
)
def __init__(self):
# Start calcs according to selected mode
if self.p.mode == self.RSI:
p0p1 = self.data - self.data(-1) # used twice below
half_abs_p0p1 = self.p.rsifactor * abs(p0p1) # used twice below
bulls = half_abs_p0p1 + p0p1
bears = half_abs_p0p1 - p0p1
else:
bulls = self.data - bt.ind.Lowest(self.data, period=self.p.period)
bears = bt.ind.Highest(self.data, period=self.p.period) - self.data
avbulls = self.p.movav(bulls, period=self.p.period)
avbears = self.p.movav(bears, period=self.p.period)
# choose smoothing average and smooth the already averaged values
smoothav = self.p.smoothav or self.p.movav # choose smoothav
smoothbulls = smoothav(avbulls, period=self.p.smoothing)
smoothbears = smoothav(avbears, period=self.p.smoothing)
if self.p.pointsize: # apply only if it makes sense
smoothbulls /= self.p.pointsize
smoothbears /= self.p.pointsize
# Assign the final values to the output lines
self.l.bulls = smoothbulls
self.l.bears = smoothbears
self.l.ash = smoothbulls - smoothbears
这里展示了指标的工作方式
康纳斯 RSI
Google 提供的此指标参考文献:
两个来源对公式都表示同意,尽管术语不同(见下文)。 应按以下方式计算 Connors RSI:
CRSI(3, 2, 100) = [RSI(3) + RSI(Streak, 2) + PercentRank(100)] / 3
注意
TradingView 表示 ROC
("变动率")必须用于 PctRank
("百分比排名")的位置,但从 TradingView 自身提供的定义来看,显然是错误的。
Streak
是一个非标准术语,需要定义,让我们从源(在 TradingView 行话中称为 "UpDown") 中参考它。
-
每日价格收盘比前一日高/低的连续天数
-
如果某日收盘价与前一日相同,则连续数重置为
0
-
向上连续产生正值,向下连续产生负值
手头有公式,理解需要使用 PercentRank
以及对 Streak
(或 UpDown
)有清晰定义,创建 ConnorsRSI
指标应该轻而易举。
class Streak(bt.ind.PeriodN):
'''
Keeps a counter of the current upwards/downwards/neutral streak
'''
lines = ('streak',)
params = dict(period=2) # need prev/cur days (2) for comparisons
curstreak = 0
def next(self):
d0, d1 = self.data[0], self.data[-1]
if d0 > d1:
self.l.streak[0] = self.curstreak = max(1, self.curstreak + 1)
elif d0 < d1:
self.l.streak[0] = self.curstreak = min(-1, self.curstreak - 1)
else:
self.l.streak[0] = self.curstreak = 0
class ConnorsRSI(bt.Indicator):
'''
Calculates the ConnorsRSI as:
- (RSI(per_rsi) + RSI(Streak, per_streak) + PctRank(per_rank)) / 3
'''
lines = ('crsi',)
params = dict(prsi=3, pstreak=2, prank=100)
def __init__(self):
# Calculate the components
rsi = bt.ind.RSI(self.data, period=self.p.prsi)
streak = Streak(self.data)
rsi_streak = bt.ind.RSI(streak, period=self.p.pstreak)
prank = bt.ind.PercentRank(self.data, period=self.p.prank)
# Apply the formula
self.l.crsi = (rsi + rsi_streak + prank) / 3.0
这里展示了指标的工作原理,还包括Streak
辅助指标,以便视觉上验证实际连续数的输出。
Donchian 通道
class DonchianChannels(bt.Indicator):
'''
Params Note:
- `lookback` (default: -1)
If `-1`, the bars to consider will start 1 bar in the past and the
current high/low may break through the channel.
If `0`, the current prices will be considered for the Donchian
Channel. This means that the price will **NEVER** break through the
upper/lower channel bands.
'''
alias = ('DCH', 'DonchianChannel',)
lines = ('dcm', 'dch', 'dcl',) # dc middle, dc high, dc low
params = dict(
period=20,
lookback=-1, # consider current bar or not
)
plotinfo = dict(subplot=False) # plot along with data
plotlines = dict(
dcm=dict(ls='--'), # dashed line
dch=dict(_samecolor=True), # use same color as prev line (dcm)
dcl=dict(_samecolor=True), # use same color as prev line (dch)
)
def __init__(self):
hi, lo = self.data.high, self.data.low
if self.p.lookback: # move backwards as needed
hi, lo = hi(self.p.lookback), lo(self.p.lookback)
self.l.dch = bt.ind.Highest(hi, period=self.p.period)
self.l.dcl = bt.ind.Lowest(lo, period=self.p.period)
self.l.dcm = (self.l.dch + self.l.dcl) / 2.0 # avg of the above
资金流动指标
参考
class MFI(bt.Indicator):
lines = ('mfi',)
params = dict(period=14)
alias = ('MoneyFlowIndicator',)
def __init__(self):
tprice = (self.data.close + self.data.low + self.data.high) / 3.0
mfraw = tprice * self.data.volume
flowpos = bt.ind.SumN(mfraw * (tprice > tprice(-1)), period=self.p.period)
flowneg = bt.ind.SumN(mfraw * (tprice < tprice(-1)), period=self.p.period)
mfiratio = bt.ind.DivByZero(flowpos, flowneg, zero=100.0)
self.l.mfi = 100.0 - 100.0 / (1.0 + mfiratio)
这是指标运作的一个视角
随机指标(通用)
原文:
www.backtrader.com/recipes/indicators/stochastic/stochastic/
backtrader已经包含了一个Stochastic
指标(包括一种显示三条计算线而不仅仅是通常的两条%k
和%d
线的变体)
但这种指标假设用于计算的数据源具有high
、low
和close
组件。这是因为原始定义使用了这些组件。
如果想要使用不同的组件,一个首要的方法可能是创建一个数据源,将不同的组件存储在数据源的high
、low
和close
行中。
但一个更直接的方法是拥有一个通用Stochastic
指标,它接受三(3)个数据组件,并将它们用作如果它们是high
、low
和close
组件。
下面的代码执行此操作,并通过允许自定义第二次平滑的移动平均值添加了一个不错的功能。
class Stochastic_Generic(bt.Indicator):
'''
This generic indicator doesn't assume the data feed has the components
``high``, ``low`` and ``close``. It needs three data sources passed to it,
which whill considered in that order. (following the OHLC standard naming)
'''
lines = ('k', 'd', 'dslow',)
params = dict(
pk=14,
pd=3,
pdslow=3,
movav=bt.ind.SMA,
slowav=None,
)
def __init__(self):
# Get highest from period k from 1st data
highest = bt.ind.Highest(self.data0, period=self.p.pk)
# Get lowest from period k from 2nd data
lowest = bt.ind.Lowest(self.data1, period=self.p.pk)
# Apply the formula to get raw K
kraw = 100.0 * (self.data2 - lowest) / (highest - lowest)
# The standard k in the indicator is a smoothed versin of K
self.l.k = k = self.p.movav(kraw, period=self.p.pd)
# Smooth k => d
slowav = self.p.slowav or self.p.movav # chose slowav
self.l.d = slowav(k, period=self.p.pdslow)
当然,需要验证当给定相同的输入集时,该指标是否确实产生与标准指标相同的结果。下面的图表是使用这组指令创建的。
# Generate 3 data feeds
d0 = bt.ind.EMA(self.data.high, period=14)
d1 = bt.ind.EMA(self.data.low, period=14)
d2 = bt.ind.EMA(self.data.close, period=14)
Stochastic_Generic(d0, d1, d2) # customized stochastic
# These two have generate the same results
Stochastic_Generic(self.data.high, self.data.low, self.data.close)
bt.ind.Stochastic(self.data)
这里是指标如何工作的视图
绝对强度柱
Stockcharts 和 Investopedia 关于此指标有文献。
公式如下:
StochRSI = (RSI - min(RSI, period)) / (max(RSI, period) - min(RSI, period))
理论上,用于计算RSI
的周期与随后用于查找RSI
的最小值和最大值的周期相同。这意味着如果选择的周期是14
(事实上的标准)用于RSI
,则指标的总回溯期将为28
注意
实际回溯期将稍长,因为 14
期RSI
的有效回溯期为 15
,因为需要比较前两个周期的收盘价格来启动计算
在任何情况下,backtrader 会自动计算所有必需的回溯期和预热期。
考虑到以下是 backtrader 中的内置指标:
-
RSI
-
Lowest
(又称MaxN
) -
Highest
(又称MinN
)
根据上述公式开发StochRSI
是直接的。
class StochRSI(bt.Indicator):
lines = ('stochrsi',)
params = dict(
period=14, # to apply to RSI
pperiod=None, # if passed apply to HighestN/LowestN, else "period"
)
def __init__(self):
rsi = bt.ind.RSI(self.data, period=self.p.period)
pperiod = self.p.pperiod or self.p.period
maxrsi = bt.ind.Highest(rsi, period=pperiod)
minrsi = bt.ind.Lowest(rsi, period=pperiod)
self.l.stochrsi = (rsi - minrsi) / (maxrsi - minrsi)
这里是指标工作原理的一瞥
商店/经纪人/数据源
介绍
这一部分托管了一系列第三方存储(或单独的经纪人 / 数据源)的实现,它们不是backtrader核心的一部分。
bt-ccxt-store
原文:
www.backtrader.com/recipes/storesbrokersdata/bt-ccxt-store/bt-ccxt-store/
bt-ccxt-store
作者 Dave Vallance
仓库链接:
Metaquotes MQL 5 - API
首次由用户 Nikolai
在这里宣布。
仓库链接:
NorgateData
原文:
www.backtrader.com/recipes/storesbrokersdata/norgatedata/norgatedata/
包的链接:
Oanda v20
原文:
www.backtrader.com/recipes/storesbrokersdata/oandav20/oandav20/
由用户 ftomassetti
仓库链接:
TradingView
原文:
www.backtrader.com/recipes/storesbrokersdata/tradingview/tradingview/
用户为 Dave-Vallance
仓库链接:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
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