Custom indicator, how datas work in init
-
Hi,
I'm trying to understand how to build a custom indicator. Let's see this one:
import backtrader as bt class DummyDifferenceDivider(bt.Indicator): lines = ('diffdiv',) # output line (array) params = ( ('period', 1), # distance to previous data point ('divfactor', 2.0), # factor to use for the division ) def __init__(self): diff = self.data(0) - self.data(-self.p.period) diffdiv = diff / self.p.divfactor self.lines.diffdiv = diffdiv
(source: https://medium.com/@danjrod/custom-indicator-development-in-python-with-backtrader-bc775552dc3e by Daniel Rodriguez)
What does this line do?
diff = self.data(0) - self.data(-self.p.period)
Daniel writes it should be self-explanatory, but i don't understand it.
It seems to me that he's subtracting two datafeeds (but this is impossible as I tried to run the code with only one datafeed and it runs). At first, I thought that it was subtracting the closing value of today and today-period, but I don't understand why it's not written as:
diff = self.datas[0].close[0] - self.datas[0].close[-self.p.period]
Thanks for any insights...
-
@marsario
When doing calculations on the whole line at once, you use round brackets and typically use the formula in the init method. In the line below you are moving the whole line back-self.p.period
and getting in returndiff
which is a whole new line.diff = self.data(0) - self.data(-self.p.period)
In the statement below we use an individual line from the dataline, in this case
close
and we get once piece of data in return, typically used innext
. So in this case, you are subtracting: the value forself.datas[9].close[0]
at the current bar and lessself.datas[0].close[-self.p.period]
-self.p.period's ago. This will yield one number, a scalar, and can be used in the current bar.diff = self.datas[0].close[0] - self.datas[0].close[-self.p.period]
-
Wow, it's really difficult for me to understand...
I tried a different test. This is the indicator's code:
class hasGrown(bt.Indicator): lines = ('hasgrown',) # output line (array) def __init__(self): self.lines.hasgrown = self.data(0) > self.data(-1)
and this is the strategy:
class buyifgrewStrategy(bt.Strategy): def __init__(self): print("Starting strategy") self.hasgrown = hasGrown() def next(self): if self.hasgrown: if not self.position: calculateSize(self) self.buy(self.data,size=self.size) print("Buying") else: if self.position: self.close(self.data) print("Selling") log(self,f"Today: {self.data.close[0]}. Yesterday: {self.data.close[-1]}")
So, in the indicator I'm checking whether the stock has price has grown yesterday (and if so I buy). If it has decreased I sell.
The code apperently works, but I don't understand why.
What am I comparing here? (open prices? close prices? all of them?)
self.lines.hasgrown = self.data(0) > self.data(-1)
The print output:
... 2021-02-16, Today: 133.19. Yesterday: 135.37 2021-02-17, Today: 130.84. Yesterday: 133.19 2021-02-18, Today: 129.71. Yesterday: 130.84 Buying 2021-02-19, Today: 129.87. Yesterday: 129.71 Selling 2021-02-22, Today: 126.0. Yesterday: 129.87 2021-02-23, Today: 125.86. Yesterday: 126.0 2021-02-24, Today: 125.35. Yesterday: 125.86 2021-02-25, Today: 120.99. Yesterday: 125.35 Buying 2021-02-26, Today: 121.26. Yesterday: 120.99 2021-03-01, Today: 127.79. Yesterday: 121.26 Selling 2021-03-02, Today: 125.12. Yesterday: 127.79 2021-03-03, Today: 122.06. Yesterday: 125.12 2021-03-04, Today: 120.13. Yesterday: 122.06 Buying 2021-03-05, Today: 121.42. Yesterday: 120.13 Strategy is over, final cash: 122076.85999999993
-
@marsario said in Custom indicator, how datas work in init:
self.lines.hasgrown = self.data(0) > self.data(-1)
Backtrader has a number of shortcut conventions that make life easier, but also more confusing for the uninitiated. When the line is not indicated a default line will be chosen. In your case above, that would be
close
.Your ohlcv data lines are stored in a list called
datas
. It's really that simple. Soself.datas[0]
is the first ohlcv data line in the list, since in python [0] gets you the first in a list.self.datas[1]
give you the second ohlcv line in that list, and so on. If you omit the[0]
component as inself.data
then the first ohlcv data line in the list is selected for you.I personally find these very confusing and opt when I code to use the full and formal written version, eg the following in next:
def next(self): self.datas[0].close[0]
Which translates into the first ohlcv data line, closing value, at the current bar. For comparison,
def next(self): self.datas[2].volume[-4]
This would give me the third ohlcv dataline in my list, retuning the volume, 4 bars ago.
If I wanted to use the whole line in init to create new indicator lines, then I would still use square brackets to select my line, but use round brackes to identify bar position. eg:
def __init__(self): self.newindicatorline = self.datas[1].high(0) -self.datas[2].low(-15)
This would create a new line that would have a value at each bar that is the high of ohlcv line 1 - the low 15 periods ago of ohlcv line 2. If you want to use this new indicator in next:
def next(self): new_ind_current_bar = self.newindicatorline[0] new_ind_last_bar = self.newindicatorline[-1]
-
@run-out Amazing! Thanks a lot! All clear!