Hi Guys,
I am trying to implement a custom indicator based on Butterworth filter from scipy.signal. It is possible to use scipy.signal.lfliter on an entire series as well as on a stream values (see the example here: https://stackoverflow.com/questions/40483518), so this should work well inside of the Update method of a PythonIndicator class. However I am getting slightly different results from a custom indicator:
from scipy.signal import butter, lfilter, lfilter_zi
import numpy as np
from QuantConnect.Indicators import PythonIndicator
class ButterworthFilterBasic(PythonIndicator):
def __init__(self, period=5):
self.Period = period
self.b, self.a = butter(3, 1/self.Period, btype='lowpass')
self.zi = lfilter_zi(self.b, self.a)
self.Value = np.nan
self.barNum = 0
def Update(self, input):
y, self.zi = lfilter(self.b, self.a, [input.Close], zi=self.zi)
self.Value = y[0]
self.barNum += 1
return self.barNum > self.Period
when I use it in a backtest:
from butterworth import ButterworthFilterBasic
class ButterworthTest(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2019, 1, 1)
self.SetEndDate(2021, 1, 1)
self.spy = self.AddEquity('SPY', Resolution.Daily)
self.bf5 = ButterworthFilterBasic(5)
self.RegisterIndicator(self.spy.Symbol, self.bf5, Resolution.Daily)
def OnData(self, data):
self.Plot('Trade Plot', 'Price', data.Bars[self.spy.Symbol].Close)
if self.bf5.IsReady:
self.Plot('Trade Plot', 'BF5', self.bf5.Value)
the resulted plot is not smooth:
This is what I expected to see:
This second image was captured from QuantBook where it was produced by the following code:
# QuantBook Analysis Tool
# For more information see [https://www.quantconnect.com/docs/research/overview]
qb = QuantBook()
spy = qb.AddEquity("SPY")
history = qb.History(qb.Securities.Keys, 500, Resolution.Daily)
import numpy as np
import pandas as pd
from scipy.signal import lfilter, lfilter_zi, butter
b, a = butter(3, 1/5, btype='lowpass')
y1 = lfilter(b, a, history.close.values)
y2 = np.zeros(y1.shape[0])
z = lfilter_zi(b, a)
for i, x in np.ndenumerate(history.close.values):
y_, z = lfilter(b, a, [x], zi=z)
y2[i] = y_[0]
bf = pd.DataFrame(index=history.index)
bf['BF1'] = y1
bf['BF2'] = y2
pd.concat([history.close, bf], axis=1).iloc[150:220].plot(figsize=(36, 24), style=['b.-', 'r.-', 'ro-'])
In the research code there are 2 filtered series:
- y1 calculated from the entire close price series at once,
- y2 calculated from individual samples one by one in a for loop.
On the plot both series are matching perfectly: the red line with big dot markers (y2) covers the red line without markers (y1). This proves that both filtering methods produce exactly the same results.
Why then the custom indicator (ButterworthFilterBasic) gives incorrect results? With the 3rd order Butterworth filter the resulting line should be smooth. It seems like the lag from the custom indicator is the same as on the second plot, but the line is not as smooth. I hope this can be fixed and someone helps me spot the error.
Mack Spinoff
Heh, it took me a while to realize that the charts in the backtest results are plotted over the calendar days, so the lines are stretched over weekends and holidays which makes them looks choppy. So there is nothing wrong with the Update() method and the noise is purely visual.
Mack Spinoff
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!