Backtesting-03 (Technical Indicators in Python)

Gaurav Kumar
14 min readMar 5, 2024

--

In the context of financial markets and trading, a technical indicator is a mathematical calculation based on historical price, volume, or other market-related data. These indicators are used by traders and analysts to gain insights into potential future price movements, identify trends, and make informed trading decisions.

Python provides various libraries that allow you to easily calculate and visualize technical indicators. Some popular libraries include pandas for data manipulation and analysis, and ta (technical analysis library) for calculating technical indicators.

Why Indicators?

Indicators, particularly in the context of financial markets and trading, serve several purposes. They are mathematical calculations based on historical price, volume, or other market-related data that provide insights into market trends, momentum, volatility, and potential price reversals. Traders and analysts use indicators to make more informed decisions about buying, selling, or holding financial assets. There are broadly two types of analysis. Fundamental and Technical Analysis. Fundamental Analysis is more on Qualitative analysis. In this article we’ll be basically focusing on the Technical Analysis. Here are some reasons why indicators are widely used:

  1. Trend Identification: Indicators help identify the direction of the prevailing market trend, whether it’s upward, downward, or sideways. Trend-following indicators, such as moving averages, are commonly used for this purpose.
  2. Momentum Measurement: Indicators like the Relative Strength Index (RSI) and Moving Average Convergence Divergence (MACD) help measure the momentum of price movements. This information can be used to identify overbought or oversold conditions.
  3. Volatility Assessment: Some indicators, like Bollinger Bands and Average True Range (ATR), provide insights into market volatility. This information is valuable for adjusting risk management strategies.
  4. Signal Generation: Indicators often generate signals or crossovers that traders interpret as buy or sell signals. For example, a moving average crossover (e.g., when a short-term moving average crosses above a long-term moving average) is considered a potential buy signal.
  5. Confirmation of Price Patterns: Traders use indicators to confirm or validate price patterns observed on charts. For instance, a trendline breakout combined with strong momentum as indicated by an RSI surge might be considered a more robust signal.
  6. Divergence Detection: Divergence occurs when the price of an asset moves in the opposite direction to an indicator. Divergence can signal potential changes in the current trend and is closely watched by traders.
  7. Risk Management: Indicators are crucial for risk management. Volatility indicators help traders set stop-loss levels and determine position sizes to manage risk effectively.
  8. Quantitative Analysis: In algorithmic and quantitative trading, indicators are often used as part of automated trading strategies. These strategies rely on predefined rules based on indicator signals to execute trades.
  9. Decision Support: Indicators provide additional information that traders can use to support their decision-making process. They help traders form a comprehensive view of market conditions.

It’s important to note that while indicators are valuable tools, they should be used alongside other forms of analysis, such as fundamental analysis and market sentiment, for a well-rounded approach to trading and investing. Additionally, no indicator is foolproof, and market conditions can change, so it’s essential to use indicators in conjunction with risk management strategies.

Using Pandas and TA for Calculating and Plotting SMA and RSI (Example)

In the entire course of this article, we will be using the ta module to generate indicator values. However, this is not mandatory, you can write your own indicator formula and/or use some other library. Completely your choice.

To use the ‘ta’ library, use:

pip install ta
import yfinance as yf
import pandas as pd
# Define the stock symbol and time period
stock_symbol = 'RELIANCE.BO' # Adjust for the appropriate stock exchange if needed
start_date = '2014-01-01'
end_date = '2024-01-01'
# Download historical data (OHLCV)
df = yf.download(stock_symbol, start=start_date, end=end_date)
# Calculate SMA(10)
df['SMA_10'] = df['Close'].rolling(window=10).mean()
# Display the DataFrame
print(df.head(15))
import pandas as pd
import ta
import matplotlib.pyplot as plt
# Assume df is a DataFrame with price data
df['SMA_10'] = ta.trend.sma_indicator(df['Close'], window=10)
df['RSI_14'] = ta.momentum.RSIIndicator(df['Close'], window=14).rsi()
# Plotting
plt.figure(figsize=(10, 6))
plt.plot(df['Close'], label='Close Price')
plt.plot(df['SMA_10'], label='SMA 10')
plt.legend()
plt.show()

Common Types of Technical Indicators and Brief Explanations

  1. Overlap Indicators
  2. Momentum Indicators
  3. Volume indicators
  4. Trend Indicators
  5. Volatility Indicators
  6. Miscellaneous

Going forward, we are going to see a couple of indicators from each category and a very crude way of using it. Note that the values of the parameters are just for the sake of showcasing how to use and not any reference or any proven benchmark. You’ll need to optimize the values in such a way that you don’t overfit, which we will be covering in the upcoming articles.

Overlap Studies

1. Bollinger Bands:

1. %B (Percent B): Represents where the last price is in relation to the upper and lower Bollinger Bands. It helps identify overbought or oversold conditions.

2. Bandwidth: Measures the width of the Bollinger Bands over time, indicating periods of high or low volatility.

Entry Signal:
When the price touches or crosses below the lower Bollinger Band, it may generate a buy signal.
Exit Signal:
When the price touches or crosses above the upper Bollinger Band, it may generate a sell signal.

# Entry signal
df['Lower_Band'], df['Upper_Band'] = ta.volatility.bollinger_lband(df['Close']), ta.volatility.bollinger_hband(df['Close'])
df['Signal'] = 0
df.loc[df['Close'] < df['Lower_Band'], 'Signal'] = 1 # Buy signal
# Exit signal
df.loc[df['Close'] > df['Upper_Band'], 'Signal'] = -1 # Sell signal

# Plotting
plt.figure(figsize=(12, 6))
plt.plot(df['Close'], label='Close Price', linewidth=2)
plt.plot(df['Lower_Band'], label='Lower Bollinger Band', linestyle='--', color='red')
plt.plot(df['Upper_Band'], label='Upper Bollinger Band', linestyle='--', color='green')
plt.plot(df[df['Signal'] == 1].index, df['Close'][df['Signal'] == 1], '^', markersize=10, color='g', label='Buy Signal')
plt.plot(df[df['Signal'] == -1].index, df['Close'][df['Signal'] == -1], 'v', markersize=10, color='r', label='Sell Signal')
plt.title(f'{stock_symbol} Bollinger Bands and Signals')
plt.xlabel('Date')
plt.ylabel('Price')
plt.legend()
plt.show()

EMAIndicator:

Exponential Moving Average (EMA): Gives more weight to recent prices, making it more responsive to current market conditions.

  • Exponential Moving Average (EMA) calculates a weighted average of prices.
  • Provides a smoother trend representation compared to simple moving averages.
Parameters:
1. close: Data from the 'Close' column.
2. window: The number of periods (n) for EMA calculation.
3. fillna: Fills NaN values if True.
Method:
1. ema_indicator(): Generates a new feature - Exponential Moving Average (EMA).
Returns:
1. A pandas Series representing the EMA.

Moving Averages (MA):

Moving Average (MA): A straightforward calculation that averages prices over a specified period equally.

Entry Signal:
When the short-term moving average (e.g., 10-day SMA) crosses above the long-term moving average (e.g., 50-day SMA), it may generate a buy signal.
Exit Signal:
When the short-term moving average crosses below the long-term moving average, it may generate a sell signal.

# Entry signal
df['MA_Short'] = df['Close'].rolling(window=10).mean()
df['MA_Long'] = df['Close'].rolling(window=50).mean()
df['Signal'] = 0
df.loc[df['MA_Short'] > df['MA_Long'], 'Signal'] = 1 # Buy signal
# Exit signal
df.loc[df['MA_Short'] < df['MA_Long'], 'Signal'] = -1 # Sell signal

# Plotting
plt.figure(figsize=(10, 6))
plt.plot(df['Close'], label='Close Price')
plt.plot(df['MA_Short'], label='MA Short (10)')
plt.plot(df['MA_Long'], label='MA Long (50)')
plt.title('Simple Moving Average Crossover Strategy')
plt.legend()
plt.show()

WMAIndicator:

Weighted Moving Average (WMA): Assigns different weights to different data points, allowing more flexibility in capturing trends.

  • Weighted Moving Average (WMA) assigns different weights to data points.
  • Provides a weighted average to smoothen price trends.
1. The resulting WMA is returned as a new feature in the form of a pandas Series.

Kaufman’s Adaptive Moving Average (KAMA):

  • Adjusts to market noise, follows small price swings closely, and identifies overall trend and turning points.
1. Adaptive moving average designed for volatile markets.
2. Follows prices closely during small price swings and low volatility.
3. Adjusts and tracks prices from a distance during wider price swings.
4. Useful for trend identification, pinpointing turning points, and filtering price movements.
5. Parameters include 'close' dataset, window period, and constants for exponential moving averages (EMA).
6. Generates a new feature indicating the adaptive moving average.
7. Can be applied to identify overall trends and time entry/exit points.

For NaN values:
1. If set to True, fills missing values in the generated feature.
2. Enhances data completeness.

Momentum Indicators

ADXIndicator:

  • Measures the strength of a trend using Plus Directional Indicator (+DI) and Minus Directional Indicator (-DI).
  • Average Directional Index (ADX) quantifies the trend’s strength over time.
  • Helps identify both the direction and strength of a trend.
1. Measures trend strength using +DI and -DI derived from smoothed averages.
2. Calculates Average Directional Index (ADX) based on the difference between +DI and -DI.
3. Helps determine trend direction and strength when used together.


Parameters:
1. Takes 'High,' 'Low,' and 'Close' columns of a dataset.
2. Uses a specified window period (default is 14).
3. Option to fill NaN values.
ADX Function:
1. Returns a new feature - Average Directional Index (ADX).
2. Type: pandas.Series.
adx_neg Function:
1. Returns a new feature - Minus Directional Indicator (-DI).
2. Type: pandas.Series.
adx_pos Function:
1. Returns a new feature - Plus Directional Indicator (+DI).
2. Type: pandas.Series.

AroonIndicator:

  • Predicts potential trend changes by calculating days since N-day highs and lows.
  • Aroon Up and Aroon Down channels indicate the uptrend and downtrend probabilities.
  • Aroon Indicator represents the difference between Aroon Up and Aroon Down.
1. Predicts potential trend changes.
2. Formula: Aroon Up = ((N - Days Since N-day High) / N) x 100, Aroon Down = ((N - Days Since N-day Low) / N) x 100, Aroon Indicator = Aroon Up - Aroon Down

Parameters:
1. close: Close prices dataset.
2. window: Number of periods (default is 25).
3. fillna: Fill NaN values if True.
aroon_down:
1. Aroon Down Channel.
2. Return Type: pandas.Series.
aroon_indicator:
1. Aroon Indicator.
2. Return Type: pandas.Series.
aroon_up:
1. Aroon Up Channel.
2. Return Type: pandas.Series

CCIIndicator:

  • Measures the difference between a security’s price change and its average.
  • High positive readings indicate strength, while low negatives suggest weakness.
  • Helps identify overbought and oversold market conditions.
1. Commodity Channel Index (CCI) calculates the difference between a security's price change and its average change.
2. Interpretation: High positive readings signal strength, indicating prices above the average. Low negative readings signal weakness, showing prices below the average.

Parameters:
1. high: High prices.
2. low: Low prices.
3. close: Close prices.
4. window: Lookback period (default 20).
5. constant: Constant multiplier (default 0.015).
6. fillna: Fill NaN values if True (default False).
Method:
1. cci(): Computes Commodity Channel Index.
Output:
1. Generates a new feature of type pandas.Series.

Money Flow Index (MFI):

  • Combines price and volume to measure buying and selling pressure.
  • Positive values indicate buying pressure, negative values show selling pressure.
1. Utilizes both price and volume to gauge buying and selling pressure.
2. MFI is positive during price increases (indicating buying pressure) and negative during price decreases (indicating selling pressure).
3. It calculates a ratio of positive to negative money flow, creating an oscillator between zero and one hundred.
4. The oscillator is derived from an RSI formula.
5. Measures market sentiment based on the balance of positive and negative money flow.
6. Ranges from 0 to 100, identifying overbought and oversold conditions.
7. Useful for assessing potential trend reversals or continuations.
8. A rising MFI suggests increasing buying pressure, while a falling MFI indicates growing selling pressure.
  • For more detailed information, you can refer to StockCharts.

Relative Strength Index (RSI):

Entry Signal:
When RSI crosses above 30 (oversold condition), it may generate a buy signal.
Exit Signal:
When RSI crosses below 70 (overbought condition), it may generate a sell signal.

# Entry signal
df['RSI'] = ta.momentum.RSIIndicator(df['Close'], window=14).rsi()
df['Signal'] = 0
df.loc[df['RSI'] < 30, 'Signal'] = 1 # Buy signal
# Exit signal
df.loc[df['RSI'] > 70, 'Signal'] = -1 # Sell signal

# Plotting
plt.figure(figsize=(10, 6))
plt.plot(df['Close'], label='Close Price')
plt.scatter(df[df['Signal'] == 1].index, df[df['Signal'] == 1]['Close'], marker='^', color='g', label='Buy Signal')
plt.scatter(df[df['Signal'] == -1].index, df[df['Signal'] == -1]['Close'], marker='v', color='r', label='Sell Signal')
plt.title('Reliance Industries Ltd - RSI-Based Trading Signals')
plt.xlabel('Date')
plt.ylabel('Close Price')
plt.legend()
plt.show()

Stochastic Oscillator:

Entry Signal:
When the %K line crosses above the %D line, it may generate a buy signal.
Exit Signal:
When the %K line crosses below the %D line, it may generate a sell signal.

# Calculate Stochastic Oscillator (%K)
df['%K'] = ta.momentum.stoch(df['High'], df['Low'], df['Close'])

# Generate entry and exit signals
df['Signal'] = 0
df.loc[(df['%K'] > 80) & (df['%K'].shift(1) <= 80), 'Signal'] = -1 # Sell signal
df.loc[(df['%K'] < 20) & (df['%K'].shift(1) >= 20), 'Signal'] = 1 # Buy signal
# Plotting
plt.figure(figsize=(12, 6))
plt.plot(df['Close'], label='Close Price')
plt.scatter(df[df['Signal'] == 1].index, df['Close'][df['Signal'] == 1], marker='^', color='g', label='Buy Signal')
plt.scatter(df[df['Signal'] == -1].index, df['Close'][df['Signal'] == -1], marker='v', color='r', label='Sell Signal')
plt.title('Stochastic Oscillator Buy/Sell Signals')
plt.xlabel('Date')
plt.ylabel('Close Price')
plt.legend()
plt.show()

Williams %R:

  • The inverse of the Fast Stochastic Oscillator reflects the close relative to the highest high.
1. Developed by Larry Williams, it's a momentum indicator.
2. Inversely related to the Fast Stochastic Oscillator.
3. Reflects the close's level relative to the highest high in a look-back period.
4. Corrects inversion by multiplying the raw value by -100.
5. Williams %R and Fast Stochastic Oscillator produce similar lines with different scaling.
6. Oscillates between 0 to -100.
7. Readings from 0 to -20 signal overbought conditions.
8. Readings from -80 to -100 indicate oversold conditions.
9. Signals derived from Stochastic Oscillator are applicable.
10. Formula: %R = (Highest High - Close)/(Highest High - Lowest Low) * -100.
11. Lowest Low is the lowest low for the look-back period.
12. Highest High is the highest high for the look-back period.
13. %R is multiplied by -100 to correct inversion and move the decimal.

Volume Indicators

Accumulation/Distribution Index (ADI):

  • Measures the accumulation or distribution of a stock, providing insights into potential price movements.
  • Leading indicator for price changes.
1. Measures accumulation or distribution of a stock.
2. Acts as a leading indicator for predicting price movements.
3. Utilizes high, low, close prices, and volume data.
4. Generates a new feature indicating market accumulation or distribution.
5. Helpful for identifying potential price trends.

For filling NaN values:
1. If set to True, fills missing values in the generated feature.
2. Enhances data completeness.

On-Balance Volume (OBV):

  • Relates price and cumulative volume to identify buying or selling pressure.
  • Cumulative total volume is used to analyze market trends.
Description: Relates stock price and volume, cumulatively measuring total volume.
Purpose: To gauge buying or selling pressure in the market.
Parameters:
close: Closing prices of the stock.
volume: Trading volume data.
fillna: Option to fill NaN values.
Output:
Generates a new feature indicating On-Balance Volume (OBV).
Output type: pandas Series.

The On-Balance Volume (OBV) indicator analyzes the relationship between stock prices and cumulative volume, helping traders identify buying or selling pressure in the market. It introduces a new feature representing the OBV, calculated from the closing prices and trading volumes. This indicator is a valuable tool for understanding market dynamics.

Volatility Indicators

Average True Range (ATR):

  • Measures price volatility.
  • Indicates the extent of price movements.
  • Strong moves are associated with large true ranges.
1. Measures the volatility of price movements.
2. Indicates the magnitude of price changes.
3. Strong price movements are associated with larger true ranges.
4. Helps assess market volatility and potential trading opportunities.

Parameters:
high: High prices of the dataset.
low: Low prices of the dataset.
close: Closing prices of the dataset.
window: Number of periods considered.
fillna: Option to fill NaN values.
average_true_range():
1. Computes the Average True Range based on provided data.
2. Returns a new feature representing the ATR.
3. Output is a pandas Series containing ATR values.

Entry Signal:
When the price exceeds a certain multiple of ATR above the recent close, it may generate a buy signal.
Exit Signal:
When the price falls below a certain multiple of ATR below the recent close, it may generate a sell signal.

# Entry and Exit signal using ATR
atr_multiplier = 2.0
df['ATR'] = ta.volatility.average_true_range(df['High'], df['Low'], df['Close'])
df['Signal'] = 0
df.loc[df['Close'] > df['Close'].shift(1) + atr_multiplier * df['ATR'], 'Signal'] = 1 # Buy signal
df.loc[df['Close'] < df['Close'].shift(1) - atr_multiplier * df['ATR'], 'Signal'] = -1 # Sell signal

# Plotting
plt.figure(figsize=(10, 6))
plt.plot(df['Close'], label='Close Price', alpha=0.5)
# Plot Buy signals with green upward arrows
plt.scatter(df.index[df['Signal'] == 1], df['Close'][df['Signal'] == 1], marker='^', color='g', label='Buy Signal')
# Plot Sell signals with red downward arrows
plt.scatter(df.index[df['Signal'] == -1], df['Close'][df['Signal'] == -1], marker='v', color='r', label='Sell Signal')
plt.title(f"{stock_symbol} Stock Price with ATR-based Buy/Sell Signals")
plt.xlabel("Date")
plt.ylabel("Stock Price (Close)")
plt.legend()
plt.show()

Remember that these are simplified examples, and actual trading strategies involve careful consideration of risk management, transaction costs, and market conditions. Always backtest your strategies and consider additional factors before implementing them in real trading.

Statistic Functions

BETA (Beta):

  • Beta is a measure of a stock’s volatility in relation to the market. It represents the sensitivity of an asset’s returns to changes in the market index.
  • Formula: Beta is calculated using the Covariance of the stock’s returns with the market returns divided by the Variance of the market returns.
  • You can calculate beta using various libraries like NumPy and pandas.
  • This can be used while making portfolio and filtering out the stocks having less/more impact of the market’s movement on its performance.

CORREL (Pearson’s Correlation Coefficient — r):

  • Pearson’s correlation coefficient (r) measures the linear relationship between two variables. It ranges from -1 (perfect negative correlation) to 1 (perfect positive correlation), with 0 indicating no linear correlation.
  • where cov is the covariance and σ represents the standard deviation.
  • You can use functions from libraries like NumPy or pandas to calculate the correlation coefficient.

STDDEV (Standard Deviation):

  • Definition: Standard Deviation is a measure of the amount of variation or dispersion in a set of values. It provides an indication of the extent to which individual data points differ from the mean.
  • where σ is the standard deviation, Xi​ are individual data points, Xˉ is the mean, and N is the number of data points.
  • Standard deviation can be calculated using functions provided by libraries such as NumPy or pandas.

VAR (Variance):

  • Variance is a measure of how far a set of numbers are spread out from their average value. It is the square of the standard deviation.
  • where Var(X) is the variance, Xi​ are individual data points, Xˉ is the mean, and N is the number of data points.
  • Variance can be calculated using functions from libraries like NumPy or pandas.

We have discussed a couple of Indicators for which code snippet is also provided. For others you are welcome to try to code using libraries in any python environment. In case you feel any difficulties or face any doubts. Feel free to get in touch and ask your questions. Here are my social handles :-

  1. LinkedIn
  2. Twitter
  3. Site
  4. BuyMeCoffee (;

--

--