Algorithmic Trading with ADX in Python

A guide on implementing the Average Directional Index trading strategy, including backtesting and comparison with the SPY ETF.

EODHD APIs
18 min readSep 21, 2024

There are several branches of technical indicators, with trend indicators being among the most widely used. These tools assist traders in determining both the direction and strength of market trends, allowing them to align their trades accordingly. Trend indicators typically yield positive results, provided they are applied effectively.

In this article, we will take a closer look at one of the most prominent trend indicators — the Average Directional Index (ADX). We’ll begin by building a foundational understanding of ADX, including its purpose and calculation. Afterward, we’ll develop the indicator from scratch and implement a trading strategy based on it in Python. To assess the effectiveness of our strategy, we will backtest it using Apple stock and compare its returns with those of the SPY ETF, which tracks the S&P 500 index.

Average True Range (ATR)

Before going deep into discovering the Average Directional Index (ADX), it’s essential to understand the Average True Range (ATR), as it plays a key role in calculating the ADX.

ATR is a technical indicator used to measure the volatility of an asset by determining how much it moves on average. It’s a lagging indicator, meaning it uses past price data to calculate current values but cannot predict future prices. However, this lag isn’t necessarily a disadvantage since ATR is designed to track market volatility more accurately. Additionally, ATR is non-directional, which means that its movement is independent of market direction. To calculate ATR, follow these steps:

  1. Calculate True Range (TR): The True Range is the greatest of three values:
  • Market high minus market low
  • Market high minus previous close
  • Previous close minus market low This can be represented as:
    TR = MAX[ {HIGH - LOW}, {HIGH - P.CLOSE}, {P.CLOSE - LOW} ]

Where:

  • MAX represents the maximum value
  • HIGH is the market’s high price
  • LOW is the market’s low price
  • P.CLOSE is the previous market close price

2. Calculate ATR: Once TR is calculated, ATR is determined by taking the smoothed average of TR values over a specified number of periods. While the original ATR calculation involves a custom smoothed average by Wilder Wiles (the indicator’s creator), other moving averages, such as SMA or EMA, can also be used. For simplicity, we’ll use a 14-period SMA for this article, with the calculation formula as follows:
ATR 14 = SMA 14 [ TR ]

Where:

  • ATR 14 is the 14-period Average True Range
  • SMA 14 is the 14-period Simple Moving Average
  • TR is the True Range

Though ATR is lagging, it remains highly useful for tracking market volatility. Now that we’ve covered ATR, let’s move on to the primary focus of this article: the Average Directional Index (ADX).

Average Directional Index (ADX)

The ADX is a widely-used technical indicator for measuring the strength of a market trend. While ADX doesn’t specify whether a trend is bullish or bearish, it indicates how strong that trend is. To determine the trend direction, ADX is paired with the Positive Directional Index (+DI) and Negative Directional Index (-DI). As the names imply, +DI measures the positive trend (bullish), while -DI measures the negative trend (bearish). ADX values typically range from 0 to 100, making it an oscillator. The traditional ADX setting uses a 14-period lookback.

To calculate ADX with a 14-period lookback, follow these steps:

  1. Determine Positive (+DM) and Negative Directional Movement (-DM):
  • +DM is calculated as the difference between the current high and the previous high
  • -DM is calculated as the difference between the previous low and the current low
    This can be represented as:

+DM = CURRENT HIGH - PREVIOUS HIGH

-DM = PREVIOUS LOW - CURRENT LOW

2. Calculate ATR: Calculate the 14-period ATR as described in the previous section.

3. Calculate +DI and -DI: To find +DI, divide the 14-period EMA of +DM by the 14-period ATR and multiply by 100. For -DI, use the 14-period EMA of -DM instead. The formulas are:

+DI 14 = 100 * [ EMA 14 ( +DM ) / ATR 14 ]

-DI 14 = 100 * [ EMA 14 ( -DM ) / ATR 14 ]

4. Determine the Directional Index (DI): The Directional Index is calculated by dividing the absolute difference between +DI and -DI by their sum, then multiplying by 100. The formula is:

DI 14 = | (+DI 14) - (-DI 14) | / | (+DI 14) + (-DI 14) | * 100

5. Calculate ADX: The ADX is calculated by taking a weighted average of the current DI value and the previous DI value. Specifically, the previous DI is multiplied by 13 (the lookback period minus 1) and added to the current DI. The formula is:

ADX 14 = [ ( PREV DI 14 * 13 ) + DI 14 ] / 14

Lastly, the ADX value is smoothed using the custom moving average designed by Wilder Wiles. This smoothing process ensures the ADX values are accurate and consistent for trend analysis.

Now that we’ve covered the ADX calculation, we can explore how to develop a trading strategy based on the ADX indicator.

About our trading strategy

In this article, we’ll build a straightforward crossover strategy. The strategy signals a buy whenever the ADX line crosses above 25 and the +DI line is above the -DI line. Conversely, a sell signal is triggered when the ADX line crosses above 25, but the -DI line is above the +DI line. The logic for our trading strategy can be expressed as:

IF P.ADX < 25 AND C.ADX > 25 AND + DI LINE > - DI LINE ==> BUY
IF P.ADX < 25 AND C.ADX > 25 AND + DI LINE < - DI LINE ==> SELL

This wraps up the theoretical portion. Now, we’ll move on to coding the ADX indicator from scratch in Python and create the trading strategy based on this indicator. We’ll then backtest it using Apple stock data and compare the strategy’s performance with the SPY ETF to assess how our ADX crossover strategy fares against a benchmark. Let’s dive into the coding.

Implementation in Python

We’ll break the coding into the following steps:

  1. Importing Packages
  2. API Key Activation
  3. Extracting Historical Stock Data
  4. ADX Calculation
  5. Plotting the ADX Indicator
  6. Implementing the Trading Strategy
  7. Plotting the Buy and Sell Signals
  8. Creating the Trading Position
  9. Backtesting
  10. Comparing with SPY ETF Returns

We will follow these steps to guide our implementation.

Step 1: Importing Packages

The first step is to import the necessary Python packages. We’ll primarily use eodhd for extracting historical stock data, Pandas for data manipulation, NumPy for handling arrays and performing mathematical operations, and Matplotlib for plotting charts. Additionally, we’ll use math for various calculations and termcolor for styling outputs (optional).

# IMPORTING PACKAGES

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

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

With the necessary packages imported, we are ready to fetch historical data for Apple using the eodhd library. If any of these packages are not installed, you can easily install them by running the pip install command in your terminal.

Step-2: API Key Activation

It is essential to register the EODHD API key with the package in order to use its functions. If you don’t have an EODHD API key, firstly, head over to their website, then, finish the registration process to create an EODHD account, and finally, navigate to the Dashboard where you could find your secret EODHD API key. It is important to ensure that this secret API key is not revealed to anyone. You can activate the API key by following this code:

api_key = '<YOUR API KEY>'
client = APIClient(api_key)

The code is pretty simple. In the first line, we are storing the secret EODHD API key into the api_key and then in the second line, we are using the APIClient class provided by the eodhd package to activate the API key and stored the response in the client variable.

Note that you need to replace <YOUR API KEY> with your secret EODHD API key. Apart from directly storing the API key with text, there are other ways for better security such as utilizing environmental variables, and so on.

Step 3: Extracting Historical Data

Before extracting data, it’s important to understand what historical or end-of-day (EOD) data is. This data captures past market activity and is vital for identifying trends, patterns, and overall market behavior. Historical data is essential for backtesting strategies and analyzing asset performance. Using the eodhd package, you can easily retrieve this data for any tradable asset by running the following code:

# EXTRACTING HISTORICAL DATA

def get_historical_data(ticker, start_date):
json_resp = client.get_eod_historical_stock_market_data(symbol = ticker, period = 'd', from_date = start_date, order = 'a')
df = pd.DataFrame(json_resp)
df = df.set_index('date')
df.index = pd.to_datetime(df.index)
return df

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

In this code, the function get_eod_historical_stock_market_data from the eodhd package is used to extract the split-adjusted historical stock data for Apple. Let’s break down the key parameters:

  • ticker: The stock symbol for which you want to fetch the data (in this case, AAPL for Apple).
  • period: Defines the time interval between each data point, which is set to a daily interval ('d').
  • from_date: Specifies the start date for the data extraction in the format "YYYY-MM-DD."
  • order: An optional parameter to arrange the data in ascending ('a') or descending ('d') order, based on dates.

Once the data is extracted, a few basic data-wrangling steps are applied to format it into a Pandas DataFrame. The resulting data looks like this:

Step 4: ADX Calculation

In this step, we’ll calculate the Average Directional Index (ADX) values by applying the method discussed earlier.

Python Implementation:

def get_adx(high, low, close, lookback):
plus_dm = high.diff()
minus_dm = low.diff()
plus_dm[plus_dm < 0] = 0
minus_dm[minus_dm > 0] = 0

tr1 = pd.DataFrame(high - low)
tr2 = pd.DataFrame(abs(high - close.shift(1)))
tr3 = pd.DataFrame(abs(low - close.shift(1)))
frames = [tr1, tr2, tr3]
tr = pd.concat(frames, axis = 1, join = 'inner').max(axis = 1)
atr = tr.rolling(lookback).mean()

plus_di = 100 * (plus_dm.ewm(alpha = 1/lookback).mean() / atr)
minus_di = abs(100 * (minus_dm.ewm(alpha = 1/lookback).mean() / atr))
dx = (abs(plus_di - minus_di) / abs(plus_di + minus_di)) * 100
adx = ((dx.shift(1) * (lookback - 1)) + dx) / lookback
adx_smooth = adx.ewm(alpha = 1/lookback).mean()
return plus_di, minus_di, adx_smooth

aapl['plus_di'] = pd.DataFrame(get_adx(aapl['high'], aapl['low'], aapl['close'], 14)[0]).rename(columns = {0:'plus_di'})
aapl['minus_di'] = pd.DataFrame(get_adx(aapl['high'], aapl['low'], aapl['close'], 14)[1]).rename(columns = {0:'minus_di'})
aapl['adx'] = pd.DataFrame(get_adx(aapl['high'], aapl['low'], aapl['close'], 14)[2]).rename(columns = {0:'adx'})
aapl = aapl.dropna()
aapl.tail()

Output:

Code Explanation:

We begin by defining a function called get_adx that takes four parameters: the stock’s high (high), low (low), and close (close) prices, along with the lookback period (lookback), which determines the number of periods to consider for calculations.

Step-by-step Breakdown:

  1. Directional Movements (+DM and -DM):
  • The first step inside the function is to compute the positive directional movement (+DM) and negative directional movement (-DM).
  • plus_dm calculates the difference between the current high and the previous high. Any negative values are set to zero.
  • minus_dm calculates the difference between the previous low and the current low. Any positive values are set to zero.

2. True Range (TR):

  • We calculate three price differences: current high minus low, absolute high minus the previous close, and absolute low minus the previous close.
  • The True Range (TR) is the maximum value among these three differences for each time period, and we store it in the tr variable.

3. Average True Range (ATR):

  • The ATR is computed as the rolling average of the True Range (tr) over the specified lookback period. We store this value in the atr variable.

4. Directional Indicators (+DI and -DI):

  • Using the calculated +DM, -DM, and ATR, we calculate the Positive Directional Indicator (+DI) and Negative Directional Indicator (-DI).
  • The calculation involves taking an Exponential Moving Average (EMA) of the directional movements divided by the ATR, and multiplying the result by 100. These values are stored in the plus_di and minus_di variables, respectively.

5. Directional Index (DX):

  • The Directional Index (DX) measures the difference between +DI and -DI.
  • DX is computed by taking the absolute difference between +DI and -DI, divided by their sum, and multiplied by 100. The DX values are stored in the dx variable.

6. Average Directional Index (ADX):

  • The ADX is calculated using a smoothing technique. The current DX is multiplied by the lookback period, and the previous ADX is multiplied by the previous period count. This smoothed ADX is stored in the adx_smooth variable.

7. Returning Results:

  • The function finally returns the calculated values of +DI, -DI, and the smoothed ADX.

8. Calling the Function:

  • We call the function with Apple’s stock data (AAPL) and a 14-period lookback to retrieve the calculated values of +DI, -DI, and ADX for further use.

This step-by-step approach ensures that the trend strength (ADX) and directional movements (+DI, -DI) are accurately calculated, which is essential for constructing a trading strategy based on trend strength.

Step-5: ADX Plot

In this step, we’ll visualize the calculated ADX values of Apple (AAPL) to better understand the trend strength and directional movement. The key objective here is to observe the plot for better insight into how the Average Directional Index (ADX), along with the Positive Directional Indicator (+DI) and Negative Directional Indicator (-DI), behave over time.

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(aapl['close'], linewidth = 2, color = '#ff9800')
ax1.set_title('AAPL CLOSING PRICE')
ax2.plot(aapl['plus_di'], color = '#26a69a', label = '+ DI 14', linewidth = 3, alpha = 0.3)
ax2.plot(aapl['minus_di'], color = '#f44336', label = '- DI 14', linewidth = 3, alpha = 0.3)
ax2.plot(aapl['adx'], color = '#2196f3', label = 'ADX 14', linewidth = 3)
ax2.axhline(25, color = 'grey', linewidth = 2, linestyle = '--')
ax2.legend()
ax2.set_title('AAPL ADX 14')
plt.show()

Output:

The chart above is divided into two sections: the top section displays Apple’s closing prices, while the bottom section shows the components of the ADX indicator. In the lower panel, along with the ADX, you’ll see a grey dashed line set at a level of 25, which serves as a threshold. As mentioned earlier, the ADX doesn’t measure trend direction but rather the strength of the trend. This is evident in the chart, as the ADX line rises when the market exhibits a strong trend (whether upward or downward) and falls during periods of market consolidation.

The same applies to the directional index (+DI and -DI) lines. The +DI line tends to rise during a solid uptrend and declines when the market moves downward. Conversely, the -DI line increases during a downtrend and decreases in an uptrend.

ADX isn’t just useful for gauging trend strength; it’s also an excellent tool for identifying ranging markets (when a stock is bouncing between a defined high and low level with no clear momentum). When the +DI and -DI lines draw closer together, the market is usually ranging. In contrast, the wider the gap between these lines, the more likely it is that the market is trending. For those unfamiliar with ADX charts, the inverse relationship between line movement and market direction may initially seem confusing.

Step-6: Creating our trading strategy

In this step, we implement the Average Directional Index (ADX) trading strategy using Python.

Python Implementation:

We begin by defining the function implement_adx_strategy, which takes four parameters: stock prices (prices), Positive Directional Index (pdi), Negative Directional Index (ndi), and the Average Directional Index (adx).

def implement_adx_strategy(prices, pdi, ndi, adx):
buy_price = []
sell_price = []
adx_signal = []
signal = 0

for i in range(len(prices)):
if adx[i-1] < 25 and adx[i] > 25 and pdi[i] > ndi[i]:
if signal != 1:
buy_price.append(prices[i])
sell_price.append(np.nan)
signal = 1
adx_signal.append(signal)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
adx_signal.append(0)
elif adx[i-1] < 25 and adx[i] > 25 and ndi[i] > pdi[i]:
if signal != -1:
buy_price.append(np.nan)
sell_price.append(prices[i])
signal = -1
adx_signal.append(signal)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
adx_signal.append(0)
else:
buy_price.append(np.nan)
sell_price.append(np.nan)
adx_signal.append(0)

return buy_price, sell_price, adx_signal

buy_price, sell_price, adx_signal = implement_adx_strategy(aapl['close'], aapl['plus_di'], aapl['minus_di'], aapl['adx'])

Code explanation:

  1. Initialization: We start by initializing three empty lists: buy_price, sell_price, and adx_signal. These will store the respective values based on the trading signals generated.
  2. Looping through the Data: We then loop through the length of the price data to generate the buy and sell signals based on conditions involving the ADX, +DI, and -DI.

3. Buy Signal: A buy signal is triggered when the ADX crosses above 25 (i.e., moving from below to above 25) and the +DI is greater than -DI. If this condition is met, the stock price is appended to the buy_price list, and the signal is marked as 1.

4. Sell Signal: A sell signal is generated when the ADX crosses above 25, but the -DI is greater than the +DI. In this case, the stock price is appended to the sell_price list, and the signal is marked as -1.

5. No Signal: If neither condition is met, both the buy_price and sell_price lists are filled with NaN, and no trading action is taken (signal is 0).

6. Returning the Values: After processing the data, the function returns the generated buy_price, sell_price, and adx_signal lists.

7. Calling the Function: The function is then called with the historical data of Apple’s stock, and the results are stored in the respective variables (buy_price, sell_price, adx_signal).

This implementation of the ADX-based trading strategy allows us to generate buy and sell signals based on the strength of the trend, as indicated by the ADX and directional indices. In the next step, we can visualize these signals to better understand their effectiveness in different market conditions.

Step-7: Plotting the trading signals

In this step, we’re visualizing the generated trading signals along with the stock price and ADX values for Apple. The plot will help us understand the points where the buy and sell signals were triggered, as well as the overall strength of the market trend.

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(aapl['close'], linewidth = 3, color = '#ff9800', alpha = 0.6)
ax1.set_title('AAPL CLOSING PRICE')
ax1.plot(aapl.index, buy_price, marker = '^', color = '#26a69a', markersize = 14, linewidth = 0, label = 'BUY SIGNAL')
ax1.plot(aapl.index, sell_price, marker = 'v', color = '#f44336', markersize = 14, linewidth = 0, label = 'SELL SIGNAL')
ax2.plot(aapl['plus_di'], color = '#26a69a', label = '+ DI 14', linewidth = 3, alpha = 0.3)
ax2.plot(aapl['minus_di'], color = '#f44336', label = '- DI 14', linewidth = 3, alpha = 0.3)
ax2.plot(aapl['adx'], color = '#2196f3', label = 'ADX 14', linewidth = 3)
ax2.axhline(25, color = 'grey', linewidth = 2, linestyle = '--')
ax2.legend()
ax2.set_title('AAPL ADX 14')
plt.show()

Output:

Code Explanation:

We are plotting the components of the Average Directional Index (ADX) alongside the buy and sell signals generated by our trading strategy. The plot allows us to visually inspect how the strategy performs in relation to the ADX, +DI, and -DI indicators.

  • Buy signals (represented by green upward triangles) appear when the ADX crosses above 25, and the +DI line is above the -DI line. This indicates a strong bullish trend.
  • Sell signals (represented by red downward triangles) are plotted when the ADX crosses above 25, and the +DI line is below the -DI line, signaling a strong bearish trend.

This visualization helps confirm the accuracy of the trading strategy by clearly identifying the moments when the ADX signals a trending market and corresponding buy or sell actions are taken.

Step-8: Creating our Position

In this step, we’re creating a position list that indicates whether we are holding the stock (represented by 1) or not (represented by 0), based on the buy and sell signals generated by the ADX strategy.

Python Implementation:

position = []
for i in range(len(adx_signal)):
if adx_signal[i] > 1:
position.append(0)
else:
position.append(1)

for i in range(len(aapl['close'])):
if adx_signal[i] == 1:
position[i] = 1
elif adx_signal[i] == -1:
position[i] = 0
else:
position[i] = position[i-1]

close_price = aapl['close']
plus_di = aapl['plus_di']
minus_di = aapl['minus_di']
adx = aapl['adx']
adx_signal = pd.DataFrame(adx_signal).rename(columns = {0:'adx_signal'}).set_index(aapl.index)
position = pd.DataFrame(position).rename(columns = {0:'adx_position'}).set_index(aapl.index)

frames = [close_price, plus_di, minus_di, adx, adx_signal, position]
strategy = pd.concat(frames, join = 'inner', axis = 1)

strategy

Output:

Code Explanation:

We begin by creating an empty list called position. This list will eventually indicate whether we are holding the stock (1) or not holding it (0).

  • In the first for-loop, we initialize the position list with placeholder values to match the length of the adx_signal list.
  • In the second for-loop, we iterate over the values of the adx_signal list:
  • If the signal indicates a “buy” (1), we append 1 to the position list, indicating that we are holding the stock.
  • If the signal indicates a “sell” (-1), we append 0 to the position list, indicating that we are not holding the stock.
  • If the signal remains neutral (0), the position stays the same as the previous value.

Finally, we perform some data manipulations to merge the position, adx_signal, and other values (such as the closing price, +DI, -DI, and ADX values) into a single dataframe named strategy. This dataframe helps us track the state of our positions and the trading signals.

From the output, you can observe that the position in the first row remains at 1, indicating we are holding the stock. However, once the adx_signal generates a sell signal (-1), the position changes to 0, indicating the stock was sold. The position will stay at 0 until the signal changes again.

Now that we’ve set up our trading strategy, we’re ready to move on to the backtesting process!

Step-9: Backtesting

Before we dive into the code, let’s first understand what backtesting entails. Backtesting is a method used by traders and analysts to evaluate how a particular trading strategy would have performed on historical data. By applying a strategy to past stock price data, we can observe how effective it might have been, allowing us to gain insights and refine the strategy accordingly.

In this step, we will implement a backtesting process for our Average Directional Index (ADX) trading strategy using the historical stock data of Apple (AAPL). This will help us evaluate how well our strategy would have performed over the selected time period. Let’s move forward with the implementation of this process.

Python Implementation:

aapl_ret = pd.DataFrame(np.diff(aapl['close'])).rename(columns = {0:'returns'})
adx_strategy_ret = []

for i in range(len(aapl_ret)):
returns = aapl_ret['returns'][i]*strategy['adx_position'][i]
adx_strategy_ret.append(returns)

adx_strategy_ret_df = pd.DataFrame(adx_strategy_ret).rename(columns = {0:'adx_returns'})
investment_value = 100000
adx_investment_ret = []

for i in range(len(adx_strategy_ret_df['adx_returns'])):
number_of_stocks = floor(investment_value/aapl['close'][i])
returns = number_of_stocks*adx_strategy_ret_df['adx_returns'][i]
adx_investment_ret.append(returns)

adx_investment_ret_df = pd.DataFrame(adx_investment_ret).rename(columns = {0:'investment_returns'})
total_investment_ret = round(sum(adx_investment_ret_df['investment_returns']), 2)
profit_percentage = floor((total_investment_ret/investment_value)*100)
print(cl('Profit gained from the ADX strategy by investing $100k in AAPL : {}'.format(total_investment_ret), attrs = ['bold']))
print(cl('Profit percentage of the ADX strategy : {}%'.format(profit_percentage), attrs = ['bold']))

Output:

Profit gained from the ADX strategy by investing $100k in AAPL : 54719.46
Profit percentage of the ADX strategy : 54%

Code Explanation:

  1. Stock Return Calculation:
    We begin by calculating Apple’s stock returns with the diff function from NumPy, storing the results in the aapl_ret dataframe.
  2. Strategy Returns Calculation:
    A for-loop is employed to iterate over the aapl_ret values, calculating the returns generated by our ADX trading strategy. These returns are collected in the adx_strategy_ret list, which is later converted into a dataframe and stored as adx_strategy_ret_df.
  3. Investment Setup:
    For backtesting, we simulate an investment of $100,000 in our ADX strategy. The amount is saved in the investment_value variable.
  4. Number of Stocks Calculation:
    The number of Apple stocks that can be purchased with the investment is calculated by dividing the investment amount by the closing price. To ensure an integer output, we use the floor function from the Math package, which eliminates decimal values, unlike the round function.
  5. Investment Returns Calculation:
    Another for-loop is used to compute the returns from this investment, followed by formatting and organizing the data into the relevant structures.
  6. Final Return Output:
    We print the total return from our $100,000 investment, revealing that over three years, the strategy yielded a profit of approximately $54,000.

Next, we proceed to compare these returns with the SPY ETF returns, which track the S&P 500.

Step-10: SPY ETF Comparison

This step is optional but highly recommended as it provides insights into how well our trading strategy performs against a benchmark, in this case, the SPY ETF. Here, we’ll retrieve the SPY ETF data using the ‘get_historical_data’ function we created and compare its returns with those from our Average Directional Index strategy applied to Apple.

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
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('ADX Strategy profit is {}% higher than the Benchmark Profit'.format(profit_percentage - benchmark_profit_percentage), attrs = ['bold']))

Output:

Benchmark profit by investing $100k : 37538.96  
Benchmark Profit percentage : 37%
ADX Strategy profit is 17% higher than the Benchmark Profit

Code Explanation:
This code is similar to the previous backtesting step, but instead of investing in Apple stock, we are now investing in the SPY ETF without applying any specific trading strategy. From the output, it’s clear that our Average Directional Index (ADX) trading strategy outperformed the SPY ETF by 17%. That’s a positive result!

Conclusion

After walking through both the theoretical and coding aspects, we’ve gained a solid understanding of the Average Directional Index (ADX) and how to build a basic ADX-based trading strategy using Python.

In my view, the true potential of ADX is revealed when combined with other technical indicators, such as the RSI, to fine-tune entry and exit points in trades. Therefore, I encourage you to take this article further by tweaking the ADX strategy, integrating it with other indicators, and backtesting it extensively. Doing so can help achieve more refined results in real-world trading scenarios.

That’s all for now! I hope you found this article insightful and helpful.

Stay tuned for more valuable articles that aim to enhance your data science skills and market analysis capabilities.

The original article was published in the EODHD Academy by Nikhil Adithyan.

Please note that this article is for informational purposes only and should not be taken as financial advice. We do not bear responsibility for any trading decisions made based on the content of this article. Readers are advised to conduct their research or consult with a qualified financial professional before making any investment decisions.

For those eager to delve deeper into such insightful articles and broaden their understanding of different strategies in financial markets, we invite you to follow our account and subscribe for email notifications.

--

--

EODHD APIs
EODHD APIs

Written by EODHD APIs

eodhd.com — stock market fundamental and historical prices API for stocks, ETFs, mutual funds and bonds all over the world.

Responses (1)