Algorithmic Trading with Williams %R in Python

Learn to build a killer trading strategy with a powerful technical indicator in python



Introduction


While having a look at the list of most popular momentum indicators that consists of the Relative Strength Index, and the Stochastic Oscillator, the one we are going to discuss today also joins the list when considering its usage and efficiency in the real world market. It’s none other than the Williams %R.


In this article, we are going to explore what Williams %R is all about, the math behind this indicator, and how a trading strategy based on it can be built with the help of python. As a bonus step, we will compare the returns of our Williams %R strategy returns with the returns of SPY ETF (an ETF specifically designed to track the movement of the S&P 500 Index) to get an idea of how well our strategy performs in the real-world market and can be considered as a step to evaluate the strategy. Considering your curiosity piqued, let’s dive into the article!


Williams %R


Founded by Larry Williams, the Williams %R is a momentum indicator whose values oscillate between 0 to -100. This indicator is most similar to the Stochastic Oscillator but differs in its calculation. Traders use this indicator to spot potential entry and exit points for trades by constructing two levels of overbought and oversold. Before moving a word on overbought and oversold levels: A stock is said to be overbought when the market’s trend seems to be extremely bullish and bound to consolidate. Similarly, a stock reaches an oversold region when the market’s trend seems to be extremely bearish and has the tendency to bounce. The traditional threshold for overbought and oversold levels are 20 and 80 respectively but there aren't any prohibitions in taking other values too.


In order to calculate the values of Williams %R with the traditional setting of 14 as the lookback period, first, the highest high and the lowest low for each period over a fourteen-day timeframe is determined. Then, the two differences are taken: The closing price from the highest high, and the lowest low from the highest high. Finally, the first difference is divided by the second difference and multiple by -100 to obtain the values of Williams %R. The calculation can be mathematically represented as follows:



W%R 14 = [ H.HIGH - C.PRICE ] / [ L.LOW - C.PRICE ] * ( - 100 )

where,
W%R 14 = 14-day Williams %R of the stock
H.HIGH = 14-day Highest High of the stock
L.LOW = 14-day Lowest Low of the stock
C.PRICE = Closing price of the stock


The underlying idea of this indicator is that the stock will keep reaching new highs when it is a strong uptrend and similarly, the stock will reach new lows when it follows a sturdy downtrend. With that being said, let’s discuss the trading strategy we are going to implement in this article.


About our trading strategy: There are a lot of Williams %R-based trading strategies that can be implemented in the real world market but the one we are going to discuss today is a strategy based on the overbought and oversold levels. The strategy reveals a buy signal whenever the previous reading of the Williams %R is below -20 and the current reading is above -20. Likewise, a sell signal is generated whenever the previous reading of the Williams %R is above -80 and the current reading is below -80. Our trading strategy can be represented as follows:



IF PREV.W%R < [ - 20 ] AND CURRENT.W%R > [ - 20 ] ==> BUY SIGNAL
IF PREV.W%R > [ - 80 ] AND CURRENT.W%R < [ - 80 ] ==> SELL SIGNAL


This concludes our theory part on Williams %R, its calculation, and the trading strategy. Now, let’s build this indicator from scratch in Python, construct the trading strategy we discussed, backtest it on the Netflix data, and compare the returns with those of the SPY ETF. Without further ado, let’s do some coding! Before moving on, a note on disclaimer: This article’s sole purpose is to educate people and must be considered as an information piece but not as investment advice or so.


Implementation in Python


The coding part is classified into various steps as follows:



1. Importing Packages
2. Extracting Stock Data from Twelve Data
3. Williams %R Calculation
4. Williams %R Indicator Plot
5. Creating the Trading Strategy
6. Plotting the Trading Lists
7. Creating our Position
8. Backtesting
9. SPY ETF Comparison


We will be following the order mentioned in the above list and buckle up your seat belts to follow every upcoming coding part.


Step-1: Importing Packages


Importing the required packages into the python environment is a non-skippable step. The primary packages are going to be Pandas to work with data, NumPy to work with arrays and for complex functions, Matplotlib for plotting purposes, and Requests to make API calls. The secondary packages are going to be Math for mathematical functions and Termcolor for font customization (optional).


Python Implementation:



import pandas as pd
import numpy as np
import requests
import matplotlib.pyplot as plt
from math import floor
from termcolor import colored as cl

plt.rcParams['figure.figsize'] = (20,10)
plt.style.use('fivethirtyeight')


Now that we have imported all the required packages into our python. Let’s pull the historical data of Netflix with Twelve Data’s API endpoint.


Step-2: Extracting data from Twelve Data


In this step, we are going to pull the historical stock data of Netflix using an API endpoint provided by twelvedata.com. Before that, a note on twelvedata.com: Twelve Data is one of the leading market data providers having an enormous amount of API endpoints for all types of market data. It is very easy to interact with the APIs provided by Twelve Data and has one of the best documentation ever. Also, ensure that you have an account on twelvedata.com, only then, you will be able to access your API key (vital element to extract data with an API).


Python Implementation:



def get_historical_data(symbol, start_date):
    api_key = 'YOUR API KEY'
    api_url = f'https://api.twelvedata.com/time_series?symbol={symbol}&interval=1day&outputsize=5000&apikey={api_key}'
    raw_df = requests.get(api_url).json()
    df = pd.DataFrame(raw_df['values']).iloc[::-1].set_index('datetime').astype(float)
    df = df[df.index >= start_date]
    df.index = pd.to_datetime(df.index)
    return df

nflx = get_historical_data('NFLX', '2020-01-01')
nflx


Output:



Code Explanation: The first thing we did is to define a function named ‘get_historical_data’ that takes the stock’s symbol (‘symbol’) and the starting date of the historical data (‘start_date’) as parameters. Inside the function, we are defining the API key and the URL and stored them into their respective variable. Next, we are extracting the historical data in JSON format using the ‘get’ function and stored it into the ‘raw_df’ variable. After doing some processes to clean and format the raw JSON data, we are returning it in the form of a clean Pandas dataframe. Finally, we are calling the created function to pull the historic data of Netflix from the starting of 2020 and stored it into the ‘nflx’ variable.


Step-4: Williams %R Calculation


In this step, we are going to calculate the values of Williams %R by following the formula we discussed before.


Python Implementation:



def get_wr(high, low, close, lookback):
    highh = high.rolling(lookback).max() 
    lowl = low.rolling(lookback).min()
    wr = -100 * ((highh - close) / (highh - lowl))
    return wr

nflx['wr_14'] = get_wr(nflx['high'], nflx['low'], nflx['close'], 14)
nflx = nflx.dropna()
nflx


Output:


Code Explanation: We are first defining a function named ‘get_wr’ that takes a stock’s high price data (‘high’), low price data (‘low’), closing price data (‘close’), and the lookback period (‘period’) as parameters. Inside the function, we are first determining the highest high over a specific lookback period timeframe with the help of the ‘rolling’ and ‘max’ functions provided by the Pandas package and stored it into the ‘highh’ variable. What the ‘rolling’ function performs is that it will take into account the n-period timeframe we specify and the ‘max’ function filters the maximum values present in the given dataframe.


Next, we are defining a variable named ‘lowl’ to store the lowest low over a specified lookback period timeframe which we determined using the ‘rolling’ and ‘min’ function (as the name suggests, filters the minimum values in the given dataframe) provided by the Pandas package.


Then, we are substituting the determined highest high and lowest low values into the formula we discussed before to calculate the values of Williams %R and stored it into the ‘wr’ variable. Finally, we are returning and calling the created function to store Netflix’s Williams %R readings with 14 as the lookback period.


Step-4: Williams %R Plot


In this step, we are going to plot the calculated Williams %R values of Netflix to make more sense out of them. The main aim of this part is not on the coding section but instead to observe the plot to gain a solid understanding of the Williams %R technical indicator.


Python Implementation:



ax1 = plt.subplot2grid((11,1), (0,0), rowspan = 5, colspan = 1)
ax2 = plt.subplot2grid((11,1), (6,0), rowspan = 5, colspan = 1)
ax1.plot(nflx['close'], linewidth = 2)
ax1.set_title('NFLX CLOSING PRICE')
ax2.plot(nflx['wr_14'], color = 'orange', linewidth = 2)
ax2.axhline(-20, linewidth = 1.5, linestyle = '--', color = 'grey')
ax2.axhline(-80, linewidth = 1.5, linestyle = '--', color = 'grey')
ax2.set_title('NFLX WILLIAMS %R 14')
plt.show()


Output:



The above chart is divided into two panels: The upper panel with the closing price of Netflix’s stock data and the lower panel with the values of Netflix’s 14-day readings of Williams %R. Now, the chart can be utilized in two ways. The first way is using the chart as a tool to identify overbought and oversold states of the market. You could observe that there are two horizontal grey lines plotted above and below the market which is nothing but the overbought and oversold levels plotted at a threshold of -20 and -80 respectively. You can consider that the market is in the state of overbought if the Williams %R has a reading of above the upper line or the overbought line. Similarly, you can assume that the market is in the state of oversold if the Williams %R has a reading of below the lower line or the oversold line.


The second way of using Williams %R is to identify false momentum in the market. During a sturdy uptrend, the readings of Williams %R tend to reach above -20 frequently. If the indicator falls and struggles to reach above -20 before the next fall, indicates that the market’s momentum is not authentic and possible to follow a tremendous downtrend. Likewise, during a healthy downtrend, the readings of Williams %R bound to go below -80 frequently. If the indicator rises and fails to reach -80 before the next rise, reveals that the market is going to follow a positive trend.


Since Williams %R is a directional indicator (an indicator whose movement is directly proportional to that of the actual market), traders also use this indicator to find and confirm strong uptrends or downtrends in a market and trade along with it. Some indicators are not of much use while utilized for identifying or confirming market trends as they might be lagging in nature (an indicator that takes into account the historical data points to determine the current reading) but Williams %R is an effective one because it is a leading indicator (an indicator that takes into account the previous data points to predict the future movements).


Step-5: Creating the trading strategy


In this step, we are going to implement the discussed Williams %R trading strategy in python.


Python Implementation:



def implement_wr_strategy(prices, wr):    
    buy_price = []
    sell_price = []
    wr_signal = []
    signal = 0

    for i in range(len(wr)):
        if wr[i-1] > -80 and wr[i] < -80:
            if signal != 1:
                buy_price.append(prices[i])
                sell_price.append(np.nan)
                signal = 1
                wr_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                wr_signal.append(0)
        elif wr[i-1] < -20 and wr[i] > -20:
            if signal != -1:
                buy_price.append(np.nan)
                sell_price.append(prices[i])
                signal = -1
                wr_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                wr_signal.append(0)
        else:
            buy_price.append(np.nan)
            sell_price.append(np.nan)
            wr_signal.append(0)
            
    return buy_price, sell_price, wr_signal
            
buy_price, sell_price, wr_signal = implement_wr_strategy(nflx['close'], nflx['wr_14'])


Code Explanation: First, we are defining a function named ‘implement_wr_strategy’ which takes the stock prices (‘prices), and the values of Williams %R indicator (‘wr’) as parameters.

Inside the function, we are creating three empty lists (buy_price, sell_price, and wr_signal) in which the values will be appended while creating the trading strategy.


After that, we are implementing the trading strategy through a for-loop. Inside the for-loop, we are passing certain conditions, and if the conditions are satisfied, the respective values will be appended to the empty lists. If the condition to buy the stock gets satisfied, the buying price will be appended to the ‘buy_price’ list, and the signal value will be appended as 1 representing to buy the stock. Similarly, if the condition to sell the stock gets satisfied, the selling price will be appended to the ‘sell_price’ list, and the signal value will be appended as -1 representing to sell the stock.


Finally, we are returning the lists appended with values. Then, we are calling the created function and stored the values into their respective variables. The list doesn’t make any sense unless we plot the values. So, let’s plot the values of the created trading lists.


Step-6: Plotting the trading signals


In this step, we are going to plot the created trading lists to make sense out of them.


Python Implementation:



ax1 = plt.subplot2grid((11,1), (0,0), rowspan = 5, colspan = 1)
ax2 = plt.subplot2grid((11,1), (6,0), rowspan = 5, colspan = 1)
ax1.plot(nflx['close'], linewidth = 2)
ax1.plot(nflx.index, buy_price, marker = '^', markersize = 12, linewidth = 0, color = 'green', label = 'BUY SIGNAL')
ax1.plot(nflx.index, sell_price, marker = 'v', markersize = 12, linewidth = 0, color = 'r', label = 'SELL SIGNAL')
ax1.legend()
ax1.set_title('NFLX TRADING SIGNALS')
ax2.plot(nflx['wr_14'], color = 'orange', linewidth = 2