← index | See also: nadaraya-whatson_indicator
Testing Mean Reversion Trading Strategy on Nifty50 Stocks
What is Mean Reversion?
Mean reversion is a trading strategy based on the idea that asset prices tend to revert to their mean or median value over time. This strategy involves identifying assets whose prices deviate significantly from the average and betting that they will eventually return to the average.
To implement a mean-reverting strategy, traders typically use statistical techniques to identify assets that are trading significantly above or below their historical average. Then, place a trade to make a profit if the asset’s price returns to its average value.
Measuring Deviation from Mean
There are several ways to measure the deviation of an asset from its mean:
- Statistical Indicators: Standard deviation and Bollinger Bands
- Moving Averages: Identify assets trading above/below recent price trends
- RSI (Relative Strength Index): Measure overbought/oversold conditions
Important Note: Mean reversion is not a guaranteed way to make money and there is no guarantee that an asset’s price will return to its average. Traders should carefully consider risk and employ appropriate risk management techniques when implementing a return-to-average strategy.
Implementation in Python
Import Required Libraries
import yfinance as yf
import pandas as pd
import numpy as np
import ta
import matplotlib.pyplot as plt
import time
from pandas_datareader import data as pdrDownload Data from Yahoo Finance
df = yf.download('AXISBANK.NS', start='2018-01-01')Calculate Required Statistical Parameters
# 20-day moving average
df['ma_20'] = df.Close.rolling(20).mean()
# 20-day volatility (standard deviation)
df['vol'] = df.Close.rolling(20).std()
# Bollinger Bands (upper and lower)
df['upper_bb'] = df.ma_20 + (2*df.vol)
df['lower_bb'] = df.ma_20 - (2*df.vol)
# RSI indicator
df['rsi'] = ta.momentum.rsi(df.Close)Define Buy and Sell Conditions
conditions = [(df.rsi < 30) & (df.Close < df.lower_bb),
(df.rsi > 70) & (df.Close > df.upper_bb)]
choices = ['Buy', 'Sell']
df['Signal'] = np.select(conditions, choices)
df = df.dropna()Signal Timing Adjustment
Since we get a buy/sell signal, we can only perform that action the next day:
df.Signal = df.Signal.shift()
df['Shifted_Close'] = df.Close.shift()
# ignore the warningDefine Trading Logic
position = False
buydates, selldates = [], []
buyprices, sellprices = []
for index, row in df.iterrows():
if not position and row['Signal'] == 'Buy':
buydates.append(index)
buyprices.append(row.Open)
position = True
if position:
if row['Signal'] == 'Sell' or row.Shifted_Close < 0.95 * buyprices[-1]:
# 0.95 is stop loss means 5% stop loss
selldates.append(index)
sellprices.append(row.Open)
position = FalsePlotting the Results
plt.figure(figsize=(10, 5))
plt.plot(df.Close)
plt.scatter(df.loc[buydates].index, df.loc[buydates].Close, marker='^', c='g')
plt.scatter(df.loc[selldates].index, df.loc[selldates].Close, marker='^', c='r')Calculate Returns
(pd.Series([(sell-buy)/buy for sell, buy in zip(sellprices, buyprices)]) + 1).prod() - 1
# Result: 0.3181 = 31.8% returnCompiling All Steps Under One Function
def Test_MeanReversion(ticker='', date_start='2018-01-01'):
df = yf.download(ticker, date_start)
time.sleep(2)
# Calculate indicators
df['ma_20'] = df.Close.rolling(20).mean()
df['vol'] = df.Close.rolling(20).std()
df['upper_bb'] = df.ma_20 + (2*df.vol)
df['lower_bb'] = df.ma_20 - (2*df.vol)
df['rsi'] = ta.momentum.rsi(df.Close)
# Generate signals
conditions = [(df.rsi < 30) & (df.Close < df.lower_bb),
(df.rsi > 70) & (df.Close > df.upper_bb)]
choices = ['Buy', 'Sell']
df['Signal'] = np.select(conditions, choices)
df = df.dropna()
# Adjust timing
df.Signal = df.Signal.shift()
df['Shifted_Close'] = df.Close.shift()
# Trading logic
position = False
buydates, selldates = [], []
buyprices, sellprices = []
for index, row in df.iterrows():
if not position and row['Signal'] == 'Buy':
buydates.append(index)
buyprices.append(row.Open)
position = True
if position:
if row['Signal'] == 'Sell' or row.Shifted_Close < 0.90 * buyprices[-1]:
selldates.append(index)
sellprices.append(row.Open)
position = False
# Calculate returns
return (pd.Series([(sell-buy)/buy for sell, buy in zip(sellprices, buyprices)]) + 1).prod() - 1Testing the Strategy Over India’s Top 50 Stocks!
Initialize Return and Ticker Columns
ret = []
tick_list = []Get Nifty50/Nifty100 Tickers from NSE Website
url = "https://www1.nseindia.com/content/indices/ind_niftysmallcap50list.csv"
# For nifty100 use this:
# url = "https://archives.nseindia.com/content/indices/ind_nifty100list.csv"
ticker_list = pd.read_csv(url)
ticker_list['Symbol'] = ticker_list['Symbol'] + '.NS'Download Data and Test Strategy
count = 0
for tick in tick_list['Symbol']:
try:
ret.append(Test_MeanReversion(tick))
tick_list.append(tick)
count = count + 1
print(count)
except Exception as e:
print(e)
print(f"Could not gather data on {tick}")Convert Data to DataFrame
rent = pd.DataFrame(ret)
tick_list = pd.DataFrame(tick_list)
Cum_return = pd.concat([rent, tick_list], axis=1)
Cum_return.set_axis(['returns', 'symbol'], axis='columns', inplace=True)Box Plot Analysis
import plotly.express as px
fig = px.box(Cum_return.returns)
fig.show()Results and Analysis
According to the box plot analysis, this strategy shows mixed results with the current parameters. However, the performance can be significantly improved by:
- Adjusting Stop Loss: Modify the stop loss percentage based on individual stock volatility
- Parameter Optimization: Fine-tune the RSI thresholds and Bollinger Band multipliers
- Risk Management: Implement position sizing and portfolio-level risk controls
- Market Regime Detection: Consider market conditions when applying mean reversion strategies
Key Insights
- Strategy Viability: Mean reversion can work well in sideways markets but may underperform in strong trending markets
- Risk Management: The 5% stop loss provides basic protection but may need adjustment based on volatility
- Market Efficiency: Indian markets may show different mean reversion characteristics compared to developed markets
- Implementation Challenges: Data quality, execution timing, and transaction costs can significantly impact real-world performance
Conclusion
While the mean reversion strategy shows promise on individual stocks like AXISBANK.NS (31.8% return), its performance across the broader Nifty50 universe is mixed. The strategy’s effectiveness depends heavily on:
- Market conditions and volatility
- Proper parameter optimization
- Robust risk management
- Transaction cost considerations
This analysis serves as a foundation for further research and optimization of mean reversion strategies in the Indian market context.
For error correction, queries, or just to chat: Connect with me on LinkedIn
Tags: Python Trading AlgorithmicTrading QuantitativeFinance TradingSystem MeanReversion Nifty50