We have added a new feature yesterday: custom indicator for python algorithm.
It is quite simple! Basically we just need to create a class with an Update method:
class MyCustomIndicator:
def __init__(self, params):
self.params = params
def Update(self, input):
pass
where input can be any IBaseData object (Tick, TradeBar, QuoteBar, etc).
The Update method with one argument is important because if we want to register the indicator for automatic updates, the engine will look for that method in order to pass an IBaseData object.
Here is an example of a simple moving average indicator:
# Python implementation of SimpleMovingAverage.
# Represents the traditional simple moving average indicator (SMA).
class CustomSimpleMovingAverage:
def __init__(self, name, period):
self.Name = name
self.Time = datetime.min
self.Value = 0
self.IsReady = False
self.queue = deque(maxlen=period)
def __repr__(self):
return "{0} -> IsReady: {1}. Time: {2}. Value: {3}".format(self.Name, self.IsReady, self.Time, self.Value)
# Update method is mandatory
def Update(self, input):
self.queue.appendleft(input.Close)
count = len(self.queue)
self.Time = input.EndTime
self.Value = sum(self.queue) / count
self.IsReady = count == self.queue.maxlen
We advice users to implement the boolean IsReady attribute, since it is ofter used when dealing with built-in indicators.
Like the built-in indicators, we can register the indicator in Initialize method:
# In Initialize
self.custom = CustomSimpleMovingAverage('custom', 60)
self.RegisterIndicator("SPY", self.custom, Resolution.Minute)
C# users, please check out this tutorial: How Do I Create an Indicator?
21pinegrove
Hi, Alexandre,
How can I use existing indicator in custom ones by python?
To use e.g. BB, we need to specify symbol, which is only available from "input" parameter in Update function?
Thanks for help.
Michael Manus
the symbol does not have to be set!
just define your bolinger band and update it with the value you have. whatever it is.
ThatBlokeDave
Hello all,
I am new to the platform so please forgive me if I am missing something obvious. I think I have a similar question to @21pinegrove but am not sure how to implement to the solution mentioned by Michael.
I would like to use an indicator as an input to my custom indicator with something along the lines of this:
class testIndicatr: ''' Custom Indicator info: https://www.quantconnect.com/forum/discussion/3383/custom-indicator-in-python-algorithm ''' def __init__(self, name): self.Name = name self.Time = datetime.min self.Value = 0 self.IsReady = False self.queue = deque(maxlen=2) self.Last_Value = 0 self.Roc = QCAlgorithm.ROC(period=1, selector=Field.Close) def __repr__(self): return "{0} -> IsReady: {1}. Time: {2}. Value: {3}".format(self.Name, self.IsReady, self.Time, self.Value) # Update method is mandatory def Update(self, input): # append left makes sure most recent value = [0] self.queue.appendleft(input.Volume) count = len(self.queue) self.Time = input.EndTime self.IsReady = count == self.queue.maxlen
Of course, this doesn't work. I am not sure I am approaching it in the correct way.
The desired result is that the indicator can take a look at the ROC indicator value and use that to calculate the custom indicator.
Any pointers would be greatly appreciated!
Joel Eames
Hi Dave,
I'm also trying to make use of a built-in indicator (LWMA in my case) within my custom indicator, also quite lost. From your code I can only point out that putting the call to Roc where you have on line 13 won't do anything because there is no context about what the current data bar is (like there would be in the initial algorithm module 'main.py' that you start with). I had more luck putting my call to LWMA inside the 'Update' function. I believe the parameter passed to 'Update' is of type 'IBaseData', however it's not as simple as passing 'input.Close' to your Roc function... I also tried using 'Field.Close' here but it complained it was the wrong type. It turns out it actually needs a 'selector', so what you should type instead is something like: Func[IBaseData, Decimal](self.convert)
...and then write a separate function called 'convert(self, bar)' that takes the current IBaseData as input and returns a decimal. I figured this out from the 'IndicatorSuiteAlgorithm.py' example posted on this page:
Hope this helps you... though I'm still stuck. This seems to satisfy the compiler but my LWMA values are just returning zeros.
Omid Abdi
Alexandre,
I have cloned your code above and run it. I get the error below. I am running into the same error when creating my custom indicator. Do you know what's causing this?
I'd really appriciate any help with this.Â
Alethea Lin
Hi Omid,
Thanks for pointing out the error. This is due to a recent refactor of custom indicator in Python on LEAN. Add return self.IsReady in the Update() function will solve the problem. The update function should look like this:
def Update(self, input): self.queue.appendleft(input.Close) count = len(self.queue) self.Time = input.EndTime self.Value = sum(self.queue) / count self.IsReady = count == self.queue.maxlen return self.IsReady
Thanks for your support!
Omid Abdi
Thank you Alethea, it worked!
Bob Dylan
Hi,
When I try to plot my custom python indicator I get this error:
Â
Runtime Error: ArgumentException : QCAlgorithm.Plot(): The last argument should be a QuantConnect Indicator object, <class 'TSI.TrueStrengthIndex'> was provided.
Thank you in advance for your help!
Bob Dylan
I found a fix: Just need to add .Value to the argument given to Plot, like:
self.Plot("TSI", self.tsi.Value)
Â
Also for the previous question of how to use another indicator in your custom indicator, what I did is:/
In __init__ put:
Â
self.ema = ExponentialMovingAverage("Example EMA", 25)
Then in Update put:
self.ema.Update(IndicatorDataPoint(input.EndTime, input.Close))
Then you can use the value of that indicator to compute the value of your own indicator like this:
self.Value = 100 * self.ema.Current.Value
Â
Bob Dylan
Seems it is not compatible with IndicatorExtensions.
When I do:
IndicatorExtensions.EMA(self.tsi, 13)
I get the following error:
During the algorithm initialization, the following exception has occurred: InvalidCastException : cannot convert object to target type at Python.Runtime.PyObject.AsManagedObject (System.Type t) [0x00011] in <c56ab175820d412caf052e079c2ab9ef>:0 at QuantConnect.Indicators.IndicatorExtensions.EMA (Python.Runtime.PyObject left, System.Int32 period, System.Nullable`1[T] smoothingFactor, System.Boolean waitForFirstToReady) [0x00007] in <a16263fe48934e838e1ef015e10df92b>:0 at (wrapper managed-to-native) System.Reflection.MonoMethod.InternalInvoke(System.Reflection.MonoMethod,object,object[],System.Exception&) at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00032] in <b0e1ad7573a24fd5a9f2af9595e677e7>:0 at Initialize in main.py:line 35 InvalidCastException : cannot convert object to target type at Python.Runtime.PyObject.AsManagedObject (System.Type t) [0x00011] in <c56ab175820d412caf052e079c2ab9ef>:0 at QuantConnect.Indicators.IndicatorExtensions.EMA (Python.Runtime.PyObject left, System.Int32 period, System.Nullable`1[T] smoothingFactor, System.Boolean waitForFirstToReady) [0x00007] in <a16263fe48934e838e1ef015e10df92b>:0 at (wrapper managed-to-native) System.Reflection.MonoMethod.InternalInvoke(System.Reflection.MonoMethod,object,object[],System.Exception&) at System.Reflection.MonoMethod.Invoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00032] in <b0e1ad7573a24fd5a9f2af9595e677e7>:0
Â
Bob Dylan
I found the solution. Turns out there's a new way to make custom python indicators. It needs to look like this:
class CustomSimpleMovingAverage(PythonIndicator): def __init__(self, name, period): self.Name = name self.Value = 0 self.queue = deque(maxlen=period) # Update method is mandatory def Update(self, input): self.queue.appendleft(input.Value) count = len(self.queue) self.Value = sum(self.queue) / count return count == self.queue.maxlen
Notice the PythonIndicator after the classname and notice that IsReady is gone (you must remove it). Instead Update method needs to return True once the indicator is ready.
Once the indicator is made that way, it will work will IndicatorExtensions as well as all other method which take an indicator.
Derek Melchin
Hi Bob,
Thanks for providing the solution. For completeness, I've attached an example backtest below.
Best,
Derek Melchin
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.
James Lambert
How do we now use the IsReady function?
Mislav Sagovac
This works for me (example, doesnt have sense):
class SadfIndicator(): def __init__(self, name, period): self.Name = name self.Time = datetime.min self.Value = 0 self.IsReady = False self.queue = deque(maxlen=period) self.queueTime = deque(maxlen=period) self.CurrentReturn = 0 def Update(self, input): self.queue.appendleft(input.Close) self.queueTime.appendleft(input.EndTime) self.Time = input.EndTime self.Value = 1 + 1 count = len(self.queue) self.IsReady = count == self.queue.maxlen return self.IsReady
Â
Greg Kendall
Can you use Fundamental data from Morningstar to create a custom indicator??
Â
Mislav Sagovac
Yes, you can. First you have to add Fundamental Universe in init:
def Initialize(self): # date, equity, brokerage and bencmark self.SetStartDate(2017, 6, 1) # self.SetEndDate(2020, 10, 10) self.SetCash(10000) # universe; use for fundamental data self.SetUniverseSelection(FineFundamentalUniverseSelectionModel(self.CoarseSelectionFunction, self.FineSelectionFunction, None, None)) self.UniverseSettings.Resolution = Resolution.Daily def CoarseSelectionFunction(self, coarse): return [self.symbol] def FineSelectionFunction(self, fine): return [self.symbol]
Then in indicators somethin like these:
class SadfIndicator(PythonIndicator): def __init__(self, name, period, algorithm, security): self.Name = name self.period = period self.Time = datetime.min self.ValuePe = 5 self.queue = deque(maxlen=period) self.queueTime = deque(maxlen=period) self.queuePe = deque(maxlen=period) self.CurrentReturn = 0 self.algorithm = algorithm self.security = security self.symbol = security.Symbol def Update(self, input): # P/E ratio pe_ratio = self.security.Fundamentals.ValuationRatios.NormalizedPERatio self.algorithm.Plot('Normalized PE', 'Ratio', pe_ratio) self.queue.appendleft(input.Price) self.queueTime.appendleft(input.EndTime) self.queuePe.appendleft(pe_ratio) self.Time = input.EndTime if len(self.queue) >= self.period: pass count = len(self.queue) self.IsReady = count == self.queue.maxlen return self.IsReady
Â
Greg Kendall
If you wanted to add a custom indicator for each symbol in your fine universe selection. (say you are selecting 100 or so stock symbols returned from the fineselectionfunction.) Would you create a dictionary and put the custom indicator in the dictionary by symbol and register the indicator using this symbol entry in the dictionary? Your example is doesn't show registering the indicator. Can you register indicators in a fine selection (where you don't know the stock symbols that will be selected ahead of time)? What is the best way to do this? Say that I am trying to look at the last 10 pe ratios for each symbol, for example.
Greg Kendall
continued... something like:
self.custom[symbol] = CustomSimpleMovingAverage('custom', 10)
self.RegisterIndicator(symbol, self.custom[symbol], Resolution.Daily)
Greg Kendall
I found a good example for what I was looking for above:
https://www.quantconnect.com/forum/discussion/10015/alpha-model-with-custom-indicator/p1
Â
Sikker Rosendal
I am also trying to re-use my custom indicator on multiple tickers - and looking for an elegant way of doing this. This works fine for SPY, but I would like to add a new ticker, for example GLD.
From examples above, the following works fine:
Initialize
Custom Indicator
Rebalance
the customSMA is used to select tickers and do a rebalance. Hence ‘DoRebalance’ is called at month end
I just want to calculate SMA (using CustomSMA indicator) for both SPY and GLD at months end. How is it possible in self.DoRebalance to retrieve latest sma values for all tickers ?
I guess, the ‘RegisterIndicator’ is a little unclear to me at this stage.
Is it possible to do a list of symbols and register all
Alexandre Catarino
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!