Crude Swing Indicator help
-
Hello All,
I want to start building an algorithm to detect swing highs / lows and plot them on the chart. Having looked through the docs I have a couple of questions which I think may just be due to a lack of programming experience on my part.Below is a very crude starting point. My aim below is to get a basic understanding of how to plot on the chart and review multiple periods backwards.
class SwingInd(bt.Indicator): lines = ('swingline',) def __init__(self): self.addminperiod(7) def next(self): #crude swing up highs = self.data.lines.high lows = self.data.lines.low if all([highs[-3] > i for i in [highs[-0],highs[-1],highs[-2], highs[-4],highs[-5],highs[-6]]]): self.lines.swingline[-3] = 1 elif all([lows[-3] < i for i in [lows[-0],lows[-1],lows[-2],lows[-4],lows[-5],lows[-6]]]): self.lines.swingline[-3] = 0 else: self.lines.swingline[-3] = 0.5
My questions are:
- Is it possible to slice the lines array? I get an error when I try to do it. I would like to tidy up the long list of individual index references (high[-1], high[-2] etc)
- Is it possible to plot the indicator as arrows on the main chart? I saw a tutorial regarding how to change the style of the buySell observers arrows but I was not sure if how to apply that to an indicator.
-
A few hints here. Someone else may have some other specifics.
- You can probably do most of what you are trying to do in the
__init__()
phase. - When referencing different indexes while in
__init__()
, use self.data.high(-3) (note theparens
instead of brackets) - There are also a number of basic tools available, for example to get the Highest value over some period. (
bt.indicators.Highest(self.data.high, period=self.p.period)
)
In my experience, I can never spend enough time looking at all of the existing examples in the backtrader/indicators directory. I always learn something new.
- You can probably do most of what you are trying to do in the
-
@RandyT is pointing out at the canonical way of writing indicators. Canonical in the sense that those ideas are what motivated backtrader (at the end of the day the user is king)
Is it possible to slice the lines array? I get an error when I try to do it. I would like to tidy up the long list of individual index references (high[-1], high[-2] etc)
No. The reason is that
[0]
is the present,[-1]
is the past and[1]
is the future (for example for theIchimoku
indicator). See the following example for slicing:lasthighs = self.data.high[3:-1]
That's perfectly acceptable in Python but within the realm of backtrader it means ... "give me something starting from the future and ending in the past. And to start with there is nothing in the future (the note about
Ichimoku
is about projecting values into the future and not because the values are there)Can something be done? Yes. To cover those use cases where an array is needed:
-
self.data.high.get(ago=0, size=3)
ago
indicates where to start (looking backwards) fetching data.0
is now,-1
is the previous bar,-2
is two bars agosize
how many items to return
Is it possible to plot the indicator as arrows on the main chart? I saw a tutorial regarding how to change the style of the buySell observers arrows but I was not sure if how to apply that to an indicator.
The declarations for plotting are the same for observers and indicators. The main documentation is here: Docs - Plotting Look for
plotinfo
andplotlines
.Some examples to decide how to best plot your lines and on which scale:
-
Plots on the data.
-
The markers for the
BuySell
observer can be seen in the source for it and which markers actually plot arrows in the blog post aboutSource - BuySell Observer
Blog - Arrows for the BuySell Observer
BUT - Your indicators delivers values of
1.0
,0.5
and0
and that scale is for sure not going to be compatible with the scale of the data. It doesn't make sense to plot on the data unless you scale the values to properly fit the scale of the data.A quick canonical (non-tested and typo-prone) implementation ... but take into account that two values could have the same high or the same low
(Delivering
1.0
,0
and-1.0
which makes it usable as a signal)class SwingInd(bt.Indicators) lines = ('swingline',) params = (('period', 7),) def __init__(self): hhigh = bt.ind.Highest(self.data.high, period=self.p.period) llow = bt.ind.Lowest(self.data.low, period=self.p.period) hswing = hhigh == self.data.high(-self.p.period // 2) lswing = llow == self.data.low(-self.p.period // 2) # (period) to shift the result back self.lines.swingline = bt.If(hswing, 1.0, bt.If(lswing, -1.0, 0))(self.p.period // 2)
bt.If
is needed because there is no way to override pythonif
statement.Your implementation with some
get
class SwingInd(bt.Indicator): lines = ('swingline',) def __init__(self): self.addminperiod(7) def next(self): #crude swing up highs = self.data.high.get(size=7) lows = self.data.low.get(size=7) if highs.pop(3) > max(highs): self.lines.swingline[-3] = 1 elif lows.pop(3) > min(lows): self.lines.swingline[-3] = 0 else: self.lines.swingline[-3] = 0.5
-
-
Amazing... Thanks for the insights and help. Really appreciate the time you have taken to give such comprehensive answers.
-
Reconsidering this Indicator, it should probably be called something else like Study, Analysis, Post-Analysis, Forensics ...
The reason is that the actual produced value is being stored at in the past
-3
(or-self.p.period // 2
) instead of at0
.For an analysis with frozen data the code indicates where the swing points are, but as a practical indicator, it should point out at position
0
that something happened which has to be acted upon ... now.