def make_pipeline(context): # Use a universe of just the top 500 US stocks universe = Q500US() sector = Sector() # Determine if the market is trending up or down. If the market is trending down then just buy bonds. If it is trending up then buy stocks # This is not an optimal solution and will obviously not work in all future market crashes spy_ma_fast_slice = SMA( inputs=[USEquityPricing.close], window_length=context.TF_FAST_LOOKBACK)[context.SPY] spy_ma_slow_slice = SMA( inputs=[USEquityPricing.close], window_length=context.TF_SLOW_LOOKBACK)[context.SPY] spy_ma_fast = SMA(inputs=[spy_ma_fast_slice], window_length=1) spy_ma_slow = SMA(inputs=[spy_ma_slow_slice], window_length=1) # If the 100 moving average crosses the 10 then alter the trend up variable trend_up = spy_ma_fast > spy_ma_slow # Get simple factors to determine "quality" companies cash_return = ms.cash_return.latest.rank(mask=universe) fcf_yield = ms.fcf_yield.latest.rank(mask=universe) roic = ms.roic.latest.rank(mask=universe) rev_growth = ms.revenue_growth.latest.rank(mask=universe) value = (cash_return + fcf_yield).rank(mask=universe) # Find sentiment rank using https://www.quantopian.com/posts/sentiment-as-a-factor sentiment_score = SMA( inputs=[sentiment.sentiment_signal], window_length=30, ) sentiment_score_zscore = sentiment_score.zscore() sentiment_overall_rank = sentiment_score_zscore.rank( mask=universe, groupby=sector).demean() # Combine factors to create one single ranking system quality = roic + rev_growth + value + sentiment_overall_rank # Create a 'momentum' factor by looking back over the last 140 days (20 weeks). momentum = Returns(window_length=context.MOMENTUM_LOOKBACK_DAYS) # Filters for top quality and momentum to use in the selection criteria top_quality = quality.top(context.TOP_ROE_QTY, mask=universe) top_quality_momentum = momentum.top(context.TARGET_SECURITIES, mask=top_quality) # Only return values to be used in the selection criteria by using the screen parameter pipe = Pipeline(columns={ 'trend_up': trend_up, 'top_quality_momentum': top_quality_momentum, }, screen=top_quality_momentum) return pipe
def make_pipeline(): """ Create and return our pipeline. We break this piece of logic out into its own function to make it easier to test and modify in isolation. In particular, this function can be copy/pasted into research and run by itself. """ # The factors we create here are based on broker recommendations data and a moving # average of sentiment data diff = (broker_ratings.rating_cnt_strong_buys.latest + broker_ratings.rating_cnt_mod_buys.latest - (broker_ratings.rating_cnt_strong_sells.latest + broker_ratings.rating_cnt_mod_sells.latest)) # Here we temper the diff between recommended buys and sells with a ratio of what # percentage of brokers actually rated a given security rat = broker_ratings.rating_cnt_with.latest/ \ (broker_ratings.rating_cnt_with.latest+broker_ratings.rating_cnt_without.latest) alpha_signal = diff * rat sentiment_score = SimpleMovingAverage( inputs=[stocktwits.bull_minus_bear], window_length=3, ) universe = QTradableStocksUS() # Construct a Factor representing the rank of each asset by our value # quality metrics. We aggregate them together here using simple addition # after zscore-ing them combined_factor = (alpha_signal.zscore() + sentiment_score.zscore()) # Build Filters representing the top and bottom NUM_POSITIONS stocks by our combined ranking system. # We'll use these as our tradeable universe each day. longs = combined_factor.top(NUM_LONG_POSITIONS, mask=universe) shorts = combined_factor.bottom(NUM_SHORT_POSITIONS, mask=universe) # The final output of our pipeline should only include # the top/bottom 300 stocks by our criteria long_short_screen = (longs | shorts) # Create pipeline pipe = Pipeline(columns={ 'longs': longs, 'shorts': shorts, 'combined_factor': combined_factor }, screen=long_short_screen) return pipe
def make_pipeline(): """ A function that creates and returns our pipeline. We break this piece of logic out into its own function to make it easier to test and modify in isolation. In particular, this function can be copy/pasted into research and run by itself. Returns ------- pipe : Pipeline Represents computation we would like to perform on the assets that make it through the pipeline screen. """ # The factors we create here are based on fundamentals data and a moving # average of sentiment data value = Fundamentals.ebit.latest / Fundamentals.enterprise_value.latest quality = Fundamentals.roe.latest sentiment_score = SimpleMovingAverage( inputs=[stocktwits.bull_minus_bear], window_length=3, ) universe = QTradableStocksUS() sGrowRate = Fundamentals.sustainable_growth_rate.latest dividendYield = Fundamentals.forward_dividend_yield.latest # We winsorize our factor values in order to lessen the impact of outliers # For more information on winsorization, please see # https://en.wikipedia.org/wiki/Winsorizing value_winsorized = value.winsorize(min_percentile=0.05, max_percentile=0.95) quality_winsorized = quality.winsorize(min_percentile=0.05, max_percentile=0.95) sentiment_score_winsorized = sentiment_score.winsorize(min_percentile=0.05, max_percentile=0.95) positive_sentiment_pct = (twitter_sentiment.bull_scored_messages.latest / twitter_sentiment.total_scanned_messages.latest) mean_sentiment_5day = SimpleMovingAverage( inputs=[sentiment.sentiment_signal], window_length=5) total_revenue = Fundamentals.total_revenue.latest sGrowRate_winsorized = sGrowRate.winsorize(min_percentile=0.05, max_percentile=0.95) dividendYield_winsorized = dividendYield.winsorize(min_percentile=0.05, max_percentile=0.95) # Here we combine our winsorized factors, z-scoring them to equalize their influence combined_factor = (value_winsorized.zscore() + quality_winsorized.zscore() + sentiment_score_winsorized.zscore() + positive_sentiment_pct.zscore() + mean_sentiment_5day.zscore() + total_revenue.zscore() + sGrowRate_winsorized.zscore()) # Build Filters representing the top and bottom baskets of stocks by our # combined ranking system. We'll use these as our tradeable universe each # day. longs = combined_factor.top(TOTAL_POSITIONS // 2, mask=universe) shorts = combined_factor.bottom(TOTAL_POSITIONS // 2, mask=universe) # The final output of our pipeline should only include # the top/bottom 300 stocks by our criteria long_short_screen = (longs | shorts) # Create pipeline pipe = Pipeline(columns={ 'longs': longs, 'shorts': shorts, 'combined_factor': combined_factor }, screen=long_short_screen) return pipe