Back to Community
Attach_pipeline returning attribute error

For the sake of being brief, I keep receiving an error trying to run the attach_pipeline command. It returns:

AttributeError: 'tuple' object has no attribute 'domain'

which I'm not sure what is causing it? Has anyone had a similar issue?

from quantopian.algorithm import attach_pipeline, pipeline_output  
def initialize(context):  
    # Rebalance every month, 1 hour after market open.  
    schedule_function(  
        rebalance,  
        date_rules.month_end(),  
        time_rules.market_open(hours=1),  
    )  
    # Create our dynamic stock selector.  
    results = make_pipeline()  
    attach_pipeline(results,'pipeline')  
8 responses

Hi Cam,

Without seeing the definition of make_pipeline, I can't pinpoint the specific issue. However, the error message suggests that the output type of make_pipeline is a tuple instead of a Pipeline object. This is most likely due to a syntax error involving an extra , character somewhere in make_pipeline.

Let me know if this helps.

Disclaimer

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 Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian 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.

Thanks for getting back, Jamie. The pipeline runs fine in research so I don't think it would be a syntax issue, here is the full pipeline.

class ZacksEarningsSurp(CustomFactor):  
    inputs=[EarningsSurprises.eps_pct_diff_surp]  
    outputs = ['fq0_eps_surp', 'fq1_eps_surp', 'fq2_eps_surp', 'fq3_eps_surp','fq4_eps_surp','fq5_eps_surp','fq6_eps_surp','fq7_eps_surp']  
    window_length = 720  
    def compute(self, today, assets, out, eps_surp):  
        out.fq0_eps_surp[:] = eps_surp[0]  
        out.fq1_eps_surp[:] = eps_surp[-90]  
        out.fq2_eps_surp[:] = eps_surp[-180]  
        out.fq3_eps_surp[:] = eps_surp[-270]  
        out.fq4_eps_surp[:] = eps_surp[-360]  
        out.fq5_eps_surp[:] = eps_surp[-450]  
        out.fq6_eps_surp[:] = eps_surp[-540]  
        out.fq7_eps_surp[:] = eps_surp[-630]  
class PriceLength(CustomFactor):  
    inputs = [USEquityPricing.close]  
    window_length = 300  
    def compute(self,today,asset_ids,out,close):  
        out[:]  = np.isnan(close[0])  


def make_pipeline():

    base_universe = Q1500US()

    price_close = USEquityPricing.close.latest  
    price_exists = PriceLength()  
    sma10 = SimpleMovingAverage(  
        inputs=[USEquityPricing.close],  
        window_length=10)  
    sma50 = SimpleMovingAverage(  
        inputs=[USEquityPricing.close],  
        window_length=50)  
    sma200 = SimpleMovingAverage(  
        inputs=[USEquityPricing.close],  
        window_length=200)  
    rsi = RSI(inputs=[USEquityPricing.close],  
        window_length=14)  
    fq0_eps_surp, fq1_eps_surp, fq2_eps_surp, fq3_eps_surp, fq4_eps_surp, fq5_eps_surp, fq6_eps_surp, fq7_eps_surp = ZacksEarningsSurp()

    long1 = (fq0_eps_surp > 0.0 and fq1_eps_surp > 0.0)  
    short1 = (fq0_eps_surp < 0.0 and fq1_eps_surp < 0.0)  
    sma10Spread = (price_close - sma10) > 0.0  
    sma50Spread = (sma10 - sma50) > 0.0  
    sma200Spread = (sma50 - sma200) > 0.0  
    rsiSpread = rsi < 70.0  
    priceExist = price_exists < 1.0  
    sma10Spread2 = (price_close - sma10) < 0.0  
    sma50Spread2 = (sma10 - sma50) < 0.0  
    sma200Spread2 = (sma50 - sma200) < 0.0  
    rsiSpread2 = rsi > 20.0  
    filterLongs = sma10Spread & sma50Spread & sma200Spread & rsiSpread & base_universe & long1 & priceExist  
    filterShorts = sma10Spread2 & sma50Spread2 & sma200Spread2 & rsiSpread2 & base_universe & short1 & priceExist  
    is_tradeable = (filterLongs | filterShorts)  
    return Pipeline(  
        columns={  
            'longs': filterLongs,  
            'shorts': filterShorts  
        },  
        screen = (is_tradeable),  
    )

def initialize(context):  
    # Rebalance every month, 1 hour after market open.  
    schedule_function(  
        rebalance,  
        date_rules.month_end(),  
        time_rules.market_open(hours=1)  
    )  
    # Create dynamic stock selector.  
    results = make_pipeline()  
    attach_pipeline(results,'pipeline')  

The following lines are incorrect

    long1 = (fq0_eps_surp > 0.0 and fq1_eps_surp > 0.0)  
    short1 = (fq0_eps_surp < 0.0 and fq1_eps_surp < 0.0)  

the and operator isn't defined for filters. Use & instead.

    long1 = (fq0_eps_surp > 0.0) & (fq1_eps_surp > 0.0)  
    short1 = (fq0_eps_surp < 0.0) & (fq1_eps_surp < 0.0)  

The error reporting on this is a bit poor so it may have appeared to work in a notebook but really wasn't.

Try that. Not sure it will fix the original issue but it certainly wasn't helping.

Disclaimer

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 Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian 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.

Thank you for the catch, Dan!

Also recommend a progressive mask something like this , starting with m = Q1500US(). But add on each step the screening of nans. For example, after the rsi = line, add a line: m &= rsi.notnull() which adds to the mask. And then always utilizing the latest mask with each step whenever calling a factor. Usually ending with screen = m. Not in this case once I decided to try offering an actual example. I've never dealt with anything quite like this before. It might need more parens.

A benefit: You can add or comment lines out more easily. But it starts with the main thing, avoiding nans in math operations.

def make_pipeline():  
    m = Q1500US()    # original mask

    price_close = USEquityPricing.close.latest  
    m &= price_close.notnull()  # add to progressive mask

    # Utilize the progressive mask ...  
    sma10  = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=10 , mask=m)  
    sma50  = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=50 , mask=m)  
    sma200 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=200, mask=m)  
    rsi    = RSI(inputs=[USEquityPricing.close], window_length=14, mask=m)  # Using mask  
    m &= rsi.notnull()  # add to progressive mask

    fq0_eps, fq1_eps, fq2_eps, fq3_eps, fq4_eps, fq5_eps, fq6_eps, fq7_eps = ZacksEarningsSurp(mask=m)  # Using mask  
    m &= fq0_eps.notnull()  
    m &= fq1_eps.notnull()

    # Long mask  
    longs  = m & (fq0_eps > 0.0)  
    longs &= m & (fq1_eps > 0.0)  
    longs &= m & (price_close - sma10) > 0.0  
    longs &= m & (sma10 - sma50)  > 0.0  
    longs &= m & (sma50 - sma200) > 0.0  
    longs &= m & (rsi < 70.0)

    shrts  = m & (fq0_eps < 0.0)  
    shrts &= m & (fq1_eps < 0.0)  
    shrts &= m & (price_close - sma10) < 0.0  
    shrts &= m & (sma10 - sma50)  < 0.0  
    shrts &= m & (sma50 - sma200) < 0.0  
    shrts &= m & (rsi > 20.0)

    return Pipeline(  
        columns = {  
            'longs'  : longs,  
            'shrts'  : shorts,  
            'price'  : price_close,  
            'rsi'    : rsi,  
            'sma10'  : sma10,  
            'sma50'  : sma50,  
            'sma200' : sma200,  
            'fq0_eps': fq0_eps,  
            'fq1_eps': fq1_eps,  
        },  
        screen = (longs | shrts),  
    )  
    # Many columns included just for preview. Once set, comment out any not used, for speed.  

From there I'd use this to vet pipeline values, to see what's there, it is easy to add.
Since it also shows the number of rows (stocks) plus min/max values, makes for quickly finding the changes if certain lines are altered, and getting a feel for what you're working with. Pipe wrangling. :p

As far as the original error, try removing the comma on this line:
time_rules.market_open(hours=1)

A few suggestions:

It is strongly advised that you use the QTradableStocksUS instead of the Q1500US. The QTradableStocksUS is our most up-to-date built-in universe and it's required for the contest.

It is also strongly advised that you use FactSet Estimates to compute earnings surprise over the Zack's dataset. The link to FactSet Estimates has an example earnings surprise computation. The FactSet version is much faster and is free to use in the contest. Additionally, we are working on phasing out subscription-based datasets from the platform, including the Zack's feed.

I also advise (less of a strong opinion here) against defining a mask iteratively, but I think there's an alternative that still gets the benefit of being able to comment out one filter at a time that @Blue mentioned (which I do think is important). Here's my suggestion:

    is_tradable = (  
        QTradableStocksUS()  
        & price_close.notnull()  
        & rsi.notnull()  
        & fq0_eps.notnull()  
        & fq1_eps.notnull()  
    )

    longs  = (  
        is_tradable  
        & (fq0_eps > 0.0)  
        & (fq1_eps > 0.0)  
        & (price_close - sma10) > 0.0  
        & (sma10 - sma50)  > 0.0  
        & (sma50 - sma200) > 0.0  
        & (rsi < 70.0)  
    )

    shrts  = (  
        is_tradable  
        & (fq0_eps < 0.0)  
        & (fq1_eps < 0.0)  
        & (price_close - sma10) < 0.0  
        & (sma10 - sma50)  < 0.0  
        & (sma50 - sma200) < 0.0  
        & (rsi > 20.0)  
    )  

Thank you for the help guys!

That's 23% slower but maybe in part because it is returning more stocks, 1331 vs my 1274. Otherwise I would think because rsi and sma (3 times) are processing all ~8000 stocks rather than using a mask in the process like mine.

Anyway, surely worth discussing and super important at the core of any strategy for pipeline be doing what we intend and 100% in our control, mystery-free, in case someone would like to make a new thread and I'll try to notice, I'm kind of on sabbatical.