top of page
  • Nikhil Adithyan

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 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 out 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 do implement some backtesting process!


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
number_of_stocks = floor(investment_value/aapl['close'][0])
cc_investment_ret = []

for i in range(len(cc_strategy_ret_df['cc_returns'])):
    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 : 60850.26
Profit percentage of the CC strategy : 60%

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 into 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 stored it into 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 sixty thousand USD in one year. 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


This step is optional but it is highly recommended as we can get an idea of how well our trading strategy performs against a benchmark (SPY ETF). In this step, we will extract the SPY ETF data using the ‘get_historical_data’ function we created and compare the returns we get from the SPY ETF with our Coppock Curve tuned zero-line crossover trading strategy returns on Apple.


You might have observed that in all of my algorithmic trading articles, I’ve compared the strategy results not with the S&P 500 market index itself but with the SPY ETF and this is because most of the stock data providers (like Twelve Data) don’t provide the S&P 500 index data. So, I have no other choice than to go with the SPY ETF. If you’re fortunate to get the S&P 500 market index data, it is recommended to use it for comparison rather than any ETF.


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'})
    
    investment_value = investment_value
    number_of_stocks = floor(investment_value/spy[-1])
    benchmark_investment_ret = []
    
    for i in range(len(benchmark['benchmark_returns'])):
        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 : 22929.75
Benchmark Profit percentage : 22%
CC Strategy profit is 38% 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 22%. 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 which I do 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 randomly 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! If you forgot to follow any of the coding parts, don’t worry. I’ve provided the full source code at the end of the article. Hope you learned something new and useful from this article.


Full code:



# 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)


# 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()


# 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()


# COPPOCK CURVE 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.5)
ax1.set_title('AAPL CLOSING PRICES')
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()


# 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'])


# 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()


# 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
strategy[10:15]


# 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
number_of_stocks = floor(investment_value/aapl['close'][0])
cc_investment_ret = []

for i in range(len(cc_strategy_ret_df['cc_returns'])):
    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']))


# 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'})
    
    investment_value = investment_value
    number_of_stocks = floor(investment_value/spy[-1])
    benchmark_investment_ret = []
    
    for i in range(len(benchmark['benchmark_returns'])):
        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']))

 

3 comments
bottom of page