Python Tutorial: Stochastic Oscillator

Download the accompanying IPython Notebook for this Tutorial from Github. 

Last Tutorial, we outlined steps for calculating the Mass Index.

In this Tutorial, we introduce a new technical indicator, the Stochastic Oscillator.

Developed by George C. Lane in the late 1950s, the Stochastic Oscillator is a momentum indicator that shows the location of the close relative to the high-low range over a set number of periods.

The Stochastic Oscillator is calculated as follows:

%K = (Current Close - Lowest Low)/(Highest High - Lowest Low) * 100
%D = 3-day SMA of %K

Lowest Low = lowest low for the look-back period
Highest High = highest high for the look-back period

The default setting for the Stochastic Oscillator is 14 periods, which can be days, weeks, months or an intraday timeframe. A 14-period %K would use the most recent close, the highest high over the last 14 periods and the lowest low over the last 14 periods. %D is a 3-day simple moving average of %K.

As a bound oscillator, the Stochastic Oscillator makes it easy to identify overbought and oversold levels. The oscillator ranges from zero to one hundred. No matter how fast a security advances or declines, the Stochastic Oscillator will always fluctuate within this range. Traditional settings use 80 as the overbought threshold and 20 as the oversold threshold. These levels can be adjusted to suit analytical needs and security characteristics. Readings above 80 for the 20-day Stochastic Oscillator would indicate that the underlying security was trading near the top of its 20-day high-low range. Readings below 20 occur when a security is trading at the low end of its high-low range.

Before looking at some chart examples, it is important to note that overbought readings are not necessarily bearish. Securities can become overbought and remain overbought during a strong uptrend. Closing levels that are consistently near the top of the range indicate sustained buying pressure. In a similar vein, oversold readings are not necessarily bullish. Securities can also become oversold and remain oversold during a strong downtrend. Closing levels consistently near the bottom of the range indicate sustained selling pressure. It is, therefore, important to identify the bigger trend and trade in the direction of this trend. Look for occasional oversold readings in an uptrend and ignore frequent overbought readings. Similarly, look for occasional overbought readings in a strong downtrend and ignore frequent oversold readings.

Let’s use Python to compute the Stochastic Oscillator.

1.) Import modules.

import pandas as pd
import numpy as np
from pandas_datareader import data as web
import matplotlib.pyplot as plt
%matplotlib inline

2.a.) Define function for querying daily close.

def get_stock(stock,start,end):
 return web.DataReader(stock,'google',start,end)['Close']

2.b.) Define function for querying daily high.

def get_high(stock,start,end):
 return web.DataReader(stock,'google',start,end)['High']

2.c.) Define function for querying daily low.

def get_low(stock,start,end):
 return web.DataReader(stock,'google',start,end)['Low']

3.) Define function for the Stochastic Oscillator, both %K and %D.

def STOK(close, low, high, n): 
 STOK = ((close - pd.rolling_min(low, n)) / (pd.rolling_max(high, n) - pd.rolling_min(low, n))) * 100
 return STOK

def STOD(close, low, high, n):
 STOK = ((close - pd.rolling_min(low, n)) / (pd.rolling_max(high, n) - pd.rolling_min(low, n))) * 100
 STOD = pd.rolling_mean(STOK, 3)
 return STOD

How does the Stochastic Oscillator function work?

3.a.) To calculate %K, we find the difference between the current close and the lowest low for the look-back period, n. We then find the difference between the highest high for the look-back period, n, and the lowest low for the same look-back period. Dividing these two values and multiplying the result by 100, we arrive at %K, which we set to variable STOK.

#STOK = ((close - pd.rolling_min(low, n)) / (pd.rolling_max(high, n) - pd.rolling_min(low, n))) * 100

3.b.) Function returns STOK.

#return STOK

3.c.) To calculate %D, we first calculate %K.

#STOK = ((close - pd.rolling_min(low, n)) / (pd.rolling_max(high, n) - pd.rolling_min(low, n))) * 100

3.d.) Then we take the 3 day moving average of %K, and set the value to variable STOD.

#STOD = pd.rolling_mean(STOK, 3) 

3.e.) Function returns STOD.

#return STOD 

4.) Query daily close, high, and low for ‘FB’ during 2016.

df = pd.DataFrame(get_stock('FB', '1/1/2016', '12/31/2016'))
df['High'] = get_high('FB', '1/1/2016', '12/31/2016')
df['Low'] = get_low('FB', '1/1/2016', '12/31/2016')

5.) Run daily close, low, and high through %K and %D functions. Save series to new columns in dataframe.

df['%K'] = STOK(df['Close'], df['Low'], df['High'], 14)
df['%D'] = STOD(df['Close'], df['Low'], df['High'], 14)
df.tail()

6.) Plot daily close, %K, and %D.

df.plot(y=['Close'], figsize = (20, 5))
df.plot(y=['%K', '%D'], figsize = (20, 5))

There you have it! We created our Stochastic Oscillator indicator. Here’s the full code:

import pandas as pd
import numpy as np
from pandas_datareader import data as web
import matplotlib.pyplot as plt
%matplotlib inline

def get_stock(stock,start,end):
 return web.DataReader(stock,'google',start,end)['Close']

def get_high(stock,start,end):
 return web.DataReader(stock,'google',start,end)['High']

def get_low(stock,start,end):
 return web.DataReader(stock,'google',start,end)['Low']

def STOK(close, low, high, n): 
 STOK = ((close - pd.rolling_min(low, n)) / (pd.rolling_max(high, n) - pd.rolling_min(low, n))) * 100
 return STOK

def STOD(close, low, high, n):
 STOK = ((close - pd.rolling_min(low, n)) / (pd.rolling_max(high, n) - pd.rolling_min(low, n))) * 100
 STOD = pd.rolling_mean(STOK, 3)
 return STOD

df = pd.DataFrame(get_stock('FB', '1/1/2016', '12/31/2016'))
df['High'] = get_high('FB', '1/1/2016', '12/31/2016')
df['Low'] = get_low('FB', '1/1/2016', '12/31/2016')
df['%K'] = STOK(df['Close'], df['Low'], df['High'], 14)
df['%D'] = STOD(df['Close'], df['Low'], df['High'], 14)
df.tail()

Python Tutorial: Mass Index

Download the accompanying IPython Notebook for this Tutorial from Github. 

Python streamlines tasks requiring multiple steps in a single block of code. For this reason, it is a great tool for querying and performing analysis on data.

Last Tutorial, we outlined steps for calculating Commodity Channel Index (CCI).

In this Tutorial, we introduce a new technical indicator, the Mass Index.

Developed by Donald Dorsey, the Mass Index uses the high-low range to identify trend reversals based on range expansions. In this sense, the Mass Index is a volatility indicator that does not have a directional bias. Instead, the Mass Index identifies range bulges that can foreshadow a reversal of the current trend.

The Mass Index is calculated as follows:

Single EMA = 9-period exponential moving average (EMA) of the high-low differential 

Double EMA = 9-period EMA of the 9-period EMA of the high-low differential

EMA Ratio = Single EMA divided by Double EMA

Mass Index = 25-period sum of the EMA Ratio

First, the Single EMA provides the average for the high-low range.

Second, the Double EMA provides a second smoothing of this volatility measure.

Using a ratio of these two exponential moving averages normalizes the data series. This ratio shows when the Single EMA becomes large relative to the Double EMA.

The final step, a 25-period summation, acts like a moving average to further smooth the data series.

Overall, the Mass Index rises as the high-low range widens and falls as the high-low range narrows.

Donald Dorsey looked for “reversal bulges” to signal a trend reversal. According to Dorsey, a bulge occurs when the Mass Index moves above 27. This initial bulge does not complete the signal though. Dorsey waited for this bulge to reverse with a move back below 26.50. Once the reversal bulge is complete, traders should use other analysis techniques to determine the direction of the next move. Ideally, a downtrend followed by a reversal bulge would suggest a bullish trend reversal. Conversely, an uptrend followed by a reversal bulge would suggest a bearish trend reversal.

Let’s use Python to compute the Mass Index.

1.) Import modules.

import pandas as pd
import numpy as np
from pandas_datareader import data as web
import matplotlib.pyplot as plt
%matplotlib inline

2.a.) Define function for querying daily close.

def get_stock(stock,start,end):
 return web.DataReader(stock,'google',start,end)['Close']

2.b.) Define function for querying daily high.

def get_high(stock,start,end):
 return web.DataReader(stock,'google',start,end)['High']

2.c.) Define function for querying daily low.

def get_low(stock,start,end):
 return web.DataReader(stock,'google',start,end)['Low']

3.) Define function for Mass Index.

def MassIndex(high, low): 
 Range = high - low 
 EX1 = pd.ewma(Range, span = 9, min_periods = 8) 
 EX2 = pd.ewma(EX1, span = 9, min_periods = 8) 
 Mass = EX1 / EX2 
 MassIndex = pd.Series(pd.rolling_sum(Mass, 25), name = 'Mass Index') 
 return MassIndex

How does the Mass Index function work?

3.a.) Function calculates the difference between the high and the low, and sets this value to variable Range.

#Range = high - low  

3.b.) Function takes a 9 period Exponential Moving Average of the Range, and sets this value to variable EX1.

#EX1 = pd.ewma(Range, span = 9, min_periods = 8)  

3.c.) Function takes a 9 period Exponential Moving Average of the EX1, to smooth volatility, and sets this value to variable EX2.

#EX2 = pd.ewma(EX1, span = 9, min_periods = 8)  

3.d.) Function takes the ratio of EX1 to EX2, and sets this value to variable Mass.

#Mass = EX1 / EX2  

3.e.) Function calculates the 25 period rolling sum of Mass, and sets this value to variable MassIndex.

#MassIndex = pd.Series(pd.rolling_sum(Mass, 25), name = 'Mass Index')  

3.f.) Function returns MassIndex.

#return MassIndex

4.) Query daily close, high, and low for ‘FB’ during 2016.

df = pd.DataFrame(get_stock('FB', '1/1/2016', '12/31/2016'))
df['High'] = get_high('FB', '1/1/2016', '12/31/2016')
df['Low'] = get_low('FB', '1/1/2016', '12/31/2016')

5.) Run daily high and low through Mass Index function. Save series to new column in dataframe.

df['MassIndex'] = MassIndex(df['High'], df['Low'])
df.tail()

6.) Plot daily close and Mass Index.

df.plot(y=['Close'])
df.plot(y=['MassIndex'])

There you have it! We created our Mass Index indicator. Here’s the full code:

import pandas as pd
import numpy as np
from pandas_datareader import data as web
import matplotlib.pyplot as plt
%matplotlib inline

def get_stock(stock,start,end):
 return web.DataReader(stock,'google',start,end)['Close']
 
def get_high(stock,start,end):
 return web.DataReader(stock,'google',start,end)['High']
 
def get_low(stock,start,end):
 return web.DataReader(stock,'google',start,end)['Low']
 
def MassIndex(high, low): 
 Range = high - low 
 EX1 = pd.ewma(Range, span = 9, min_periods = 8) 
 EX2 = pd.ewma(EX1, span = 9, min_periods = 8) 
 Mass = EX1 / EX2 
 MassIndex = pd.Series(pd.rolling_sum(Mass, 25), name = 'Mass Index') 
 return MassIndex
 
df = pd.DataFrame(get_stock('FB', '1/1/2016', '12/31/2016'))
df['High'] = get_high('FB', '1/1/2016', '12/31/2016')
df['Low'] = get_low('FB', '1/1/2016', '12/31/2016')
df['MassIndex'] = MassIndex(df['High'], df['Low'])
df.tail()

Python Tutorial: CCI

Download the accompanying IPython Notebook for this Tutorial from Github. 

Python streamlines tasks requiring multiple steps in a single block of code. For this reason, it is a great tool for querying and performing analysis on data.

Last Tutorial, we outlined steps for calculating Rate of Change (ROC).

In this Tutorial, we introduce a new technical indicator, the Commodity Channel Index (CCI).

Developed by Donald Lambert, the Commodity Channel Index (CCI) is a versatile indicator that can be used to identify a new trend or warn of extreme conditions. CCI measures the current price level relative to an average price level over a given period of time. CCI is relatively high when prices are far above their average. CCI is relatively low when prices are far below their average. In this manner, CCI can be used to identify overbought and oversold levels.

The Commodity Channel Index (CCI) is calculated as follows:

CCI = (Typical Price  -  n-period SMA of TP) / (Constant x Mean Deviation)

Typical Price (TP) = (High + Low + Close)/3

Constant = .015

 Lambert set the Constant at .015 to ensure that approximately 70 to 80 percent of CCI values would fall between -100 and +100. This percentage also depends on the look-back period. A shorter CCI (10 periods) will be more volatile with a smaller percentage of values between +100 and -100. Conversely, a longer CCI (40 periods) will have a higher percentage of values between +100 and -100.

Lambert set the Constant at .015 to ensure that approximately 70 to 80 percent of CCI values would fall between -100 and +100. This percentage also depends on the look-back period. A shorter CCI (10 periods) will be more volatile with a smaller percentage of values between +100 and -100. Conversely, a longer CCI (40 periods) will have a higher percentage of values between +100 and -100.

The Commodity Channel Index (CCI) can be used as either a coincident or leading indicator. As a coincident indicator, surges above +100 reflect strong price action that can signal the start of an uptrend. Plunges below -100 reflect weak price action that can signal the start of a downtrend.

As a leading indicator, chartists can look for overbought or oversold conditions that may foreshadow a mean reversion. Similarly, bullish and bearish divergences can be used to detect early momentum shifts and anticipate trend reversals.

Let’s use Python to compute the Commodity Channel Index (CCI).

1.) Import modules.

import pandas as pd
import numpy as np
from pandas_datareader import data as web
import matplotlib.pyplot as plt
%matplotlib inline

2.a.) Define function for querying daily close.

def get_stock(stock,start,end):
 return web.DataReader(stock,'google',start,end)['Close']

2.b.) Define function for querying daily high.

def get_high(stock,start,end):
 return web.DataReader(stock,'google',start,end)['High']

2.c.) Define function for querying daily low.

def get_low(stock,start,end):
 return web.DataReader(stock,'google',start,end)['Low']

3.) Define function for Commodity Channel Index (CCI).

def CCI(close, high, low, n, constant): 
 TP = (high + low + close) / 3 
 CCI = pd.Series((TP - pd.rolling_mean(TP, n)) / (constant * pd.rolling_std(TP, n)), name = 'CCI_' + str(n)) 
 return CCI

How does the CCI function work?

3.a.) Function calculates Typical Price as the sum of the (High, Low, and Close) divided by three. The function sets this value to variable TP.

#TP = (high + low + close) / 3  

3.b.) Function subtracts n period simple moving average of the Typical Price from the current Typical Price. The difference is divided by the n period standard deviation of the Typical Price multiplied by the constant. The function sets this value to variable CCI.

#CCI = pd.Series((TP - pd.rolling_mean(TP, n)) / (constant * pd.rolling_std(TP, n)), name = 'CCI_' + str(n))  

3.c.) Function returns CCI

#return CCI

4.) Query daily close, high, and low for ‘FB’ during 2016.

df = pd.DataFrame(get_stock('FB', '1/1/2016', '12/31/2016'))
df['High'] = get_high('FB', '1/1/2016', '12/31/2016')
df['Low'] = get_low('FB', '1/1/2016', '12/31/2016')

5.) Run daily close, high, and low through CCI function. Save series to new column in dataframe.

df['CCI'] = CCI(df['Close'], df['High'], df['Low'], 20, 0.015)
df.tail()

6.) Plot daily close and CCI.

df.plot(y=['Close'])
df.plot(y=['CCI'])

There you have it! We created our CCI indicator. Here’s the full code:

import pandas as pd
import numpy as np
from pandas_datareader import data as web
import matplotlib.pyplot as plt
%matplotlib inline

def get_stock(stock,start,end):
 return web.DataReader(stock,'google',start,end)['Close']

def get_high(stock,start,end):
 return web.DataReader(stock,'google',start,end)['High']

def get_low(stock,start,end):
 return web.DataReader(stock,'google',start,end)['Low']

def CCI(close, high, low, n, constant): 
 TP = (high + low + close) / 3 
 CCI = pd.Series((TP - pd.rolling_mean(TP, n)) / (constant * pd.rolling_std(TP, n)), name = 'CCI_' + str(n)) 
 return CCI

df = pd.DataFrame(get_stock('FB', '1/1/2016', '12/31/2016'))
df['High'] = get_high('FB', '1/1/2016', '12/31/2016')
df['Low'] = get_low('FB', '1/1/2016', '12/31/2016')
df['CCI'] = CCI(df['Close'], df['High'], df['Low'], 20, 0.015)
df.tail()

Python Tutorial: ROC

Download the accompanying IPython Notebook for this Tutorial from Github. 

Python streamlines tasks requiring multiple steps in a single block of code. For this reason, it is a great tool for querying and performing analysis on data.

Last Tutorial, we outlined steps for calculating Relative Strength Index (RSI).
In this Tutorial, we introduce a new technical indicator, the Rate of Change (ROC).

‘The only thing constant is change’

The Rate of Change (ROC) is a technical indicator of momentum that measures the percentage change in price between the current price and the price n periods in the past.

The Rate of Change (ROC) is calculated as follows:

ROC = ((Most recent closing price - Closing price n periods ago) / Closing price n periods ago) x 100

The Rate of Change (ROC) is classed as a momentum indicator because it measures strength of price momentum. For example, if a stock’s price at the close of trading today is 10, and the closing price five trading days prior was 7, then the Rate of Change (ROC) over that time frame is approximately 43, calculated as (10 – 7 / 7) x 100 = 42.85.

 Positive values indicate upward buying pressure or momentum, while negative values below zero indicate selling pressure or downward momentum. Increasing values in either direction, positive or negative, indicate increasing momentum, and decreasing values indicate waning momentum.

The Rate of Change (ROC) is also sometimes used to indicate overbought or oversold conditions for a security. Positive values that are greater than 30 are generally interpreted as indicating overbought conditions, while negative values lower than negative 30 indicate oversold conditions.

 Let’s use Python to compute the Rate of Change (ROC).

1.) Import modules.

import pandas as pd
import numpy as np
from pandas_datareader import data as web
import matplotlib.pyplot as plt
%matplotlib inline

2.) Define function for querying daily close.

def get_stock(stock,start,end):
     return web.DataReader(stock,'google',start,end)['Close']

3.) Define function for Rate of Change (ROC).

def ROC(df, n):  
    M = df.diff(n - 1)  
    N = df.shift(n - 1)  
    ROC = pd.Series(((M / N) * 100), name = 'ROC_' + str(n))   
    return ROC

How does the ROC function work?

3.a.) Function calculates difference in most recent closing price from closing price n periods ago. Sets the value to variable M.
#M = df.diff(n - 1)

3.b.) Function calculates closing price n periods ago. Sets the value to variable N.

#N = df.shift(n - 1)

3.c.) Function creates series called ROC that is ((M/N) * 100)

#ROC = pd.Series(((M / N) * 100), name = 'ROC_' + str(n))

3.d.) Function returns ROC

#return ROC

4.) Query daily close for ‘FB’ during 2016.

df = pd.DataFrame(get_stock('FB', '1/1/2016', '12/31/2016'))

5.) Run daily close through ROC function. Save series to new column in dataframe.

df['ROC'] = ROC(df['Close'], 12)
df.tail()

6.) Plot daily close and ROC.

df.plot(y=['Close'])
df.plot(y=['ROC'])

There you have it! We created our ROC indicator. Here’s the full code:

import pandas as pd
import numpy as np
from pandas_datareader import data as web
import matplotlib.pyplot as plt
%matplotlib inline

def get_stock(stock,start,end):
     return web.DataReader(stock,'google',start,end)['Close']
    
def ROC(df, n):  
    M = df.diff(n - 1)  
    N = df.shift(n - 1)  
    ROC = pd.Series(((M / N) * 100), name = 'ROC_' + str(n))   
    return ROC
    
df = pd.DataFrame(get_stock('FB', '1/1/2016', '12/31/2016'))
df['ROC'] = ROC(df['Close'], 12)
df.tail()

Capitalism 2.0 Will Include a Healthy Dose of Socialism | Eric Weinstein

 

 

‘We never saw that capitalism may be defeated by its own child, Technology’

‘Markets only really work when the value of something and the price of that good or service coincide’

‘What causes value and price to get out of alignment?’

‘Technology figures out the small size of market failures and make it rather large’

‘Musicians went from producing a private good, where price and value coincided, to in fact producing a public good’

‘The idea of taxing people to pay for a standing army and their steady diet of jazz and rock and roll probably didn’t make a lot of sense’

‘The portion of the pie that is private goods is likely to shrink’

‘Traditionally, technology has moved us from low value occupations to higher value occupations’

‘Almost all code can be broken into two kinds: code that runs once and never repeats, and code that loops over and over and over’

‘Unfortunately, what most jobs are, are some version of a loop’

‘Our technical training for occupations moves the entire population into the crosshairs of software’

‘All repetitive behaviors are in the crosshairs of software’

‘We still have the Rube Goldberg section of codes, where something happens once, never to be repeated’

‘A poem will be composed that will never need to be recomposed’

‘Most people do not see themselves as capable of doing these one off acts of inspiration, that will probably always be fairly highly rewarded’

‘They see themselves as needing a repetitive behavior on which they can build their families, hopes, and dreams’

‘That era may have changed’

‘So many souls will require respect, hope, freedom, and choice who may not be able to defend themselves in the market as our software and machines continue to get better and better’

‘This is reason why something like universal basic income comes out of a place as fiercely capitalistic as Silicon Valley’

‘These are the folks who are closest to seeing the destruction that their work may visit upon the population’

‘Technology is forcing those who are more familiar with it to become the most compassionate’

‘I’m rather confused about whether to be optimistic or pessimistic’

‘Our government is populated by people who come from softer disciplines, whether that is law or poli sci’

‘Very few people in government come from a hardcore technical background’

‘There are very few senators who can solve a partial differential equation or program a computer’

‘This is a terrible inversion of what should be happening’

‘The technical professions were turned into support roles for this leadership class’

‘During the 1950s there was a tremendous vogue for thinking of a technical intellectual elite who could in fact lead us to a more hopeful, technological, scientific tomorrow’

‘Somewhere along the line, that got lost’

‘We have the technical talent to build an optimistic future’

‘For whatever reason, we are so terrified now of those technical folks, that we keep attempting to subordinate them’

‘If you look at a society like China’s, China is certainly not falling for this trap–they are proceeding along a very different path’

‘Whether or not we understand where we are and make the correct decisions for an optimistic future, depends as to whether we have the right leadership class’

‘Do we realize that the technical people should have been making the decisions all along’

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!

Python Tutorial: RSI

Download the accompanying IPython Notebook for this Tutorial from Github. 

Python streamlines tasks requiring multiple steps in a single block of code. For this reason, it is a great tool for querying and performing analysis on data.

Last Tutorial, we outlined steps for calculating Price Channels.

In this Tutorial, we introduce a new technical indicator, the Relative Strenght Index (RSI).

The Relative Strength Index (RSI) is a momentum indicator developed by noted technical analyst Welles Wilder, 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.

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

RSI values range from 0 to 100.

Traditional interpretation and usage of the RSI is that RSI values of 70 or above indicate that a security is becoming overbought or overvalued, and therefore may be primed for a trend reversal or corrective pullback in price. On the other side, an RSI reading of 30 or below is commonly interpreted as indicating an oversold or undervalued condition that may signal a trend change or corrective price reversal to the upside.

Let’s use Python to compute the Relative Strenght Index (RSI).

1.) Import modules (numpy included).

import pandas as pd
import numpy as np
from pandas_datareader import data as web
import matplotlib.pyplot as plt
%matplotlib inline

2.) Define function for querying daily close.

def get_stock(stock,start,end):
 return web.DataReader(stock,'google',start,end)['Close']

3.) Define function for RSI.

def RSI(series, period):
 delta = series.diff().dropna()
 u = delta * 0
 d = u.copy()
 u[delta > 0] = delta[delta > 0]
 d[delta < 0] = -delta[delta < 0]
 u[u.index[period-1]] = np.mean( u[:period] ) #first value is sum of avg gains
 u = u.drop(u.index[:(period-1)])
 d[d.index[period-1]] = np.mean( d[:period] ) #first value is sum of avg losses
 d = d.drop(d.index[:(period-1)])
 rs = pd.stats.moments.ewma(u, com=period-1, adjust=False) / \
 pd.stats.moments.ewma(d, com=period-1, adjust=False)
 return 100 - 100 / (1 + rs)

How does the RSI function work?

– 3.a.) Function creates two series of daily differences.

– 3.b.) One series is daily positive differences, i.e. gains.

– 3.c.) One series is daily negative difference, i.e. losses.

– 3.d.) Average daily positive differences for the period specified.

– 3.e.) Average daily negative difference for the period specified.

– 3.f.) RS is set equal to Exponential Moving Average of daily positive differences for the period sepcified / Exponential Moving Average of daily positive differences for the period sepcified.

– 3.g) Return 100 – 100 / (1 + RS)

 4.) Query daily close for ‘FB’ during 2016.

df = pd.DataFrame(get_stock('FB', '1/1/2016', '12/31/2016'))

5.) Run daily close through RSI function. Save series to new column in dataframe.

df['RSI'] = RSI(df['Close'], 14)
df.tail()

6.) Plot daily close and RSI.

df.plot(y=['Close'])
df.plot(y=['RSI'])

There you have it! We created our RSI indicator. Here’s the full code:

import pandas as pd
import numpy as np
from pandas_datareader import data as web
import matplotlib.pyplot as plt
%matplotlib inline

def get_stock(stock,start,end):
 return web.DataReader(stock,'google',start,end)['Close']
 
def RSI(series, period):
 delta = series.diff().dropna()
 u = delta * 0
 d = u.copy()
 u[delta > 0] = delta[delta > 0]
 d[delta < 0] = -delta[delta < 0]
 u[u.index[period-1]] = np.mean( u[:period] ) #first value is sum of avg gains
 u = u.drop(u.index[:(period-1)])
 d[d.index[period-1]] = np.mean( d[:period] ) #first value is sum of avg losses
 d = d.drop(d.index[:(period-1)])
 rs = pd.stats.moments.ewma(u, com=period-1, adjust=False) / \
 pd.stats.moments.ewma(d, com=period-1, adjust=False)
 return 100 - 100 / (1 + rs)
 
df = pd.DataFrame(get_stock('FB', '1/1/2016', '12/31/2016'))
df['RSI'] = RSI(df['Close'], 14)
df.tail()

Python Tutorial: Price Channels

Download the accompanying IPython Notebook for this Tutorial from Github. 

 

Python streamlines tasks requiring multiple steps in a single block of code. For this reason, it is a great tool for querying and performing analysis on stock ticker data.

Last post, we outlined steps for calculating Bollinger Bands.

In this post, we introduce a new technical indicator,  Price Channels.

Price Channels

Price Channels are lines set above and below the price of a security. The upper channel is set at the x-period high and the lower channel is set at the x-period low. For a 20-day Price Channel, the upper channel would equal the 20-day high and the lower channel would equal the 20-day low.

Price Channels can be used to identify upward thrusts that signal the start of an uptrend or downward plunges that signal the start of a downtrend. Price Channels can also be used to identify overbought or oversold levels within a bigger downtrend or uptrend.

Price Channels are calculated as follows:

Upper Channel: 20-day high
Lower Channel: 20-day low

Let’s use Python to compute Price Channels.

1. Import modules.

import pandas as pd
import pandas.io.data as web
import matplotlib.pyplot as plt
%matplotlib inline

2. Define function for querying the daily high.

def get_high(stock, start, end): 
     return web.get_data_yahoo(stock, start, end)['High']

3. Define function for querying the daily low.

def get_low(stock, start, end): 
     return web.get_data_yahoo(stock, start, end)['Low']

4. Define function for querying daily close.

def get_close(stock, start, end): 
     return web.get_data_yahoo(stock, start, end)['Adj Close']

5. Query daily high, daily low, and daily close for ‘FB’ during 2016.

x = pd.DataFrame(get_high('FB', '1/1/2016', '12/31/2016'))
x['Low'] = pd.DataFrame(get_low('FB', '1/1/2016', '12/31/2016'))
x['Close'] = pd.DataFrame(get_close('FB', '1/1/2016', '12/31/2016'))

6. Compute 4 week high and 4 week low using rolling max/min. Add 50 day simple moving average for good measure.

x['4WH'] = pd.rolling_max(x['High'], 20)
x['4WL'] = pd.rolling_min(x['Low'], 20)
x['50 sma'] = pd.rolling_mean(x['Close'], 50)

7. Plot 4WH, 4WL, 50 sma, and daily close.

x.plot(y=['4WH', '4WL', '50 sma', 'Close'])

There you have it! We created our Price Channels. Here’s the full code:

import pandas as pd 
import pandas.io.data as web 
import matplotlib.pyplot as plt
%matplotlib inline

def get_high(stock, start, end): 
     return web.get_data_yahoo(stock, start, end)['High']
def get_low(stock, start, end): 
     return web.get_data_yahoo(stock, start, end)['Low']
def get_close(stock, start, end): 
     return web.get_data_yahoo(stock, start, end)['Adj Close']

x = pd.DataFrame(get_high('FB', '1/1/2016', '12/31/2016'))
x['Low'] = pd.DataFrame(get_low('FB', '1/1/2016', '12/31/2016'))
x['Close'] = pd.DataFrame(get_close('FB', '1/1/2016', '12/31/2016'))

x['4WH'] = pd.rolling_max(x['High'], 20)
x['4WL'] = pd.rolling_min(x['Low'], 20)
x['50 sma'] = pd.rolling_mean(x['Close'], 50)

x.plot(y=['4WH', '4WL', '50 sma', 'Close'])

In celebration of completing this tutorial, let’s watch Ed Seykota sing ‘The Whipsaw Song’.