Back to Community
CustomFilter Error: Help Please

Hi all, I´m fairly new here in Quantopian and am trying to practice by making a customfilter that will return either true or false for if there is a golden cross between the 50sma and 200sma. Could someone explain to me what is wrong? Thanks in advance.

class GoldenCross(CustomFilter):  
    def compute(self, today, asset_ids, out, data_inputs):  
        sma50 = SimpleMovingAverage(inputs = [USEquityPricing.close], window_length = 50, mask = base_universe)  
        sma200 = SimpleMovingAverage(inputs = [USEquityPricing.close], window_length = 200, mask = base_universe)  
        #if sma50 2-days ago < sma200 2-days ago AND sma50 today > sma200 today , goldencross = true  
        if (sma50[-2] < sma200[-2]) and (sma50[0] > sma200[0]):  
            out[-1] = True  
             out[-1] = False  

This is the algorithm I used. Tbh I don´t even know if what i wrote is correct or would even work or not. But the main reason for this post is because of the error message I get, after I initialise this class in my make_pipeline() function and run it.

This is the error returned:

TypeError Traceback (most recent call last)
in ()
----> 1 pipe = run_pipeline(make_pipeline(), '2010-01-01', '2011-01-01')

in compute(self, today, asset_ids, out, data_inputs)
6 #if sma50 2-days ago < sma200 2-days ago AND sma50 today > sma200 today = goldencross = true
----> 7 if (sma50[-2] < sma200[-2]) and (sma50[0] < sma200[0]):
8 out[-1] = True

TypeError: Term.getitem() expected a value of type Asset for argument 'key', but got int instead.

4 responses

You can't use builtin (or custom) factors directly in compute because they don't contain actual values yet. They only get filled when you call them later.
THEORETICALLY you could use it like this:

class GoldenCross(CustomFilter):  
    inputs = [SimpleMovingAverage(inputs = [USEquityPricing.close], window_length = 50, mask = base_universe)]

    def compute(self, today, asset_ids, out, sma50):  
        """Do some calculations with sma50"""

But SimpleMovingAverage happens to be a factor that isn't window safe and you will get an error. There is however a quick and dirty hack to get around it, simply define a class that inherits from SimpleMovingAverage and add window_safe = True to it:

class SimpleMovingAverage(SimpleMovingAverage):  
    window_safe = True  

Now you can use it as input for your GoldenCross:

class GoldenCross(CustomFilter):  
    data_inputs = [USEquityPricing.close]  
    sma50 = SimpleMovingAverage(inputs=data_inputs, window_length=50, mask=base_universe)  
    sma200 = SimpleMovingAverage(inputs=data_inputs, window_length=200, mask=base_universe)

    inputs = [sma50, sma200]  
    window_length = 3  
    mask = base_universe

    def compute(self, today, asset_ids, out, sma50, sma200):  
        screen = (sma50[-2] < sma200[-2]) & (sma50[-1] > sma200[-1])  
        out[:] = screen  

An approach without using SimpleMovingAverage at all which does the same:

class GoldenCross(CustomFilter):  
    inputs = [USEquityPricing.close]  
    window_length = 202  
    mask = base_universe

    def compute(self, today, asset_ids, out, c):  
        # Calculate the mean for the last 50 and 200 days up until yesterday  
        ma50_yesterday = np.nanmean(c[-51:-1], axis=0)  
        ma200_yesterday = np.nanmean(c[-201:-1], axis=0)

        # and the mean for the most recent 50 and 200 days  
        ma50_today = np.nanmean(c[-50:], axis=0)  
        ma200_today = np.nanmean(c[-200:], axis=0)

        # This will result in a boolean array where the value is True for all assets with a golden cross:  
        cross = (ma50_yesterday < ma200_yesterday) & (ma50_today > ma200_today)  
        out[:] = cross

A more detailed explanation about what's going on in custom filters and factors:
In the compute function you're dealing with numpy arrays which have the shape (window_length, number_of_assets). When you slice them like some_input[-1] you get an array with shape (,number_of_assets). Let's say you have a window_length of 5 and the base_universe would contain 3 assets, some_input would look like this (just some random integers):

[[ 4 10  8]
 [ 6  3  2]  
 [10  3  3]  
 [ 6  1  5]  
 [ 4  2  3]]  

some_input[-1] would be just the last row:

[4 2 3]

and some_input[-2] the second last:

[6 1 5]

Now using an if-statement on these 2 arrays like

if some_input[-1] > some_input[-2]:  

would result in a

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()  

But you're not interested if any or all of the elements satisfy the condition. You want to know WHERE the condition is true. This can simply be accomplished by stating the condition:

boolean_array = some_input[-1] > some_input[-2]  

The boolean_array would look like this:

[False  True False]

which is excactly the kind of output you want for a custom filter. If you want to combine several conditions you need to use the bitwise and '&' instead of 'and'. If you don't assign them as a variable first you need to put parentheses around them like so:

combined_boolean = (a > b) & (c < d)  

For your output you want the whole boolean array as output. But if you set

out[-1] = True  

You will get an array where only the value for the last asset is True. To get all truth values use

out[:] = boolean_array  

Hello Tentor,

thanks a lot for your reply! The information you gave was really useful, as a lot of the things you mentioned I was not aware of before this :)

Would you mind telling me how does the window_length effect the GoldenCross Custom Filter? I used different values and got very different results.
And why did you use this - window_length = 202 ?

Thanks again

window_length determines how many rows (=days) of data you request. I chose 202 to make sure we have at least 201 rows because we need 200 days excluding today in order to calculate ma200_yesterday. 201 would have been enough for the window_length but I like to add 1 or 2 as a margin of safety...

Increasing the window_length shouldn't make any difference, but decreasing it to under 201 will result in the ma200 not really being the 200-day-average but the mean for whatever days the window_length is now. The expression c[-200:] means something like "evereything that's in c when you look 200 values back". If c only has 100 rows, then it would be all of those 100. It would be a different story to leave out the colon c[-200] which means "exactly that row or value in c when you look 200 values back". Since there is nothing with a window_length of 100 you would get an error telling you the index is out of range.

I see. Thank you so much Tentor! Have a good day/night :)