Percent Price Oscillator conversion
-
Hello,
I am new to the backtrader community and need help with a specific indicator that I am trying to convert. Indicator is used for finding market tops and bottom based on the calculations.
I am struggling to replicate those calculations in backtrader. Below is the specific function that I am not sure how to convert. As you can see in the function it used L0[1], I am not sure how to get this value. I was thinking of putting this value in an array but how would I reference it. or I should create an array the first time it will be zero and for the next candle it will have an value at index 1 and keep track of the candle number and use always candle-1. just thinking out loud
lag(g, p) =>
L0 = (1 - g)p+gnz(L0[1])
L1 = -gL0+nz(L0[1])+gnz(L1[1])
L2 = -gL1+nz(L1[1])+gnz(L2[1])
L3 = -gL2+nz(L2[1])+gnz(L3[1])
f = (L0 + 2L1 + 2L2 + L3)/6
fLink to the Indicator
https://www.tradingview.com/script/ngr0qRmw-CM-Laguerre-PPO-PercentileRank-Mkt-Tops-Bottoms/ -
@amit this looks like recursive indicator, so I would search docs and arcticles for this approach:
https://backtrader.com/docu/
https://backtrader.com/blog/Look on how the recursive built-in indicators are developed.
https://github.com/mementum/backtrader/tree/master/backtrader/indicatorsAlso search the forum, IIRC Laguerre indicators were discussed here.
-
@ab_trader Thank you for having a look. I will keep digging and share if I find anything
-
-
-
@ab_trader Hello, thank you for the reference and apologies for late reply. I was trying to make the code changes and make it work.
As in the suggested post I managed to get the array values correctly populated. thank you for that.
Now I am stuck at PercentRank indicatot. its returning no value. and when I do next() on pctRankT it gives Divide by zero error. I looked in the lambda function, is it that it has to have all the lookback values before it can be used. I am using ccxtfeed to get the data from Binance.
pctRankT = bt.indicators.PercentRank(ppoT, period=self.params.LookBackTop)
pctRankB = bt.indicators.PercentRank(ppoB, period=self.params.LookBackBottom) * -1if(pctRankT >= self.params.ExtPercent or (pctRankT >= self.params.WarPercent and pctRankT < self.params.ExtPercent)):
top = 1Thank you very much for your help.
-
@ab_trader Just wanted to check, is there anywhere I can send you the code. I just need bit of guidance.
Regards
-
@amit said in Percent Price Oscillator conversion:
Just wanted to check, is there anywhere I can send you the code. I just need bit of guidance.
usually if somebody needs help, they post the scripts here.
-
@ab_trader hello, here is the full code.
import backtrader as bt
import mathclass CustomStrategy(bt.Strategy):
# Moving average parameters
params = (('OscPeriod',50),('AdxPeriod',14), ('AtrPeriod',24),
('ExtPercent',70),('WarPercent',90),
('PPOShort',0.4),('PPOLong',0.8),
('LookBackTop',200),('LookBackBottom',200),
('AdxRange',20))def log(self, txt, dt=None): dt = dt or self.datas[0].datetime.date(0) print(f'{dt.isoformat()} {txt}') # Comment this line when running optimization def __init__(self): self.L0S = [0,0] self.L1S=[0,0] self.L2S=[0,0] self.L3S=[0,0] self.L0L = [0,0] self.L1L=[0,0] self.L2L=[0,0] self.L3L=[0,0] self.dataclose = self.datas[0].close # Order variable will contain ongoing order details/status self.order = None # Adx values self.pDi = bt.indicators.AverageDirectionalMovementIndex(self.datas[0],period=self.params.AdxPeriod).DIplus self.mDi = bt.indicators.AverageDirectionalMovementIndex(self.datas[0],period=self.params.AdxPeriod).DIminus # ATR values self.atr = bt.indicators.AverageTrueRange(self.datas[0],period=self.params.AtrPeriod) #Calculate TP self.tpBuy = self.datas[0].close + self.atr *0.95 self.tpSell = self.datas[0].close - self.atr *0.95 #calculate stop loss #self.fractal = bt.studies.contrib.fractal.Fractal(self.datas, period=2, bardist=0.01).lines #self.fup = self.fractal.fractal_bullish[0] #self.fdown = self.fractal.fractal_bearish[0] #Previous Day Close self.close = LastClose(self.datas[1]) #hl2 self.hl2=(self.data.high+self.data.low)/2 self.text =0 def notify_order(self, order): if order.status in [order.Submitted, order.Accepted]: # An active Buy/Sell order has been submitted/accepted - Nothing to do return # Check if an order has been completed # Attention: broker could reject order if not enough cash if order.status in [order.Completed]: if order.isbuy(): self.log(f'BUY EXECUTED, {order.executed.price:.2f}') elif order.issell(): self.log(f'SELL EXECUTED, {order.executed.price:.2f}') self.bar_executed = len(self) elif order.status in [order.Canceled, order.Margin, order.Rejected]: self.log('Order Canceled/Margin/Rejected') # Reset orders self.order = None def lagS(self,g,p): self.L0S.insert(0, (1 - g) * p + g * self.L0S[1] ) self.L1S.insert(0, -g * self.L0S[0] + self.L0S[1] + g * self.L1S[1]) self.L2S.insert(0, -g * self.L1S[0] + self.L1S[1] + g * self.L2S[1]) self.L3S.insert(0, -g * self.L2S[0] + self.L2S[1] + g * self.L3S[1]) f = (self.L0S[0] + 2 * self.L1S[0] + 2 * self.L2S[0] + self.L3S[0]) / 6 return f def lagL(self,g,p): self.L0L.insert(0, (1 - g) * p + g * self.L0L[1] ) self.L1L.insert(0, -g * self.L0L[0] + self.L0L[1] + g * self.L1L[1]) self.L2L.insert(0, -g * self.L1L[0] + self.L1L[1] + g * self.L2L[1]) self.L3L.insert(0, -g * self.L2L[0] + self.L2L[1] + g * self.L3L[1]) f = (self.L0L[0] + 2 * self.L1L[0] + 2 * self.L2L[0] + self.L3L[0]) / 6 return f def next(self): lmas=0 lmal=0 top =0 bottom =0 #Top & Bottom calculations lmal = self.lagL(self.params.PPOLong,self.hl2[0]) lmas = self.lagS(self.params.PPOShort,self.hl2[0]) #Remove any data beyond 2 iterations del self.L0S[2:] del self.L1S[2:] del self.L2S[2:] del self.L3S[2:] del self.L0L[2:] del self.L1L[2:] del self.L2L[2:] del self.L3L[2:] pctileB = self.params.ExtPercent * -1 wrnpctileB = self.params.WarPercent * -1 ppoT = (lmas-lmal)/lmal*100 ppoB = (lmal - lmas)/lmal*100 pctRankT = bt.indicators.PercentRank(ppoT, period=self.params.LookBackTop) pctRankB = bt.indicators.PrettyGoodOscillator .PercentRank(ppoB, period=self.params.LookBackBottom) * -1 if(pctRankT >= self.params.ExtPercent or (pctRankT >= self.params.WarPercent and pctRankT < self.params.ExtPercent)): top = 1 if(pctRankB <= pctileB or (pctRankB <= wrnpctileB and pctRankB > pctileB)): bottom = 1 # Check for open orders if self.order: return # Check if we are in the market if not self.position: # We are not in the market, look for a signal to OPEN trades #Calculate Top and Bottom if the Color on HMA is changed if(top == 1 and self.pdi > self.params.AdxRange): self.log(f'BUY CREATE {self.dataclose[0]:2f}') # Keep track of the created order to avoid a 2nd order self.order = self.buy() elif (bottom == 1 and self.mdi > self.params.AdxRange): self.log(f'SELL CREATE {self.dataclose[0]:2f}') # Keep track of the created order to avoid a 2nd order self.order = self.sell() else: if ( (self.position.size>0) and (self.dataclose[0] >= self.tpBuy) ): self.log(f'CLOSE CREATE {self.dataclose[0]:2f}') self.order = self.close() elif ( (self.position.size<0) and (self.dataclose[0] <= self.tpSell) ): self.log(f'CLOSE CREATE {self.dataclose[0]:2f}') self.order = self.close()