Median calculation
-
Hello,
I am new to backtrader and until today had managed to find all the answers in the Community. I can't find help on how to calculate the median (P50) of a line for the last 'period' bars (so basically a movav but for the median not the mean). I am creating an indicator and have managed to implement the calculation (sorting and retrieving the value in the middle) within next() but for efficiency and plotting I would like to have a class for the indicator and the calculation in init.
Many thanks.
-
@carlrom
I think I would be inclined to use thestatistics
package for python and then use theirmedian
method.import statistics
You can create an indictor class using next in the indicator to create your line. It would look like this.
class Median(bt.Indicator): lines = ("median", ) params = ( ("median_period", 10), ) def __init__(self): self.addminperiod(self.p.median_period) def next(self): close_array = self.data.get(size=self.p.median_period) self.l.median[0] = statistics.median(close_array) # logging print(self.data.datetime.date(), close_array, self.l.median[0])
Use the
self.addminperiod
to ensure there are no empty list for statistics and that you have the minimum period of data in your median calculation.Plotting can be controlled according to indicator.plots
Your indicator can be called from your strategy init using:
def __init__(self): self.median = Median(median_period=self.p.median_period)
The logging looks like this for
median_period=5
:2020-11-06 array('d', [261.36, 265.3, 287.38, 294.68, 293.41]) 287.38 2020-11-09 array('d', [265.3, 287.38, 294.68, 293.41, 278.77]) 287.38 2020-11-10 array('d', [287.38, 294.68, 293.41, 278.77, 272.43]) 287.38 2020-11-11 array('d', [294.68, 293.41, 278.77, 272.43, 276.48]) 278.77 2020-11-12 array('d', [293.41, 278.77, 272.43, 276.48, 275.08]) 276.48
The entire code:
import datetime import backtrader as bt import statistics class Median(bt.Indicator): lines = ("median", ) params = ( ("median_period", 10), ) def __init__(self): self.addminperiod(self.p.median_period) def next(self): close_array = self.data.get(size=self.p.median_period) self.l.median[0] = statistics.median(close_array) # Logging print(self.data.datetime.date(), close_array, self.l.median[0]) class Strategy(bt.Strategy): params = ( ("median_period", 5), ) def log(self, txt, dt=None): """ Logging function fot this strategy""" dt = dt or self.data.datetime[0] if isinstance(dt, float): dt = bt.num2date(dt) print("%s, %s" % (dt.date(), txt)) def print_signal(self): self.log( f"o {self.datas[0].open[0]:7.2f} " f"h {self.datas[0].high[0]:7.2f} " f"l {self.datas[0].low[0]:7.2f} " f"c {self.datas[0].close[0]:7.2f} " f"v {self.datas[0].volume[0]:7.0f} " f"median {self.median[0]:5.0f}" ) def __init__(self): self.median = Median(median_period=self.p.median_period) def next(self): self.print_signal() if __name__ == "__main__": cerebro = bt.Cerebro() tickers = ['FB'] for ticker in tickers: data = bt.feeds.YahooFinanceData( dataname=ticker, timeframe=bt.TimeFrame.Days, fromdate=datetime.datetime(2020, 11, 1), todate=datetime.datetime(2020, 12, 5), reverse=False, ) cerebro.adddata(data, name=ticker) cerebro.addstrategy(Strategy) # Execute cerebro.run()
-
@run-out It worked! I had tried running the next() within the indicator class but I wasn't including the subscript [0] in median[0].
THANKS A LOT!