Quantopian's community platform is shutting down. Please read this post for more information and download your code.
Back to Community
sorting based on morningstar fundamentals

I'm trying to sort based on morningstar fundamentals. I see this post:
https://www.quantopian.com/posts/how-to-filter-based-on-more-than-one-morningstar-fundamental-grade

So I'm trying to rewrite it for the pipeline api:

   growth_score = fundamentals.asset_classification.growth_score  
    value_score = fundamentals.asset_classification.value_score  
    growth_grade = fundamentals.asset_classification.growth_grade  
    financial_health_grade = fundamentals.asset_classification.financial_health_grade  
    profitability_grade = fundamentals.asset_classification.profitability_grade  
    growth_score_filter = (growth_score >90)  
    value_filter = (value_score >90)  
    growth_grade_filter = (growth_grade == 'A')  | (growth_grade == 'B')  
    financial_health_filter = (financial_health_grade == 'A') | (financial_health_grade == 'B')  
    profitability_filter = (profitability_grade == 'A')  | (profitability_grade == 'B')  
    pipe = Pipeline(  
        screen = (base_universe & growth_score_filter &  value_filter & growth_grade_filter & financial_health_filter &  profitability_filter),  
... 

This doesn't work. I get the error:
BadBinaryOperator: Can't compute PrecomputedFilter & UserBinaryExpression

Any recommendations on how I can use this data?

3 responses

Hi Arun,

For the filters use .eq() operator instead of ==.
E.g. growth_grade.eq('A') instead of growth_grade == 'A'.

Also you will likely have to add .latest in each fillter:
growth_grade = fundamentals.asset_classification.growth_grade.latest

That's right.

Try this style folks. It might seem odd at first. Doesn't take very long to become accustomed to it. Fast & efficient development.
I like this route ...

def initialize(context):  
    attach_pipeline(make_pipeline(), 'pipe')

def make_pipeline():  
    f  = Fundamentals  
    m  = QTradableStocksUS()   # mask  
    gs = f.growth_score          .latest.zscore(mask=m) ; m &= (gs.percentile_between(50, 90))  
    vs = f.value_score           .latest.zscore(mask=m) ; m &= (vs.percentile_between(50, 90))  
    gg = f.growth_grade          .latest                ; m &= (gg.eq('A') | gg.eq('B'))  
    hg = f.financial_health_grade.latest                ; m &= (hg.eq('A') | hg.eq('B'))  
    pg = f.profitability_grade   .latest                ; m &= (pg.eq('A') | pg.eq('B'))

    return Pipeline(  
        screen  = m,  
        columns = {  
            'gs': gs,  
            'vs': vs,  
            'gg': gg,  
            'hg': hg,  
            'pg': pg,  
        }  
    )

def before_trading_start(context, data):  
    context.out = pipeline_output('pipe')

    if 'log_data_done' not in context:    # show values once  
        log_data(context, data, context.out, 6)  # all fields (columns) if unspecified  

Adds to mask progressively, which I think ought to be more efficient (each fundamental operating on the next smaller set of stocks, or the first two in this case, the ones with zscore. I added zscore as a way to be able to use mask, rank(mask=m) is another, and actually I probably should have also done percentile_between(50, 90, mask=m), forgot).
Easy to modify. Two comment characters can remove an entire fundamental, due to the semi-colons, two lines in one, and then there's also its column. The &= is and-equal, shorthand, the same as m = m & (something), adding to the mask.
Vertical alignment for easy reading.

log_data() is a preview of what's you've got. min, mean & max for those with numeric values. And some examples (currently set to 6) with symbols showing high & low values.
That preview can catch things. For example I wonder why growth_grade is so often 'B'. You might know.
You might want to change to percentile_between(90, 99). See what that does to the number of rows. That's the number of stocks that make it through the filters (only 21 right now). If you set percentile_between(0, 100) you'll see the entire range in the preview.

Notice with the import of Fundamentals (upper case), there is no need to remember or look up the category of each fundamental. For example fundamentals.valuation.shares_outstanding becomes simply Fundamentals.shares_outstanding (valuation category dropped).

At a certain point when you're satisfied with the 'A's & 'B's you can comment out those columns, the mask will still be in place.
If you'd rather have the log_data() output gone and out of the way after awhile, I prefer to do this as a way of turning that off:

if 0 and 'log_data_done' not in context: # show values once

The '0 and' makes that always false.

2018-06-20 05:45 log_data:44 INFO $10,000,000    2018-06-20 to 2018-06-22  
2018-06-20 05:45 log_data:111 INFO Rows: 21  Columns: 5  
                        min               mean               max  
    gs       0.114249484021     0.742931755782     1.28431644656  
    vs     0.00806275550685     0.586417734418     1.34426626765  
2018-06-20 05:45 log_data:126 INFO _ _ _   gg   _ _ _  
    ... gg highs  
                     gg        gs hg pg        vs  
Equity(24794 [MMS])   B  0.460806  A  A  0.306865  
Equity(45656 [GLPI])  B  0.114249  A  B  0.387484  
Equity(693 [AZO])     B  0.620327  B  B  0.826385  
Equity(4521 [LOW])    B  0.967748  B  A  0.317739  
Equity(4589 [LUV])    B  1.284316  B  B  1.344266  
Equity(10869 [TSCO])  B  0.341208  B  A  0.364232  
    ... gg lows  
                     gg        gs hg pg        vs  
Equity(41928 [FBHS])  B  0.423992  A  B  0.635489  
Equity(42125 [VAC])   B  0.857381  B  B  0.073454  
Equity(43981 [NCLH])  B  1.162103  B  B  1.244516  
Equity(45007 [CDW])   B  0.481913  A  A  0.237116  
Equity(19909 [MKSI])  A  0.927223  B  B  0.633637  
Equity(49543 [FCPT])  A  1.062826  A  B  0.417714  
2018-06-20 05:45 log_data:126 INFO _ _ _   gs   _ _ _  
    ... gs highs  
                     gg        gs hg pg        vs  
Equity(4589 [LUV])    B  1.284316  B  B  1.344266  
Equity(337 [AMAT])    B  1.219054  B  B  0.999890  
Equity(43981 [NCLH])  B  1.162103  B  B  1.244516  
Equity(21429 [ON])    B  1.142671  B  B  0.752519  
Equity(14277 [OTEX])  B  1.084832  A  B  0.550737  
Equity(49543 [FCPT])  A  1.062826  A  B  0.417714  
    ... gs lows  
                     gg        gs hg pg        vs  
Equity(24794 [MMS])   B  0.460806  A  A  0.306865  
Equity(41928 [FBHS])  B  0.423992  A  B  0.635489  
Equity(13777 [AEIS])  B  0.383860  B  B  0.355959  
Equity(10869 [TSCO])  B  0.341208  B  A  0.364232  
Equity(41047 [HCA])   B  0.213464  B  A  1.301880  
Equity(45656 [GLPI])  B  0.114249  A  B  0.387484  
2018-06-20 05:45 log_data:126 INFO _ _ _   hg   _ _ _  
    ... hg highs  
                     gg        gs hg pg        vs  
Equity(337 [AMAT])    B  1.219054  B  B  0.999890  
Equity(693 [AZO])     B  0.620327  B  B  0.826385  
Equity(43981 [NCLH])  B  1.162103  B  B  1.244516  
Equity(42125 [VAC])   B  0.857381  B  B  0.073454  
Equity(4521 [LOW])    B  0.967748  B  A  0.317739  
Equity(41047 [HCA])   B  0.213464  B  A  1.301880  
    ... hg lows  
                     gg        gs hg pg        vs  
Equity(45656 [GLPI])  B  0.114249  A  B  0.387484  
Equity(32483 [EVR])   B  0.642545  A  B  0.618744  
Equity(39063 [KAR])   B  0.718611  A  B  0.417312  
Equity(41928 [FBHS])  B  0.423992  A  B  0.635489  
Equity(45007 [CDW])   B  0.481913  A  A  0.237116  
Equity(24794 [MMS])   B  0.460806  A  A  0.306865  
2018-06-20 05:45 log_data:126 INFO _ _ _   pg   _ _ _  
    ... pg highs  
                     gg        gs hg pg        vs  
Equity(49543 [FCPT])  A  1.062826  A  B  0.417714  
Equity(45656 [GLPI])  B  0.114249  A  B  0.387484  
Equity(693 [AZO])     B  0.620327  B  B  0.826385  
Equity(4589 [LUV])    B  1.284316  B  B  1.344266  
Equity(13777 [AEIS])  B  0.383860  B  B  0.355959  
Equity(14277 [OTEX])  B  1.084832  A  B  0.550737  
    ... pg lows  
                     gg        gs hg pg        vs  
Equity(40159 [VC])    B  0.512977  B  A  0.008063  
Equity(41047 [HCA])   B  0.213464  B  A  1.301880  
Equity(10869 [TSCO])  B  0.341208  B  A  0.364232  
Equity(4521 [LOW])    B  0.967748  B  A  0.317739  
Equity(45007 [CDW])   B  0.481913  A  A  0.237116  
Equity(24794 [MMS])   B  0.460806  A  A  0.306865  
2018-06-20 05:45 log_data:126 INFO _ _ _   vs   _ _ _  
    ... vs highs  
                     gg        gs hg pg        vs  
Equity(4589 [LUV])    B  1.284316  B  B  1.344266  
Equity(41047 [HCA])   B  0.213464  B  A  1.301880  
Equity(43981 [NCLH])  B  1.162103  B  B  1.244516  
Equity(337 [AMAT])    B  1.219054  B  B  0.999890  
Equity(693 [AZO])     B  0.620327  B  B  0.826385  
Equity(21429 [ON])    B  1.142671  B  B  0.752519  
    ... vs lows  
                     gg        gs hg pg        vs  
Equity(13777 [AEIS])  B  0.383860  B  B  0.355959  
Equity(4521 [LOW])    B  0.967748  B  A  0.317739  
Equity(24794 [MMS])   B  0.460806  A  A  0.306865  
Equity(45007 [CDW])   B  0.481913  A  A  0.237116  
Equity(42125 [VAC])   B  0.857381  B  B  0.073454  
Equity(40159 [VC])    B  0.512977  B  A  0.008063  

The actual growth & value score ranges without zscore ...

    gs =  f.growth_score          .latest ; m &= (gs.percentile_between(0, 100, mask=m))  
    vs =  f.value_score           .latest ; m &= (vs.percentile_between(0, 100, mask=m))

2018-06-20 05:45 log_data:111 INFO Rows: 111  Columns: 5  
                min              mean          max  
    gs     13.14057      68.765334955     98.12893  
    vs      6.05222     34.0799184685      79.5777  

With zscore ...

    gs =  f.growth_score          .latest.zscore(mask=m) ; m &= (gs.percentile_between(0, 100, mask=m))  
    vs =  f.value_score           .latest.zscore(mask=m) ; m &= (vs.percentile_between(0, 100, mask=m))

2018-06-20 05:45 log_data:111 INFO Rows: 111  Columns: 5  
                      min                mean               max  
    gs     -1.66829310266      0.706101613519      1.9595139197  
    vs     -1.72748830041     -0.486653071619     1.52761328777  

Thanks Dimitriy and BlueSeahawk. Let me try that out.