Quantopian: RSI Strategy Backtest

Relative Strength Index (RSI) Strategy Backtest

Contact: andrewshamlet@gmail.com // @andrewshamlet

Download the IPython Notebook that accompanies this Tutorial from Github. 

View the Quantopian Backtest here. 

Summary

  • The Relative Strength Index (RSI) is a momentum indicator that compares the magnitude of recent gains and losses over a specified time period. RSI values range from 0 to 100.
  • For this strategy, we buy $FB when the RSI is less than 30, and we will sell $FB when the RSI is greater than 70. The RSI will be calculated at a minutely frequency, as opposed to a daily frequency.
  • During 01/01/16 – 12/31/16,
    • The RSI Strategy produces 32.2% return, resulting in $3,220 pre-tax return.
    • FB Buy & Hold produces 10.0% return, resulting in $1,000 pre-tax return.
    • SPY Buy & Hold produces 12.0% return, resulting in $1,200 pre-tax return.
    • Compared to the SPY Buy & Hold, the RSI Strategy produces $2,220 Alpha whereas FB Buy & Hold produces ($200) Alpha, both on $10,000, principal.
  • During 05/19/12 – 12/31/16,
    • The RSI Strategy produces 147.4% return, resulting in $14,740 pre-tax return.
    • FB Buy & Hold produces 238.5% return, resulting in $23,850 pre-tax return.
    • SPY Buy & Hold produces 89.6% return, resulting in $8,960 pre-tax return.
    • Compared to SPY Buy & Hold, the RSI Strategy produces $5,780 Alpha whereas FB Buy & Hold produces $14,890 Alpha, both on $10,000 principal.
  • Thus, on the broader time horizon, FB Buy & Hold outperforms the RSI Strategy.
  • The question still stands: what about 2016 makes the RSI Strategy superior in performance to FB Buy & Hold?

 

Introduction 

In this post, we use Quantopian to build and backtest a Relative Strength Index (RSI) trading strategy.

 

Quantopian

About Quantopian:

Quantopian provides capital, education, data, a research environment, and a development platform to algorithm authors (quants). Quantopian provides everything a quant needs to create a strategy and profit from it.

Quantopian’s members include finance professionals, scientists, developers, and students from more than 180 countries from around the world. The members collaborate in our forums and in person at regional meetups, workshops, and QuantCon, Quantopian’s flagship annual event.”

In other words, Quantopian is a website where one can build, test, and deploy trading strategies, using Python.

 

Relative Strength Index

To review, the Relative Strength Index (RSI) is a momentum indicator that compares the magnitude of recent gains and losses over a specified time period to measure speed and change of price movements of a security. It is primarily used to identify overbought or oversold conditions in the trading of an asset.

RSI values range from 0 to 100.

The Relative Strength Index (RSI) is calculated as follows:

RSI = 100 - 100 / (1 + RS)

RS = Average gain of last 14 trading days / Average loss of last 14 trading days

 

Strategy

For this strategy, we buy $FB when the RSI is less than 30, and we will sell $FB when the RSI is greater than 70. The RSI will be calculated at a minutely frequency, as opposed to a daily frequency.

Trading Strategy

Buy - RSI < 30

Sell - RSI > 70

 

Code

Here is the Python code for the RSI Strategy.

import talib
import numpy as np
import pandas as pd

def initialize(context):
    context.stocks = symbols('FB')
    context.pct_per_stock = 1.0 / len(context.stocks)
    context.LOW_RSI = 30
    context.HIGH_RSI = 70
    
    set_benchmark(sid(42950))  
    
def handle_data(context, data):
    prices = data.history(context.stocks, 'price', 40, '1d')

    rsis = {}
    
    for stock in context.stocks:
        rsi = talib.RSI(prices[stock], timeperiod=14)[-1]
        rsis[stock] = rsi
        
        current_position = context.portfolio.positions[stock].amount
        
        if rsi > context.HIGH_RSI and current_position > 0 and data.can_trade(stock):
            order_target(stock, 0)

        elif rsi < context.LOW_RSI and current_position == 0 and data.can_trade(stock):
            order_target_percent(stock, context.pct_per_stock)

    record(FB_rsi=rsis[symbol('FB')])

At its foundation, Quantopian code is made up of three chunks: import modules, initialize, and handle_data.

1.) First we import the Talib, Numpy, and Pandas modules. As we’ll see, Talib streamlines the calculation of Technical Indicators.

import talib 
import numpy as np 
import pandas as pd

2.)  The initialize function:

def initialize(context): 
     context.stocks = symbols('FB') 
     context.pct_per_stock = 1.0 / len(context.stocks) 
     context.LOW_RSI = 30 
     context.HIGH_RSI = 70 

     set_benchmark(sid(42950)) 

2.a.) Define the security to trade, $FB.

context.stocks = symbols('FB') 

2.b.) Define the weight of each security. Since the RSI Strategy trades one security, the weight is 1.0. If there were two securities, the weight would be 0.5.

context.pct_per_stock = 1.0 / len(context.stocks) 

2.c.) Define the LOW_RSI value as 30

context.LOW_RSI = 30 

2.d.) Define the HIGH_RSI value as 70

context.HIGH_RSI = 70 

2.e.) Define the benchmark to which we will compare our strategy. In the example, the benchmark is set to $FB, essentially a buy and hold strategy. Remove ‘set_benchmark()’ to set the benchmark to the standard, ‘SPY’, or market rate.

set_benchmark(sid(42950)) 

3.)  The handle_data function:

def handle_data(context, data): 
     prices = data.history(context.stocks, 'price', 40, '1d') 

     rsis = {} 

     for stock in context.stocks: 
          rsi = talib.RSI(prices[stock], timeperiod=14)[-1] 
          rsis[stock] = rsi 

          current_position = context.portfolio.positions[stock].amount 

          if rsi > context.HIGH_RSI and current_position > 0 and data.can_trade(stock): 
               order_target(stock, 0) 

          elif rsi < context.LOW_RSI and current_position == 0 and data.can_trade(stock): 
               order_target_percent(stock, context.pct_per_stock) 

     record(FB_rsi=rsis[symbol('FB')])

3.a.) Query the ‘FB’ historical price data for the past 40 trading days.

prices = data.history(context.stocks, 'price', 40, '1d') 

3.b.) Create dictionary of RSI values.

rsis = {} 

3.c.) Create for loop for RSI calculation and order logic.

for stock in context.stocks: 

3.d.) Use Talib to calculate Relative Strength Index.

rsi = talib.RSI(prices[stock], timeperiod=14)[-1]

3.e.) Save Talib output to dictionary.

rsis[stock] = rsi 

3.f.) Save current portfolio positions in order to not execute too many/few orders.

current_position = context.portfolio.positions[stock].amount 

3.g.) Order logic: if RSI is greater than 70 and positions are greater than 0, then sell all positions.

if rsi > context.HIGH_RSI and current_position > 0 and data.can_trade(stock): 
               order_target(stock, 0) 

3.h.) Order logic: if RSI is less than 30 and positions are equal to 0, then buy positions equal to weight defined in initialize function.

elif rsi < context.LOW_RSI and current_position == 0 and data.can_trade(stock): 
               order_target_percent(stock, context.pct_per_stock) 

3.i.) Chart RSI data for $FB.

record(FB_rsi=rsis[symbol('FB')])

1 Year Performance

For the time period, 01/01/16 – 12/31/16

% Return Principal Pre-Tax Return Alpha
RSI Strategy 32.2% $10,000 $3,220 $2,220
FB Buy & Hold 10.0% $10,000 $1,000 ($200)
SPY Buy & Hold 12.0% $10,000 $1,200 N/A

 

We backtest the RSI Strategy with a $10,000 principal for the time period, 01/01/16 – 12/31/16. 

During 01/01/16 – 12/31/16,

  • The RSI Strategy produces 32.2% return, resulting in $3,220 pre-tax return.
  • FB Buy & Hold produces 10.0% return, resulting in $1,000 pre-tax return.
  • SPY Buy & Hold produces 12.0% return, resulting in $1,200 pre-tax return.
  • Compared to the SPY Buy & Hold, the RSI Strategy produces $2,220 Alpha whereas FB Buy & Hold produces ($200) Alpha, both on $10,000, principal.

 

 

Beyond 1 Year Performance

Yes, $2,220 Alpha on $10,000 principal is impressive.

Before we go and bet the farm, let’s see how the RSI Strategy performs over a longer time period.

Since the ‘FB’ IPO occurred on 05/18/12, we will backtest for the period 05/19/12 – 12/31/16.

For the time period, 05/19/12 – 12/31/16

% Return Principal Pre-Tax Return Alpha
RSI Strategy 147.4% $10,000 $14,740 $5,780
FB Buy & Hold 238.5% $10,000 $23,850 $14,890
SPY Buy & Hold 89.6% 10,000 $8,960 N/A

During 05/19/12 – 12/31/16,

  • The RSI Strategy produces 147.4% return, resulting in $14,740 pre-tax return.
  • FB Buy & Hold produces 238.5% return, resulting in $23,850 pre-tax return.
  • SPY Buy & Hold produces 89.6% return, resulting in $8,960 pre-tax return.
  • Compared to SPY Buy & Hold, the RSI Strategy produces $5,780 Alpha whereas FB Buy & Hold produces $14,890 Alpha, both on $10,000 principal.

Thus, on the broader time horizon, FB Buy & Hold outperforms the RSI Strategy.

 

Concluding Thought

Over the long term, money would go further with the FB Buy & Hold strategy.

The question still stands: what about 2016 makes the RSI Strategy superior in performance to FB Buy & Hold?

Until next time!

If they Google You, Do you Win?

In a way, this election is a referendum on “do actions speak louder than words”, is what people do in the privacy of their internet browsing more reflective of their future behavior than what they tell pollsters? And while I have focused on twitter as a barometer of public opinion, there are other data sources that could signal the private thoughts and future actions of voters. The linked NYT article, “If they Google you, Do you Win?”, mentions using the Google queries “Trump Clinton” vs. “Clinton Trump” as signals of voter interest, with the respective queries reflecting bias towards the candidate listed first, i.e. “Clinton Trump” would reflect bias towards Clinton. Using this methodology, I researched Google trends for Battleground states to see where public opinion may be. The data are displayed below.

screen-shot-2016-11-03-at-5-49-10-pm

For the month of October 2016, “Trump Clinton” leads “Clinton Trump” in every state with the exception of Nevada.

You might say Trump is a polarizing celebrity, and for that reason he may be top of mind even if the individual plans to vote for Clinton. Okay, well then let’s penalize Trump 10%. Even in that case, ‘Factored “Trump Clinton”‘ indicates that, with the exception of Nevada, the three states that are in play are Virginia, Iowa, and Florida.

So while it is unclear in which direction the election will result, I believe we may be surprised at how close the results turn out to be, and that one thing we may remember is the discrepancy between what was reported in the polls leading up to the election and what actually happened online. We only have 4 days left to see which source provides a clearer signal of truth, and until then….Good luck to both candidates!

Hillary: Age Perception Problem?

“I come from the ‘60s, a long time ago,” Hillary Clinton said at Saturday’s Democratic Presidential debate, in response to a question about student activism. The gaffe has mostly fallen on deaf ears.

Hillary Clinton is 68 years old. She was born October 26, 1947. Here is a visualization showing the age distribution of US Presidents upon assuming the Oval Office.

Screen Shot 2015-11-19 at 12.53.10 PM (2)

To date, only three US Presidents between the ages 65-69 have assumed the Oval Office. Once again, Hillary Clinton is 68 years old. She “come(s) from the ‘60s, a long time ago.”

Granted, the US population is aging. However, when selecting Presidents, this aging US population has trended towards younger Presidents.

Age upon assuming Oval Office: Barack Obama, 47 years old; George W. Bush, 54 years old; Bill Clinton, 46 years old.

Hillary wishes to dispel the age perception. HFA recently posted the SnapChat logo wearing a most recognizable pantsuit.

Screen Shot 2015-11-19 at 12.55.00 PM (2)

Of all the social networks, Snapchat skews youngest. According to Business Insider, 45% of Snapchat users are between the ages 18-24.Screen Shot 2015-11-19 at 12.44.44 PM (2)

Thus, a strategy has emerged: capture the young mind. Win the unspoiled voter, who is excited by the prospect of casting her first vote. Btw, Hillary uses Snapchat.

Further evidence of the strategy:

Twitter Banner displaying young prospective voters

Screen Shot 2015-11-19 at 12.53.58 PM (2)

HFA blog post appealing to youth and “a new age”

Screen Shot 2015-11-19 at 1.43.10 PM (2)

We should expect age to become a louder issue as the campaigns unfold. For reference, here are the ages of all the candidates, both Democrats and Republicans. Those between the ages 44- 60 are in bold.

Hilary Clinton, 68 years old

Bernie Sanders, 74 yeards old

Martin O’Malley, 52 years old

Donald Trump, 69 years old

Ben Carson, 64 years old

Marco Rubio, 44 years old

Ted Cruz, 44 years old

Jeb Bush, 62 years old

Carly Fiorina, 61 years old

John Kasich, 63 years old

Rand Paul, 52 years old

Moral Foundations: Reddit Political Communities

Moral Foundations Theory is a social psychological theory intended to explain the origins of and variation in human moral reasoning. The theory proposes moral foundations such as fairness, care, in-group, authority, and purity, and has been popularized by psychologist Jonathan Haidt in his book The Righteous Mind.

Haidt describes human morality as it relates to politics and proposes differences between conservatives and liberals as they relate to the moral foundations (TED Talk). Specifically, whereas conservatives appeal to fairness, care, in-group, authority, and purity equally, liberals appeal to fairness and care more than they appeal to in-group, authority, and purity.

Setting out to observe this phenomenon within Reddit Political Communities, I performed word frequency analyses on the /r/Republican and /r/Democrats corpora, totaling the words for each moral foundation, as defined by the LIWC dictionary. Comparing the totals, I found a trend consistent with Moral Foundations Theory. The visualization shows the moral foundations for /r/Democrats normalized against those for /r/Republican, with each value for /r/Republican set at 100%.

reddit_moralfoundations