The use of technical indicators has never declined over time, but making a profit using them is uncertain due to one of their main drawbacks: revealing false trading signals. This drawback is more significant than you might think because you might tend to enter the market at the wrong time by following these signals and face heavy losses. However, this doesn’t mean technical indicators are obsolete. We just have to modify how we use them. One of the best measures to reduce the number of false signals is by adding another indicator to the trading strategy that is contrary in nature to the other indicator.

In this article, we will use two famous technical indicators: the Average Directional Index (ADX) and the Relative Strength Index (RSI) to build a trading strategy. We will test this strategy on real-world market data using Python to determine whether it generates a substantial profit. So, without further ado, let’s dive into the article!

ADX is a technical indicator widely used to measure the strength of a market trend. Unlike measuring the direction of the trend, whether it’s bullish or bearish, ADX represents how strong the trend is. To identify the direction of the trend, ADX is combined with a Positive Directional Index (+DI) and a Negative Directional Index (-DI). +DI measures the bullish or positive trend, while -DI measures the bearish or negative trend. All these components’ values are bound between 0 and 100, acting as an oscillator. The traditional ADX setting is 14, as the lookback period.

The values of +DM (+ Directional Movement) and -DM (- Directional Movement) are determined. +DM is calculated by finding the difference between the current high and the previous high, similarly, -DM is calculated by finding the difference between the previous low and the current low. It can be represented as follows:

`+DM = CURRENT HIGH - PREVIOUS HIGH-DM = PREVIOUS LOW - CURRENT LOW`

Then, an ATR (Average True Range) with a lookback period of 14 is calculated. Now, using the calculated directional movement and the ATR values, +DI and -DI are calculated. To determine +DI, the Exponential Moving Average (EMA) of +DM with a 14-day lookback period is divided by the 14-day ATR, then multiplied by 100. The same applies to determining -DI, but using the Negative Directional Movement (-DM) instead. The formulas can be represented as follows:

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

The Directional Index (DI) is calculated using +DI and -DI values. It’s determined by dividing the absolute value of the difference between +DI and -DI by the absolute value of the sum of +DI and -DI, then multiplying by 100. The formula can be represented as follows:

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

Finally, ADX is calculated using the DI values. ADX is calculated by multiplying the previous DI value by 13 (lookback period -1), adding it to the current DI, then multiplying by 100. The formula can be represented as follows:

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

ADX cannot be used as it is; it needs to be smoothed with a customized moving average created by the founder of the indicator, Welles Wilder himself. That’s the process of calculating ADX. Now, let’s analyze a chart where Apple’s stock prices are plotted along with its ADX 14 reading.

The above chart is divided into two panels: the upper panel with Apple’s closing prices and the lower panel with the components of ADX. Along with the components, a grey dashed line is plotted, representing the ADX threshold at a level of 35. As mentioned before, ADX doesn’t track the trend’s direction but indicates its strength. This can be seen several times in the chart, where the ADX line increases during strong trends and decreases during consolidation. This pattern holds for both the directional index lines too. The +DI line increases during strong uptrends and decreases during downtrends, and vice versa for the -DI line.

ADX serves not only to gauge the robustness of a market trend but also acts as a valuable instrument for recognizing range-bound markets. Such markets feature stock prices oscillating between distinct high and low thresholds, indicating minimal momentum. The proximity of the ADX lines suggests a range-bound market, while a wider gap between the lines signifies stronger market trends. For newcomers to the ADX chart, the movement of each line might initially lead to confusion, as it moves inversely in relation to the market’s behavior.

# Relative Strength Index (RSI)

Before moving on, let’s first establish a clear grasp of what an Oscillator entails within the realm of stock trading. An oscillator serves as a technical tool that constructs a trend-based indicator, defining its values within a specified range from high to low. Traders utilize these established ranges in conjunction with the trend-based indicator to discern the prevailing market condition and formulate potential buy and sell decisions. Importantly, oscillators find wide application in short-term trading, though there are no constraints on their use for long-term investments.

The Relative Strength Index (RSI), founded and developed by J. Welles Wilder (also the creator of ADX) in 1978, functions as a momentum oscillator employed by traders to determine whether the market finds itself in a state of overbought or oversold. Before we delve further, let’s delve into the concepts of overbought and oversold. A market enters an overbought state when traders consistently buy an asset, propelling it into a highly bullish trend that inevitably leads to consolidation. Conversely, a market is deemed oversold when traders persistently sell an asset, pushing it into a bearish trend that tends to rebound.

Given its nature as an oscillator, RSI values are confined within the 0 to 100 range. The conventional approach to interpreting market conditions using RSI involves considering an RSI reading of 70 or higher as indicative of an overbought state. Similarly, an RSI reading of 30 or lower suggests that the market is in an oversold state. These overbought and oversold thresholds can also be customized based on the specific stock or asset under consideration. For instance, certain assets may consistently exhibit RSI readings of 80 and 20. In such cases, the overbought and oversold levels can be adjusted to 80 and 20 respectively. The standard RSI setting employs a lookback period of 14.

While RSI might appear somewhat analogous to the Stochastic Oscillator in terms of value interpretation, the methodology behind its calculation differs significantly. Calculating RSI involves three key steps:

• Calculating the Exponential Moving Average (EMA) of the gain and loss of an asset: Before moving forward, it’s important to grasp the concept of the Exponential Moving Average (EMA). EMA is a variant of the Moving Average (MA) that assigns greater weight to recent data points and diminishing weight to those further in the past. In this stage, the returns of the asset are computed, distinguishing between gains and losses. These distinct values are then used to compute two EMAs for a specified number of periods.
• Calculating the Relative Strength of an asset: The Relative Strength of an asset is determined by dividing the Exponential Moving Average of the asset’s gains by the Exponential Moving Average of its losses over a designated number of periods. Mathematically, this can be expressed as:
```RS = GAIN EMA / LOSS EMA

where,
RS = Relative Strength
GAIN EMA = Exponential Moving Average of the gains
LOSS EMA = Exponential Moving Average of the losses```
• Calculating the RSI values: In this phase, the RSI itself is calculated using the previously derived Relative Strength values. To compute the RSI values for a given asset over a specified number of periods, a formula is applied:
```RSI = 100.0 - (100.0 / (1.0 + RS))

where,
RSI = Relative Strength Index
RS = Relative Strength```

This completes the entire process of RSI calculation. Similar to how we analyzed an ADX chart to build a solid understanding of the indicator, we will adopt the same approach to comprehend the RSI.

The provided chart is divided into two panels: The upper panel displays Apple’s closing price, while the lower panel presents the calculated RSI 14 values for Apple. Upon scrutinizing the RSI panel, it becomes evident that the movement of the calculated values mirrors that of Apple’s closing price. Therefore, it can be inferred that RSI serves as a directional indicator. It’s worth noting that certain indicators are non-directional, causing their movement to be inversely proportional to the actual stock movement. This characteristic can sometimes perplex traders and pose challenges in comprehension.

Observing the RSI chart, a notable trend emerges: the RSI plot anticipates trend reversals even before they manifest in the market. Put simply, RSI signals a downtrend or uptrend ahead of the actual market movement. This characteristic underscores RSI’s role as a leading indicator. Leading indicators essentially consider the present value of a data series to predict future movements. As a leading indicator, RSI effectively forewarns traders about potential trend shifts in advance. On the flip side, there are lagging indicators, which rely on historical data series values to represent the present situation.

`ADX > 35 AND RSI < 50 AND +DI < -DI ==> BUY SIGNALADX > 35 AND RSI > 50 AND +DI > -DI ==> SELL SIGNAL`

With this, we conclude the theoretical segment and transition to the practical implementation using Python. We will build the indicators from scratch, construct the discussed trading strategy, backtest it using historical Apple stock data, and ultimately compare the outcomes with those of the SPY ETF. So, let’s roll up our sleeves and get into the coding! But before we proceed, it’s important to note that this article serves an educational purpose and should not be misconstrued as investment advice.

# Implementation in Python

The coding phase is organized into several steps:

```1. Importing Packages
2. Extracting Stock Data using EODHD
4. RSI Calculation
6. Creating our Position
7. Backtesting
8. SPY ETF Comparison```

We will adhere to the order listed above and buckle up your seat belts to follow every upcoming coding part.

## Step-1: Importing Packages

Importing necessary packages into the Python environment is a fundamental step. Primary packages include Pandas for data manipulation, NumPy for array operations and complex functions, Matplotlib for visualization, and Requests for API calls. Additionally, secondary packages like Math for mathematical operations and Termcolor for font customization (optional) are employed.

Python Implementation:

```# IMPORTING PACKAGES

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import requests
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 Stock Data using 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):
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', '2010-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 2010 and store it in the ‘aapl’ variable.

In this stage, we will perform the ADX calculation using the method we previously discussed.

Python Implementation:

```# ADX CALCULATION

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

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

Output:

Code Explanation: We start by defining the ‘get_adx’ function, which accepts the stock’s high (‘high’), low (‘low’), close data (‘close’), and a lookback period (‘lookback’) as parameters.

Within the function, our initial step involves computing and storing the positive directional movement (+DM) and negative directional movement (-DM), which are respectively assigned to the ‘plus_dm’ and ‘minus_dm’ variables. Subsequently, we proceed with theATR) calculation. Initially, we determine the three distinct differences and establish the variable ‘tr’ to retain the maximum values among these computed differences. These highest values are then employed to calculate and retain the ATR values, which are stored within the ‘atr’ variable.

Leveraging the computed directional movements and ATR values, we proceed to calculate the positive directional index (+DI) and negative directional index (-DI), which we respectively store as ‘plus_di’ and ‘minus_di’. Drawing upon the previously discussed formula, we proceed to calculate the Directional Index values (DX). This calculated DX is then integrated into the ADX formula to facilitate the derivation of the Average Directional Index (ADX) values. Subsequently, a variable named ‘adx_smooth’ is employed to house the smoothed values of ADX.

In conclusion, the function delivers the +DI, -DI, and ADX values, each obtained utilizing a 14-day lookback period for Apple stock.

## Step-4: RSI Calculation

In this section, we will compute the RSI values using a 14-day lookback period, applying the RSI formula mentioned earlier.

Python Implementation:

```# RSI CALCULATION

def get_rsi(close, lookback):
ret = close.diff()
up = []
down = []

for i in range(len(ret)):
if ret[i] < 0:
up.append(0)
down.append(ret[i])
else:
up.append(ret[i])
down.append(0)

up_series = pd.Series(up)
down_series = pd.Series(down).abs()

up_ewm = up_series.ewm(com = lookback - 1, adjust = False).mean()
down_ewm = down_series.ewm(com = lookback - 1, adjust = False).mean()

rs = up_ewm/down_ewm
rsi = 100 - (100 / (1 + rs))
rsi_df = pd.DataFrame(rsi).rename(columns = {0:'rsi'}).set_index(close.index)
rsi_df = rsi_df.dropna()

return rsi_df[3:]

aapl['rsi_14'] = get_rsi(aapl['close'], 14)
aapl = aapl.dropna()
aapl.tail()```

Output:

Code Explanation: To start, we define a function named ‘get_rsi’ that takes the closing price of a stock (‘close’) and the lookback period (‘lookback’) as parameters. Within the function, we first calculate the stock’s returns using the ‘diff’ function from the Pandas package, storing the result in the ‘ret’ variable. This function essentially computes the difference between the current value and the previous value. Subsequently, we employ a for-loop on the ‘ret’ variable to categorize gains and losses, appending the respective values to either the ‘up’ or ‘down’ variables.

Next, we calculate the Exponential Moving Averages (EMAs) for both ‘up’ and ‘down’ using the ‘ewm’ function from Pandas, storing these in the ‘up_ewm’ and ‘down_ewm’ variables respectively. Utilizing these calculated EMAs, we derive the Relative Strength by applying the formula discussed earlier, storing the result in the ‘rs’ variable.

By leveraging the calculated Relative Strength values, we compute the RSI values according to its formula. After performing some data processing and manipulations, we return the calculated Relative Strength Index values in the form of a Pandas dataframe.

Finally, we call the created function to obtain the RSI values for Apple with a 14-day lookback period.

## Step-5: Creating the Trading Strategy:

In this step, we will implement the previously discussed trading strategy that combines the Average Directional Index (ADX) and Relative Strength Index (RSI) using Python.

Python Implementation:

```# TRADING STRATEGY

sell_price = []
signal = 0

for i in range(len(prices)):
if adx[i] > 35 and pdi[i] < ndi[i] and rsi[i] < 50:
if signal != 1:
sell_price.append(np.nan)
signal = 1
else:
sell_price.append(np.nan)

elif adx[i] > 35 and pdi[i] > ndi[i] and rsi[i] > 50:
if signal != -1:
sell_price.append(prices[i])
signal = -1
else:
sell_price.append(np.nan)
else:
sell_price.append(np.nan)

Inside the function, we initialize three empty lists (‘buy_prices’, ‘sell_prices’, and ‘adx_rsi_signals’) to which values will be appended while constructing the trading strategy.

Subsequently, we implement the trading strategy using a for-loop. Within this loop, specific conditions are checked, and if met, corresponding values are added to the empty lists. When the conditions favor buying a stock, the buying price is appended to the ‘buy_prices’ list, and a signal value of 1 is appended to signify a buy signal. Similarly, if the conditions support selling a stock, the selling price is added to the ‘sell_prices’ list, and a signal value of -1 is appended to indicate a sell signal. Lastly, we return the lists with their appended values. We then call the created function and store the values in their respective variables.

## Step-6: Creating our Position

In this phase, we will create a list indicating whether we hold the stock (1) or do not own it (0).

Python Implementation:

```# POSITION

position = []
position.append(0)
else:
position.append(1)

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

pdi = aapl['plus_di']
ndi = aapl['minus_di']
rsi = aapl['rsi_14']
close_price = aapl['close']

strategy = pd.concat(frames, join = 'inner', axis = 1)```

Output:

Code Explanation: To begin, we create an empty list named ‘position’. We employ two nested for-loops. The first loop is used to generate values for the ‘position’ list, aligning its length with that of the ‘signal’ list. The second loop generates actual position values.

Within the second loop, we iterate over the values of the ‘signal’ list. The ‘position’ list is updated based on the fulfilled conditions. The position remains 1 if we hold the stock, and 0 if we sold the stock or do not own it. Finally, we perform some data manipulations to combine all the created lists into a single dataframe.

From the displayed output, it is evident that for the first two rows, our position in the stock remains at 1 (as there is no change in the trading signal). However, the position shifts to 0 when we sell the stock upon receiving a buy signal (-1) from the trading strategy. Our position will remain -1 until the trading signal changes. Now, let’s proceed to implement backtesting processes!

## Step-7: Backtesting

Before proceeding, it’s important to understand the concept of backtesting. Backtesting involves evaluating how well a trading strategy has performed using historical stock data. In our case, we will implement a backtesting process for our combined ADX and RSI trading strategy using Apple stock data.

Python Implementation:

```# BACKTESTING

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

for i in range(len(aapl_ret)):

investment_value = 100000

number_of_stocks = floor(investment_value/aapl['close'][i])

profit_percentage = floor((total_investment_ret/investment_value)*100)
print(cl('Profit gained from the ADX RSI strategy by investing \$100k in AAPL : {}'.format(total_investment_ret), attrs = ['bold']))
print(cl('Profit percentage of the ADX RSI strategy : {}%'.format(profit_percentage), attrs = ['bold']))```

Output:

```Profit gained from the ADX RSI strategy by investing \$100k in AAPL : 198395.05
Profit percentage of the ADX RSI strategy : 198%```

Code Explanation: To begin, we calculate the returns of Apple stock using the ‘diff’ function from the NumPy package, storing the result as a dataframe in the ‘aapl_ret’ variable. Next, we employ a for-loop to iterate over the values of ‘aapl_ret’, calculating the returns obtained from our RVI trading strategy. These return values are added to the ‘adx_rsi_strategy_ret’ list. Subsequently, we convert the ‘adx_rsi_strategy_ret’ list into a dataframe, storing it in the ‘adx_rsi_strategy_ret_df’ variable.

The subsequent section involves the actual backtesting process. We backtest our strategy by investing \$100,000 in it. The investment amount is stored in the ‘investment_value’ variable. Then, we calculate the number of Apple stocks that can be purchased using this investment amount. It’s worth noting that the ‘floor’ function from the Math package is used, 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. A for-loop is employed to calculate investment returns, followed by additional data manipulation tasks.

Finally, the total return from investing \$100,000 in our trading strategy is printed. The result reveals an approximate profit of \$200,000 over a span of about thirteen and a half years, equating to a profit percentage of 198%. Impressive! Now, let’s compare these returns with the returns from the SPY ETF (an ETF designed to track the S&P 500 stock market index).

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

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

Output:

```Benchmark profit by investing \$100k : 159541.51
Benchmark Profit percentage : 159%
ADX RSI Strategy profit is 39% higher than the Benchmark Profit```

Code Explanation: The code utilized in this step closely resembles the code from the previous backtesting step. However, instead of investing in Apple stock, we invest in the SPY ETF without implementing any trading strategies. The output clearly indicates that our trading strategy has outperformed the SPY ETF by an impressive 39%. This is indeed a remarkable achievement!

# Final Thoughts!

Following an extensive exploration of both theoretical concepts and coding implementation, we have successfully comprehended the Average Directional Index (ADX) and Relative Strength Index (RSI) and integrated these two indicators to construct a profitable trading strategy.

Now, let’s discuss potential improvements. While there is ample room for enhancement in this article, one crucial aspect is evaluating the trading strategy using relevant metrics. This step is essential prior to implementing the strategy in the real-world market. Thorough evaluation provides clearer insights into performance beyond mere profitability.

The reason for not delving into this aspect within this article is that strategy evaluation warrants an entire section of its own. It cannot be adequately covered in a brief segment. With this in mind, you’ve reached the conclusion of the article. I hope you’ve gained new and valuable insights from this article.