In today’s article, we are going to discuss an indicator that is specifically dedicated to long-term trading purposes — 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 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
The prerequisites for the Coppock Curve are the Rate Of Change (ROC) and the Weighted Moving Average (WMA). Without having some knowledge of these concepts, it will be 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 such as the RSI and CCI, the Rate Of Change indicator is an unbounded oscillator whose values are not bound to certain limits.
To calculate the readings of ROC, we have to first determine the ’n’ value which is 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 most 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 is 12. Note that this is a very basic example of how weights are assigned to calculate the WMA, 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
The Curve Basics
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 to use 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.
Coppock Curve Trading Strategy
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 just 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. One has to be more 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 signal 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 EODHD 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)
With the required packages imported into Python, we can proceed to fetch historical data for Apple using EODHD’s OHLC split-adjusted data API endpoint.
Step-2: Extracting data from EODHD
In this phase, we’re set to retrieve the historical stock data for Apple using the OHLC split-adjusted API endpoint provided by EODHD. It’s important to note that EOD Historical Data (EODHD)is a reliable provider of financial APIs, encompassing an extensive array of market data, including historical data and economic news. Be sure to possess an EODHD account and access your secret API key, a crucial element for data extraction via the API.
Python Implementation:
# EXTRACTING STOCK DATA def get_historical_data(symbol, start_date): api_key = 'YOUR API KEY' api_url = f'https://eodhistoricaldata.com/api/technical/{symbol}?order=a&fmt=json&from={start_date}&function=splitadjusted&api_token={api_key}' raw_df = requests.get(api_url).json() df = pd.DataFrame(raw_df) df.date = pd.to_datetime(df.date) df = df.set_index('date') return df aapl = get_historical_data('AAPL', '2020-01-01') aapl.tail()
Output:
Code Explanation: We begin by defining a function named ‘get_historical_data,’ which takes the stock symbol (‘symbol’) and the start date for historical data (‘start_date’) as parameters. Inside the function, we define the API key and URL, then retrieve the historical data in JSON format using the ‘get’ function and store it in the ‘raw_df’ variable. After cleaning and formatting the raw JSON data, we return it as a Pandas dataframe. Finally, we call this function to fetch Apple’s historical data from the start of 2020 and store it in 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 in 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: just as we did in the other two functions, here as well we first define 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 buying 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 in 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-5: Plotting the trading signals
In this step, we are going to plot the created trading lists to make sense of them.
Python Implementation:
# COPPOCK CURVE TRADING SIGNAL PLOT
ax1 = plt.subplot2grid((11,1), (0,0), rowspan = 5, colspan = 1)
ax2 = plt.subplot2grid((11,1), (6,0), rowspan = 6, colspan = 1)
ax1.plot(aapl['close'], linewidth = 2, label = 'aapl')
ax1.plot(aapl.index, buy_price, marker = '^', color = 'green', markersize = 12, linewidth = 0, label = 'BUY SIGNAL')
ax1.plot(aapl.index, sell_price, marker = 'v', color = 'r', markersize = 12, linewidth = 0, label = 'SELL SIGNAL')
ax1.legend()
ax1.set_title('AAPL CC TRADING SIGNALS')
for i in range(len(aapl)):
if aapl.iloc[i, 5] >= 0:
ax2.bar(aapl.iloc[i].name, aapl.iloc[i, 5], color = '#009688')
else:
ax2.bar(aapl.iloc[i].name, aapl.iloc[i, 5], color = '#f44336')
ax2.set_title('AAPL COPPOCK CURVE')
plt.show()
Output:
Code Explanation: We are plotting the readings of the Coppock Curve along with the buy and sell signals generated by the tuned Zero-line crossover trading strategy. We can observe that whenever the previous four readings of the Coppock Curve are below the zero line and the current reading is above the zero-line, a green-colored buy signal is plotted in the chart. Similarly, whenever the previous four readings of the Coppock Curve are above the zero-line and the current reading is below the zero-line, a red-colored sell signal is plotted in the chart.
Step-6: Creating our Position
In this step, we are going to create a list that indicates 1 if we hold the stock or 0 if we don’t own or hold the stock.
Python Implementation:
# STOCK POSITION
position = []
for i in range(len(cc_signal)):
if cc_signal[i] > 1:
position.append(0)
else:
position.append(1)
for i in range(len(aapl['close'])):
if cc_signal[i] == 1:
position[i] = 1
elif cc_signal[i] == -1:
position[i] = 0
else:
position[i] = position[i-1]
close_price = aapl['close']
cc = aapl['cc']
cc_signal = pd.DataFrame(cc_signal).rename(columns = {0:'cc_signal'}).set_index(aapl.index)
position = pd.DataFrame(position).rename(columns = {0:'cc_position'}).set_index(aapl.index)
frames = [close_price, cc, cc_signal, position]
strategy = pd.concat(frames, join = 'inner', axis = 1)
strategy
Output:
Code Explanation: First, we are creating an empty list named ‘position’. We are passing two for-loops, one is to generate values for the ‘position’ list to just match the length of the ‘signal’ list. The other for-loop is the one we are using to generate actual position values. Inside the second for-loop, we are iterating over the values of the ‘signal’ list, and the values of the ‘position’ list get appended concerning which condition gets satisfied. The value of the position remains 1 if we hold the stock or remains 0 if we sold or don’t own the stock. Finally, we are doing some data manipulations to combine all the created lists into one dataframe.
From the output being shown, we can see that in the first two rows, our position in the stock has remained 1 (since there isn’t any change in the Coppock Curve signal) but our position suddenly turned to -1 as we sold the stock when the Coppock Curve trading signal represents a sell signal (-1). Our position will remain 0 until some changes in the trading signal occur. Now it’s time to implement some backtesting processes!
Step-7: Backtesting
Before moving on, it is essential to know what backtesting is. Backtesting is the process of seeing how well our trading strategy has performed on the given stock data. In our case, we are going to implement a backtesting process for our Coppock Curve trading strategy over the Apple stock data.
Python Implementation:
# BACKTESTING aapl_ret = pd.DataFrame(np.diff(aapl['close'])).rename(columns = {0:'returns'}) cc_strategy_ret = [] for i in range(len(aapl_ret)): returns = aapl_ret['returns'][i]*strategy['cc_position'][i] cc_strategy_ret.append(returns) cc_strategy_ret_df = pd.DataFrame(cc_strategy_ret).rename(columns = {0:'cc_returns'}) investment_value = 100000 cc_investment_ret = [] for i in range(len(cc_strategy_ret_df['cc_returns'])): number_of_stocks = floor(investment_value/aapl['close'][i]) returns = number_of_stocks*cc_strategy_ret_df['cc_returns'][i] cc_investment_ret.append(returns) cc_investment_ret_df = pd.DataFrame(cc_investment_ret).rename(columns = {0:'investment_returns'}) total_investment_ret = round(sum(cc_investment_ret_df['investment_returns']), 2) profit_percentage = floor((total_investment_ret/investment_value)*100) print(cl('Profit gained from the CC strategy by investing $100k in AAPL : {}'.format(total_investment_ret), attrs = ['bold'])) print(cl('Profit percentage of the CC strategy : {}%'.format(profit_percentage), attrs = ['bold']))
Output:
Profit gained from the CC strategy by investing $100k in AAPL : 91164.07 Profit percentage of the CC strategy : 91%
Code Explanation: First, we are calculating the returns of the Apple stock using the ‘diff’ function provided by the NumPy package and we have stored it as a dataframe in the ‘aapl_ret’ variable. Next, we are passing a for-loop to iterate over the values of the ‘aapl_ret’ variable to calculate the returns we gained from our Coppock Curve trading strategy, and these returns values are appended to the ‘cc_strategy_ret’ list. Next, we are converting the ‘cc_strategy_ret’ list into a dataframe and storing it in the ‘cc_strategy_ret_df’ variable.
Next comes the backtesting process. We are going to backtest our strategy by investing a hundred thousand USD into our trading strategy. So first, we are storing the amount of investment into the ‘investment_value’ variable. After that, we are calculating the number of Apple stocks we can buy using the investment amount. You can notice that I’ve used the ‘floor’ function provided by the Math package because, while dividing the investment amount by the closing price of Apple stock, it spits out an output with decimal numbers. The number of stocks should be an integer but not a decimal number. Using the ‘floor’ function, we can cut out the decimals. Remember that the ‘floor’ function is way more complex than the ‘round’ function. Then, we are passing a for-loop to find the investment returns followed by some data manipulation tasks.
Finally, we are printing the total return we got by investing a hundred thousand into our trading strategy and it is revealed that we have made an approximate profit of ninety thousand USD in three and a half years. That’s not bad! Now, let’s compare our returns with SPY ETF (an ETF designed to track the S&P 500 stock market index) returns.
Step-8: SPY ETF Comparison
While this step is optional, it is highly recommended as it enables us to assess the performance of our trading strategy against a benchmark (SPY ETF). Here, we’ll extract SPY ETF data using the ‘get_historical_data’ function and compare the returns from the SPY ETF with the returns generated by our trading strategy applied to Apple stock.
Python Implementation:
# SPY ETF COMPARISON def get_benchmark(start_date, investment_value): spy = get_historical_data('SPY', start_date)['close'] benchmark = pd.DataFrame(np.diff(spy)).rename(columns = {0:'benchmark_returns'}) benchmark_investment_ret = [] for i in range(len(benchmark['benchmark_returns'])): number_of_stocks = floor(investment_value/spy[i]) returns = number_of_stocks*benchmark['benchmark_returns'][i] benchmark_investment_ret.append(returns) benchmark_investment_ret_df = pd.DataFrame(benchmark_investment_ret).rename(columns = {0:'investment_returns'}) return benchmark_investment_ret_df benchmark = get_benchmark('2020-01-01', 100000) investment_value = 100000 total_benchmark_investment_ret = round(sum(benchmark['investment_returns']), 2) benchmark_profit_percentage = floor((total_benchmark_investment_ret/investment_value)*100) print(cl('Benchmark profit by investing $100k : {}'.format(total_benchmark_investment_ret), attrs = ['bold'])) print(cl('Benchmark Profit percentage : {}%'.format(benchmark_profit_percentage), attrs = ['bold'])) print(cl('CC Strategy profit is {}% higher than the Benchmark Profit'.format(profit_percentage - benchmark_profit_percentage), attrs = ['bold']))
Output:
Benchmark profit by investing $100k : 40181.36 Benchmark Profit percentage : 40% CC Strategy profit is 51% higher than the Benchmark Profit
Code Explanation: The code used in this step is almost similar to the one used in the previous backtesting step but, instead of investing in Apple, we are investing in SPY ETF by not implementing any trading strategies. From the output, we can see that our Coppock Curve tuned zero-line crossover trading strategy has outperformed the SPY ETF by 51%. That’s great!
Final Thoughts!
After an immense process of crushing both the theory and coding parts, we have successfully learned what the Coppock Curve is all about, and how a trading strategy based on it can be implemented with the help of Python.
Now, talking about improvisation of which I speak a lot in all of my articles, one important aspect that can be improved is the way to choose the best stocks. In this article, we chose the Apple stock for the implementation of the indicator but it might end up resulting in bad trades while applied in the real-world market. So, it is essential to pick the right stocks, but how? Approaching this situation with a quantitative strategy would be more optimal and if this sounds too technical, read my article about it here.
That’s it! Hope you learned something new and useful from this article.