Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Indicator Development

If anything (besides one or more winning Strategies) must ever be developed,this something is a custom Indicator.

Such development within the platform is, according to the author, easy.

The following is needed:

  • A class derived from Indicator (either directly or from an already existing subclass)

  • Define thelines it will hold

    An indicator must at least have 1 line. If deriving from an existing one,the line(s) may have already be defined

  • Optionally define parameters which can alter the behavior

  • Optionally provided/customize some of the elements which enable sensible plotting of the indicators

  • Provide a fully defined operation in__init__ with a binding (assignment) to the line(s) of the indicator or else providenext and (optionally)once methods

    If an indicator can be fully defined with logic/arithmetic operations duringinitialization and the result is assigned to the line: done

    Be it not the case, at least anext has to be provided where the indicatormust assign a value to the line(s) at index 0

    Optimization of the calculation for therunonce mode (batch operation) canbe achieved by providing aonce method.

Important note: Idempotence

Indicators produce an output for each bar they receive. No assumption has to bemade about how many times the same bar will be sent. Operations have to beidempotent.

The rationale behind this:

  • The same bar (index-wise) can be sent many times with changing values (namely the changing value is the closing price)

This enables, for example, “replaying” a daily session but using intraday datawhich could be made of 5 minutes bars.

It could also allow the platform to get values from a live feed.

A dummy (but functional) indicator

So can it be:

classDummyInd(bt.Indicator):lines=('dummyline',)params=(('value',5),)def__init__(self):self.lines.dummyline=bt.Max(0.0,self.params.value)

Done! The indicator will output always the same value: either 0.0 orself.params.value if it happens to be greater than 0.0.

The same indicator but using the next method:

classDummyInd(bt.Indicator):lines=('dummyline',)params=(('value',5),)defnext(self):self.lines.dummyline[0]=max(0.0,self.params.value)

Done! Same behavior.

Note

Notice how in the__init__ versionbt.Max is used to assign tothe Line objectself.lines.dummyline.

bt.Max returns anlines object that is automatically iterated foreach bar passed to the indicator.

Hadmax been used instead, the assigment would have beenpointless, because instead of a line, the indicator would have amember variable with a fixed value.

Duringnext the work is done directly with floating point valuesand the standardmax built-in can be used

Let’s recall thatself.lines.dummyline is the long notation and that it canbe shortened to:

  • self.l.dummyline

and even to:

  • self.dummyline

The latter being only possible if the code has not obscured this with a memberattribute.

The 3rd and last version provides an additionalonce method to optimize thecalculation:

classDummyInd(bt.Indicator):lines=('dummyline',)params=(('value',5),)defnext(self):self.lines.dummyline[0]=max(0.0,self.params.value)defonce(self,start,end):dummy_array=self.lines.dummyline.arrayforiinxrange(start,end):dummy_array[i]=max(0.0,self.params.value)

A lot more effective but developing theonce method has forced to scratch beyondthe surface. Actually the guts have been looked into.

The__init__ version is in any case the best:

  • Everything is confined to the initialization

  • next andonce (both optimized, becausebt.Max already has them) are provided automatically with no need to play with indices and/or formulas

Be it needed for development, the indicator can also override the methodsassociated tonext andonce:

  • prenext andnexstart

  • preonce andoncestart

Manual/Automatic Minimum Period

If possible the platform will calculate it, but manual action may be needed.

Here is a potential implementation of aSimple Moving Average:

classSimpleMovingAverage1(Indicator):lines=('sma',)params=(('period',20),)defnext(self):datasum=math.fsum(self.data.get(size=self.p.period))self.lines.sma[0]=datasum/self.p.period

Although it seems sound, the platform doesn’t know what the minimum period is,even if the parameter is named “period” (the name could be misleading and someindicators receive several “period”s which have different usages)

In this casenext would be called already for the 1st bar and everthingwould explode because get cannot return the neededself.p.period.

Before solving the situation something has to be taken into account:

  • The data feeds passed to the indicators may already carry aminimum period

The sampleSimpleMovingAverage may be done on for example:

  • A regular data feed

    This has a default mininum period of 1 (just wait for the 1st bar thatenters the system)

  • Another Moving Average … and this in turn already has aperiod

    If this is 20 and again our sample moving average has also 20, we end upwith a minimum period of 40 bars

    Actually the internal calculation says 39 … because as soon as the firstmoving average has produced a bar this counts for the next moving average,which creates an overlapping bar, thus 39 are needed.

  • Other indicators/objects which also carry periods

Alleviating the situation is done as follows:

classSimpleMovingAverage1(Indicator):lines=('sma',)params=(('period',20),)def__init__(self):self.addminperiod(self.params.period)defnext(self):datasum=math.fsum(self.data.get(size=self.p.period))self.lines.sma[0]=datasum/self.p.period

Theaddminperiod method is telling the system to take into account the extraperiod bars needed by this indicator to whatever minimum period there may bein existence.

Sometimes this is absolutely not needed, if all calculations are done withobjects which already communicate its period needs to the system.

A quickMACD implementation with Histogram:

frombacktrader.indicatorsimportEMAclassMACD(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-me2self.l.signal=EMA(self.l.macd,period=self.p.period_signal)self.l.histo=self.l.macd-self.l.signal

Done! No need to think about mininum periods.

  • EMA stands forExponential Moving Average (a platform built-in alias)

    And this one (already in the platform) already states what it needs

  • The named lines of the indicator “macd” and “signal” are being assigned objects which already carry declared (behind the scenes) periods

    • macd takes the period from the operation “me1 - me2” which has in turn take the maximum from the periods of me1 and me2 (which are both exponential moving averages with different periods)

    • signal takes directly the period of the Exponential Moving Average over macd. This EMA also takes into account the already existing macd period and the needed amount of samples (period_signal) to calculate itself

    • histo takes the maximum of the two operands “signal - macd”. Once both are ready can histo also produce a value

A full custom indicator

Let’s develop a simple custom indicator which “indicates” if a moving average(which can be modified with a parameter) is above the given data:

importbacktraderasbtimportbacktrader.indicatorsasbtindclassOverUnderMovAv(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)

Done! The indicator will have a value of “1” if the average is above the dataand “-1” if below.

Be the data a regular data feed the 1s and -1s would be produced comparing withthe close price.

Although more can be seen in thePlotting section and to have a behaved andnice citizen in the plotting world, a couple of things can be added:

importbacktraderasbtimportbacktrader.indicatorsasbtindclassOverUnderMovAv(bt.Indicator):lines=('overunder',)params=dict(period=20,movav=bt.ind.MovAv.Simple)plotinfo=dict(# Add extra margins above and below the 1s and -1splotymargin=0.15,# Plot a reference horizontal line at 1.0 and -1.0plothlines=[1.0,-1.0],# Simplify the y scale to 1.0 and -1.0plotyticks=[1.0,-1.0])# Plot the line "overunder" (the only one) with dash style# ls stands for linestyle and is directly passed to matplotlibplotlines=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 thereplabels=[self.p.period]# Put only the moving average if it's not the default oneplabels+=[self.p.movav]*self.p.notdefault('movav')returnplabelsdef__init__(self):movav=self.p.movav(self.data,period=self.p.period)self.l.overunder=bt.Cmp(movav,self.data)

[8]ページ先頭

©2009-2025 Movatter.jp