Sentdex provides stock sentiment scores by analyzing a number of major news sources like the WSJ, Forbes, and CNBC. Stock sentiment simply refers to the general public's attitude towards a given security and can often be used as a "directional signal to figure out whether to long or short stocks in your portfolio."
A number of researchers have shown that daily stock news sentiment, even when lagged, can serve as important predictors of future stock price movement. One study in particular successfully used news sentiment to create a low volatility, high return trading strategy.
Inspired, I attempted to do the same by creating a long/short algorithm using Sentdex's news sentiment dataset.
This strategy calculates a three-day moving average of Sentdex's
sentiment_signal factor (-3 to 6) to determine both long and short positions. On a daily basis, long positions are created in the 25 securities with the highest sentiment and short positions are created in the 25 securities with the lowest sentiment. A three-day look-back window is used because I wanted a time period that would both smooth out one-day sentiment spikes while providing a recent enough signal to (hopefully) predict stock-price movement in the following day.
The result is a low volatility, low beta strategy with returns doubling the S&P 500 from 2014 – 2015 and also avoiding the large downturns of 2014.
The full Sentdex dataset is available for $10/month and includes availability in live trading. The dataset covers almost all the securities in the S&P 500 along with a few others.
Take a look and let me know what you think. For questions on accessing this data, please email [email protected]
The purpose of this algorithm is to show a simple example of Sentdex's data in an algorithm. Further work is required to reduce the impact of slippage and commission and gear it for live trading.
|Returns||1 Month||3 Month||6 Month||12 Month|
|Alpha||1 Month||3 Month||6 Month||12 Month|
|Beta||1 Month||3 Month||6 Month||12 Month|
|Sharpe||1 Month||3 Month||6 Month||12 Month|
|Sortino||1 Month||3 Month||6 Month||12 Month|
|Volatility||1 Month||3 Month||6 Month||12 Month|
|Max Drawdown||1 Month||3 Month||6 Month||12 Month|
""" This algorithm uses Sentdex's dataset to create a long-short equity strategy. It uses the `sentiment_signal`, which is a measure of the sentiment from over 20 different major news sources like CNBC, WSJ, Yahoo and more. This value is then used to rank each security where the top 25 with the highest scores are longed at an equal weight and the bottom 25 with the lowest scores are shorted with an equal weight. """ from quantopian.algorithm import attach_pipeline, pipeline_output from quantopian.pipeline import Pipeline from quantopian.pipeline.data.builtin import USEquityPricing from quantopian.pipeline.factors import CustomFactor # The sample version available from 15 Oct 2012 - 11 Jan 2016 from quantopian.pipeline.data.sentdex import sentiment_free as sentdex # The premium version found at https://www.quantopian.com/data/sentdex/sentiment # from quantopian.pipeline.data.sentdex import sentiment as sentdex import pandas as pd import numpy as np # Calculates the average impact of the sentiment over the window length class AvgSentiment(CustomFactor): def compute(self, today, assets, out, impact): np.mean(impact, axis=0, out=out) class AvgDailyDollarVolumeTraded(CustomFactor): inputs = [USEquityPricing.close, USEquityPricing.volume] window_length = 20 def compute(self, today, assets, out, close_price, volume): out[:] = np.mean(close_price * volume, axis=0) # Put any initialization logic here. The context object will be passed to # the other methods in your algorithm. def initialize(context): window_length = 3 pipe = Pipeline() pipe = attach_pipeline(pipe, name='sentiment_metrics') dollar_volume = AvgDailyDollarVolumeTraded() # Add our AvgSentiment factor to the pipeline using a 3 day moving average pipe.add(AvgSentiment(inputs=[sentdex.sentiment_signal], window_length=window_length), "avg_sentiment") # Screen out low liquidity securities. pipe.set_screen((dollar_volume > 10**7)) context.shorts = None context.longs = None # context.spy = sid(8554) # Set commissions and slippage to zero to evaluate alpha generation # of the strategy schedule_function(rebalance, date_rules.every_day(), time_rules.market_open(hours=1)) set_commission(commission.PerShare(cost=0, min_trade_cost=0)) set_slippage(slippage.FixedSlippage(spread=0)) def before_trading_start(context, data): results = pipeline_output('sentiment_metrics').dropna() # Separate securities into longs and shorts longs = results[results['avg_sentiment'] > 0] shorts = results[results['avg_sentiment'] < 0] # Order them in their individual segments long_ranks = longs["avg_sentiment"].rank().order() short_ranks = shorts['avg_sentiment'].rank().order() num_stocks = min([25, len(long_ranks.index), len(short_ranks.index)]) # Find the top 25 stocks to long and bottom 25 to short context.shorts = short_ranks.tail(num_stocks) context.longs = long_ranks.head(num_stocks) # The pipe character "|" is the pandas union operator update_universe(context.longs.index | context.shorts.index) # Will be called on every trade event for the securities you specify. def handle_data(context, data): num_positions = [pos for pos in context.portfolio.positions if context.portfolio.positions[pos].amount != 0] record(lever=context.account.leverage, exposure=context.account.net_leverage, num_pos=len(context.portfolio.positions), oo=len(get_open_orders())) def rebalance(context, data): for security in context.shorts.index: if get_open_orders(security): continue if security in data: order_target_percent(security, -1.0 / len(context.shorts)) for security in context.longs.index: if get_open_orders(security): continue if security in data: order_target_percent(security, 1.0 / len(context.longs)) for security in context.portfolio.positions: if get_open_orders(security): continue if security in data: if security not in (context.longs.index | context.shorts.index): order_target_percent(security, 0)