Back to Community
Alpha Factor based off StockTwits Trader Mood (Long/Short)

This is an algorithm that was inspired by a study documenting the non-linear evidence of social media on stock prices. In the study, Thársis T.P. Souza finds that social media has an effect on stock prices that cannot be explained away by other fundamental factors. My main takeaway from the study wasn't the researcher's findings, but rather the universe he examined (DJIA). In previous algorithms, I had used a blind factor ranking regardless of message volume. In this algorithm, I constrain my universe to purely securities with the highest levels of message volume and get more consistent and stable results through 2015 than my previous work.

Before cloning the algorithm, please read through these strategy notes:

  • This algorithm is basically a culmination of parts 3~4 of the Trader Mood Research Series: Hypothesis testing and crafting a signal to begin strategy creation.
  • This algorithm provides a simple framework for working with Trader Mood by only looking at the previous day's mood. In order to evaluate that signal independent of other factors, slippage and commissions have been set to zero. The algorithm itself is not geared for practical trading and will perform poorly with slippage & commissions on.
  • It uses the StockTwits Trader Mood dataset by PsychSignal between January of 2014 ~ December 2015. The full sample data feed is available from 10 Jul 2009 - two months delay from today.
  • The algorithm uses a weighting of message volume and bullish/bearish intensity to determine positions. The message volume is used as a filter (the top 500 securities with the highest volume of messages are looked at) and the previous day's bullish/bearish intensity is used as the ranking methodology.
  • Basic liquidity floors are used: ADV > 10,000,000 and only looking at the top 1,000 stocks by liquidity ranking

I'm excited to see what progressions we can make with this, please post back with your own variations if you want suggestions/feedback.

Finally, join us for our recorded webinar as we walk through this series with James Crane-Baker from PsychSignal.

This algorithm is for education - the algorithm is not intended to provide investment advice.

Clone Algorithm
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
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 sample algorithm will give you a brief overview on using the
StockTwits Trader Mood PsychSignal dataset.

This dataset measures the mood of traders posting messages on 
StockTwits

Key metrics:

bull_scored_messages - total count of bullish sentiment messages
                       scored by PsychSignal's algorithm
bear_scored_messages - total count of bearish sentiment messages
                       scored by PsychSignal's algorithm
bullish_intensity - score for each message's language for the stength
                    of the bullishness present in the messages on a 0-4
                    scale. 0 indicates no bullish sentiment measured, 4
                    indicates strongest bullish sentiment measured. 4 is rare
bearish_intensity - score for each message's language for the stength
                    of the bearish present in the messages on a 0-4 scale.
                    0 indicates no bearish sentiment measured, 4 indicates
                    strongest bearish sentiment measured. 4 is rare
total_scanned_messages - number of messages coming through PsychSignal's
                         feeds and attributable to a symbol regardless of
                         whether the PsychSignal sentiment engine can score
                         them for bullish or bearish intensity
"""
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.factors import CustomFactor, AverageDollarVolume
import pandas as pd
import numpy as np

# This is the Twitter & StockTwits (All Fields) with Retweets dataset
# Full information can be found at
# https://www.quantopian.com/data/psychsignal/stocktwits
# from quantopian.pipeline.data.psychsignal import stocktwits as psychsignal

# Using the free sample in your pipeline algo
# Free dataset availability is 24 Aug 2009 - 31 Dec 2015
from quantopian.pipeline.data.psychsignal import stocktwits_free as psychsignal

class PsychSignal(CustomFactor):
    """
    Baseline PsychSignal Factor
    """
    inputs = [psychsignal.bull_minus_bear]
    window_length = 1
    
    def compute(self, today, assets, out, bull_minus_bear):
        out[:] = bull_minus_bear
        
        
class PsychSignalMessages(CustomFactor):
    """
    Created to rank each security by message coverage
    """
    inputs = [psychsignal.bull_scored_messages, psychsignal.bear_scored_messages]
    window_length = 30
    
    def compute(self, today, assets, out, bull_msgs, bear_msgs):
        np.mean(bull_msgs + bear_msgs, axis=0, out=out)
    
# Compute final rank and assign long and short baskets.
def before_trading_start(context, data):
    results = pipeline_output('factors').dropna()
    lower, upper = results['psychsignal_sentiment'].quantile([.05, .95])
    context.shorts = results[results['psychsignal_sentiment'] <= lower]
    context.longs = results[results['psychsignal_sentiment'] >= upper]
    update_universe(context.longs.index | context.shorts.index)

    
# Put any initialization logic here. The context object will be passed to
# the other methods in your algorithm.
def initialize(context):
    # Create our pipeline
    pipe = Pipeline()
    pipe = attach_pipeline(pipe, name='factors')
    pipe.add(PsychSignal(), "psychsignal_sentiment")
    
    # Screen out penny stocks and low liquidity securities.
    dollar_volume = AverageDollarVolume(window_length = 20)
    
    # Only looks at securities within the top 1000 most liquid
    # and the 500 securities within those with the highest message
    # coverage
    liquidity_rank = dollar_volume.rank(ascending=False) < 1000
    message_rank = PsychSignalMessages().rank(ascending=False,
                                              mask=liquidity_rank)
    pipe.set_screen((dollar_volume > 10**7) & (500 > message_rank))
    
    # Set our shorts and longs and define our benchmark
    context.spy = sid(8554)
    context.shorts = None
    context.longs = None
    
    # Create our scheduled functions
    schedule_function(rebalance, date_rules.every_day())
    schedule_function(cancel_open_orders, date_rules.every_day(),
                      time_rules.market_close())
    set_commission(commission.PerShare(cost=0, min_trade_cost=0))
    set_slippage(slippage.FixedSlippage(spread=0))

    
# Will be called on every trade event for the securities you specify. 
def handle_data(context, data):
    record(lever=context.account.leverage,
           exposure=context.account.net_leverage,
           num_pos=len(context.portfolio.positions),
           oo=len(get_open_orders()))

    
def cancel_open_orders(context, data):
    # Cancel any open orders at the end of each day 
    for security in get_open_orders():
        for order in get_open_orders(security):
            cancel_order(order)
    
def rebalance(context, data):
    # Order our shorts
    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.index))
            
    # Order our longs
    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.index))
            
    # Order securities not in the portfolio
    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)
There was a runtime error.
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.

1 response

For those who missed the webinar, you can view it here: http://bit.ly/1SMv1lP

Also attached is the tearsheet for this algorithm and a few notes:

  • Turnover seems pretty stable (approx 30% per month 1.5% per day)
  • Returns are higher in 2015 than in 2014
  • Overall performance is stable
Loading notebook preview...
Notebook previews are currently unavailable.