The Coppock Curve : Coding and Backtesting a Trading Strategy in Python

A complete guide to using the Coppock Curve indicator for making better algorithmic trades



In today’s article, we are going to discuss a special indicator that is specifically dedicated to long-term trading purposes which is the Coppock Curve. We will first discuss the concepts that are prerequisites for the Coppock Curve. Then, we will move on to exploring the main concept of this article which is the Coppock Curve, and the math behind the indicator. After that, we will proceed to the coding part where we will use Python to build the indicator from scratch, construct a trading strategy based on it, backtest the strategy and compare the results with those of the SPY ETF (an ETF specifically designed to track the movement of the S&P 500 market index). With that being said, let’s dive into the article.


ROC and WMA


As I said before, we will first discuss the concepts that are prerequisites for the Coppock Curve which are nothing but the Rate Of Change (ROC) and the Weighted Moving Average (WMA). Without having some knowledge of these concepts, it will tough to learn the Coppock Curve.


First is the ROC indicator. The Rate Of Change indicator is a momentum indicator that is used by traders as an instrument to determine the percentage change in price from the current closing price and the price of a specified number of periods ago. Unlike other momentum indicators like the RSI and CCI, the Rate Of Change indicator is an unbounded oscillator whose values does not bound between certain limits.


To calculate the readings of ROC, we have to first determine the ’n’ value which is nothing but how many periods ago the current closing price is compared to. The determination of ’n’ varies from one trader to another but the traditional setting is 9 (widely used for short-term trading). With 9 as the ’n’ value, the readings of the ROC indicator are calculated as follows:


First, the closing price of 9 periods ago is subtracted from the current closing price. This difference is then divided by the closing price of 9 periods ago and multiplied by 100. The calculation can be mathematically represented as follows:



ROC 9 = [ ( C.CLOSE - PREV9.CLOSE ) / PREV9.CLOSE ] * 100

where,
C.CLOSE = Current Closing Price
PREV9.CLOSE = Closing Price of 9 Periods ago


Next is the WMA. One thing that bothered traders while using Simple Moving Average is that the indicator assigned equal weights to all data points present in a series. Here is where the Weighted Moving Average comes into play. To solve this problem, the WMA assigns greater weight (or greater importance) to the latest or the recent data point and lesser weight to the data points in the past. To determine the WMA for a given series, each value is multiplied by certain weights that are predetermined and the results are summed up.


Now, let’s assume a series that has the past three days’ closing price data whose values are 12, 13, 15 respectively. Now to calculate the WMA of these three closing prices, we need to first determine the weights which will be 1, 2, 3 and the sum of the weights is 6. Using these predetermined weights and their sum, the WMA calculation goes as follows:



[ ( 15 * 3 ) + ( 13 * 2 ) + ( 12 * 1 ) ] / 6  = 21.333333


From the above calculation, you could see that we have assigned greater weights to the latest data point which is 15, and lesser weights to the past data point which 12. Note that this is a very basic example of how weights are assigned to calculate the WMA but in the real world it’s way more complex. Sometimes, the weights can also be a decimal number. That’s all about ROC and WMA. Now, let’s dive into the main concept of this article which is the Coppock Curve.


Coppock Curve


Founded by Edwin Coppock, the Coppock Curve is a long-term momentum indicator that is often used by traders or investors to identify uptrends and downtrends in a market. This indicator is majorly applied on market indices like the S&P 500 to determine buy and sell signals but in this article, we are going to apply it to stocks and there is no restriction in doing too. Also, this indicator is designed in such a way that it is implemented on a monthly timeframe but today we are going to try using it on a daily timeframe.


The readings of the Coppock Curve are calculated by taking the WMA of the total of two ROCs, one with a lesser and the other with a greater ’n’ value. The typical setting to determine the Coppock Curve is 10 as the lookback period for WMA, 14 and 11 as the ’n’ value for long and short ROC respectively. The formula to calculate the Coppock Curve with the typical setting can be represented as follows:



COPPOCK CURVE = WMA 10 [ LONG ROC - SHORT ROC ]

where,
WMA 10 = 10-day Weighted Moving Average
LONG ROC = 14-period Rate Of Change
SHORT ROC = 11-period Rate Of Change


That’s the whole process of calculating the readings of the Coppock Curve. Now, let’s analyze a chart where Apple’s closing price data is plotted along with its Coppock Curve.


The above chart is divided into two panels: the upper panel with the closing price data of Apple, and the lower panel with the readings of the Coppock Curve. From the above chart, it can be observed that whenever the readings of the Coppock Curve are above zero, the histogram is plotted in green color, and similarly, whenever the readings are below zero or negative, the histogram turns to red color. Now, using the histogram, we could easily spot the current trend of the market. If the histogram is plotted in green color, it represents that the market is in an uptrend, and if the histogram is plotted in red color, the market is observed to be in a downtrend. The Coppock Curve can also be used to detect ranging and trending markets but it’s not its main forte.


Now let’s discuss the trading strategy that can be built using the Coppock Curve. The foremost strategy implemented based on this indicator is the zero-line cross which reveals a buy signal whenever the Coppock Curve rises from below to above the zero-line, likewise, a sell signal is revealed whenever the Coppock Curve goes from above to below the zero-line. If zero-line sounds like a buzzword, it is nothing but zero (0). This strategy can be represented as follows:



IF P.COPPC < ZERO-LINE AND C.COPPC > ZERO-LINE ==> BUY SIGNAL
IF P.COPPC > ZERO-LINE AND C.COPPC < ZERO-LINE ==> SELL SIGNAL


Directly applying this strategy might lead to catastrophic results since the Coppock Curve has two drawbacks. The first drawback of the Coppock Curve is that it is very lagging in nature. So one has to be cautious than ever while using the indicator for trading purposes. Another drawback is that the Coppock Curve is prone to revealing a lot of false signals leading us to make bad trades. In order to achieve good results, it is necessary to tune the typical zero-line cross strategy. Our tuned strategy reveals a buy signal only if the past four readings are below the zero-line and the current reading is above the zero-line. Similarly, a sell is generated only if the past four readings are above the zero-line and the current reading is below the zero-line. The tuned strategy can be represented as follows:



IF P.4 COPPCs < ZERO-LINE AND C.COPPC > ZERO-LINE ==> BUY SIGNAL
IF P.4 COPPCs > ZERO-LINE AND C.COPPC < ZERO-LINE ==> SELL SIGNAL


This concludes our theory part on Coppock Curve. Now, let’s move on to the programming part where we are first going to build the indicator from scratch, build the tuned Zero-line crossover strategy which we just discussed, then, compare our strategy’s performance with that of SPY ETF in Python. 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. Coppock Curve Calculation
4. Creating the Tuned Zero-line Crossover Trading Strategy
5. Plotting the Trading Lists
6. Creating our Position
7. Backtesting
8. 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:



# IMPORTING PACKAGES

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

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


Now that we have imported all the required packages into our python. Let’s pull the historical data of Apple 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 Apple 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:



# EXTRACTING STOCK DATA

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

aapl = get_historical_data('AAPL', '2020-01-01')
aapl.tail()


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 Apple from the starting of 2020 and stored it into the ‘aapl’ variable.


Step-3: Coppock Curve Calculation


In this step, we are going to calculate the readings of the Coppock Curve by following the formula we discussed before.


Python Implementation:



# COPPOCK CURVE CALCULATION

def wma(data, lookback):
    weights = np.arange(1, lookback + 1)
    val = data.rolling(lookback)
    wma = val.apply(lambda prices: np.dot(prices, weights) / weights.sum(), raw = True)
    return wma

def get_roc(close, n):
    difference = close.diff(n)
    nprev_values = close.shift(n)
    roc = (difference / nprev_values) * 100
    return roc

def get_cc(data, roc1_n, roc2_n, wma_lookback):
    longROC = get_roc(data, roc1_n)
    shortROC = get_roc(data, roc2_n)
    ROC = longROC + shortROC
    cc = wma(ROC, wma_lookback)
    return cc

aapl['cc'] = get_cc(aapl['close'], 14, 11, 10)
aapl = aapl.dropna()
aapl.tail()


Output:



Code Explanation: The above code can be classified into three categories: Weighted Moving Average calculation, Rate Of Change calculation, and the Coppock Curve calculation.


WMA calculation: In this part, we are first defining a function named ‘wma’ that takes the closing prices (‘data’), and the lookback period (‘lookback’) as parameters. Inside the function, we are first determining the weights that are to be assigned to each data point and stored them into the ‘weights’ variable. Next, we are creating a variable named ‘val to store the rolling data series for a specified number of periods with the help of the ‘rolling’ function provided by the Pandas package. Now, using the predetermined weights and the rolling values, we are calculating and storing the WMA values into the ‘wma’ variable.


ROC calculation: Firstly, we are defining a function named ‘get_roc’ that takes the stock’s closing price (‘close’) and the ’n’ value (’n’) as parameters. Inside the function, we are first taking the difference between the current closing price and the closing price for a specified number of periods ago using the ‘diff’ function provided by the Pandas package. With the help of the ‘shift’ function, we are taking into account the closing price for a specified number of periods ago and stored it into the ‘nprev_values’ variable. Then, we are substituting the determined values into the ROC indicator formula we discussed before to calculate the values and finally returned the data.


Coppock Curve calculation: Like how we did in the other two functions, here also we are first defining a function named ‘get_cc’ that takes a stock’s closing price data (‘data’), the ’n’ value for the longer ROC (‘roc_1’) and the shorter ROC (‘roc_2’), and the Weighted Moving Average lookback period (‘wma_lookback’) as parameters. Inside the function, we are first determining the two ROCs, one with the greater ’n’ value and the other with the shorter ’n’ value using the ‘get_roc’ function we created earlier. Then we are adding both the ROCs and stored the results into the ‘ROC’ variable. With the help of the ‘wma’ function we created before, we are taking the Weighted Moving Average of the sum of the two ROCs to get the readings of the Coppock Curve.


Finally, we are calling the created ‘get_cc’ function to store the readings of Apple’s Coppock Curve. Now, let’s proceed to create the discussed tuned zero-line crossover trading strategy.


Step-4: Creating the trading strategy


In this step, we are going to implement the discussed Coppock Curve tuned Zero-line crossover trading strategy in python.


Python Implementation:



# COPPOCK CURVE STRATEGY

def implement_cc_strategy(prices, cc):
    buy_price = []
    sell_price = []
    cc_signal = []
    signal = 0
    
    for i in range(len(prices)):
        if cc[i-4] < 0 and cc[i-3] < 0 and cc[i-2] < 0 and cc[i-1] < 0 and cc[i] > 0:
            if signal != 1:
                buy_price.append(prices[i])
                sell_price.append(np.nan)
                signal = 1
                cc_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                cc_signal.append(0)
        elif cc[i-4] > 0 and cc[i-3] > 0 and cc[i-2] > 0 and cc[i-1] > 0 and cc[i] < 0:
            if signal != -1:
                buy_price.append(np.nan)
                sell_price.append(prices[i])
                signal = -1
                cc_signal.append(signal)
            else:
                buy_price.append(np.nan)
                sell_price.append(np.nan)
                cc_signal.append(0)
        else:
            buy_price.append(np.nan)
            sell_price.append(np.nan)
            cc_signal.append(0)
            
    return buy_price, sell_price, cc_signal

buy_price, sell_price, cc_signal = implement_cc_strategy(aapl['close'], aapl['cc'])


Code Explanation: First, we are defining a function named ‘implement_cc_strategy’ which takes the stock prices (‘prices’), and the readings of the Coppock Curve (‘cc’) as parameters.

Inside the function, we are creating three empty lists (buy_price, sell_price, and cc_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 th