0% found this document useful (0 votes)
0 views17 pages

Building A Mean Reversion Trading System With Python

The document outlines the development of a Mean Reversion Trading System using Python, which leverages the statistical concept of mean reversion to identify trading opportunities in sideways markets. Key components include the use of Bollinger Bands and the Relative Strength Index (RSI) to define entry and exit conditions, followed by backtesting various parameter combinations to optimize performance. The document also includes code snippets for implementing the strategy and visualizing the results through 3D plots.

Uploaded by

As Win
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
0 views17 pages

Building A Mean Reversion Trading System With Python

The document outlines the development of a Mean Reversion Trading System using Python, which leverages the statistical concept of mean reversion to identify trading opportunities in sideways markets. Key components include the use of Bollinger Bands and the Relative Strength Index (RSI) to define entry and exit conditions, followed by backtesting various parameter combinations to optimize performance. The document also includes code snippets for implementing the strategy and visualizing the results through 3D plots.

Uploaded by

As Win
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 17

Building a Mean Reversion

Trading System with Python.

The Mean Reversion System is a popular trading strategy


that relies on the statistical concept of mean reversion.
This principle suggests that prices and returns eventually
return to their historical average or mean.
Traders employing this strategy look for opportunities to
profit from price deviations from the mean, expecting the
price to revert to its average over time. To summarize:
Mean reversion is a financial theory that assumes
asset prices fluctuate around a stable long-term
average.
This type of trading system is generally well-suited for
sideways markets, where prices do not follow a specific
direction but instead fluctuate above and below the
average over a certain period.
Therefore, I will design and develop a mean reversion
system to maximize profits in sideways markets while
minimizing the drawdown during bearish markets. The
indicators best suited for this purpose are Bollinger
Bands and RSI.

From the candlestick chart, we will use observation to


define the entry and exit conditions as follows:
Entry Conditions:

 The closing price is below the lower Bollinger Band

 The RSI is below 30 (indicating oversold conditions)

Exit Conditions:

 The closing price rises above the Bollinger Band’s


mean
From now on, it will be a step-by-step to building a trading
system and performing optimization with Python.

1. Imports

import numpy as np
import pandas as pd
import yfinance as yf
import vectorbt as vbt
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

 numpy and pandas: For numerical and data


manipulation tasks.

 yfinance: To download historical stock data.

 vectorbt: To perform backtesting and analyze


portfolios.

 matplotlib.pyplot and mpl_toolkits.mplot3d: For


plotting 3D graphs.
2. Functions for Technical Indicators

# Function to calculate Bollinger Bands


def calculate_bollinger_bands(df, window=20, std_dev=2):
"""Calculate Bollinger Bands."""
df['Mean'] = df['Close'].rolling(window=window).mean()
df['Std'] = df['Close'].rolling(window=window).std()
df['Upper_Band'] = df['Mean'] + (std_dev * df['Std'])
df['Lower_Band'] = df['Mean'] - (std_dev * df['Std'])
return df

 calculate_bollinger_bands: This function calculates the


Bollinger Bands, which consist of 3 components:

 Mean (the moving average of the closing prices),

 Upper Band (Mean + standard deviation * a


multiplier),

 Lower Band (Mean — standard deviation * a


multiplier).

 It uses a rolling window (default of 20 days) and a


standard deviation multiplier (default of 2) to
calculate these bands.

# Function to calculate RSI


def calculate_rsi(df, period=14):
"""Calculate Relative Strength Index (RSI)."""
delta = df['Close'].diff()
gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
rs = gain / loss
df['RSI'] = 100 - (100 / (1 + rs))
return df
 calculate_rsi: This function calculates the Relative
Strength Index (RSI), which is a momentum
oscillator that measures the speed and change of
price movements. It is computed using the average
gains and losses over a specified period (default of 14
days).

3. Data Download

# Define the stock symbol and time period


symbol = 'INTC'
start_date = '2014-01-01'
end_date = '2024-12-27'

# Download the data


df = yf.download(symbol, start=start_date, end=end_date)
df.columns = ['Adj Close', 'Close', 'High', 'Low', 'Open', 'Volume']
df.ffill(inplace=True)

 symbol = 'SI=F': Specifies the stock symbol for Silver


Futures (SI=F).

 start_date = '2014-01-01' and end_date = '2024-12-27':

Defines the time range for the stock data.

 df = yf.download(symbol, start=start_date, end=end_date):

Downloads the historical data for Silver Futures from


Yahoo Finance.

 df.columns = ['Adj Close', 'Close', 'High', 'Low', 'Open',

'Volume']: Renames the columns of the downloaded


DataFrame to standard names.
 df.ffill(inplace=True): Forward-fills any missing data (in
case there are gaps in the data).

4. Parameter Combinations for Optimization

# Initialize arrays to store results


results = []
window_range = range(1, 50) # Example range for window
std_dev_range = np.arange(1, 4, 0.5) # Example range for std_dev
period_range = range(1, 30) # Example range for period

 The script then sets up parameter ranges for the


Bollinger Bands and RSI:

 window_range = range(1, 50): Range of window sizes for


the moving average.

 std_dev_range = np.arange(1, 4, 0.5): Range of standard


deviation multipliers.

 period_range = range(1, 30): Range of periods for


calculating the RSI.

5. Loop for Backtesting and Optimization

# Loop through all combinations of window, std_dev, and period


for window in window_range:
for std_dev in std_dev_range:
for period in period_range:
# Calculate Bollinger Bands and RSI for current parameter set
temp_df = df.copy()
temp_df = calculate_bollinger_bands(temp_df, window=window,
std_dev=std_dev)
temp_df = calculate_rsi(temp_df, period=period)

# Define entry and exit signals


temp_df['Buy_Signal'] = (temp_df['Close'] <
temp_df['Lower_Band']) & (temp_df['RSI'] < 30)
temp_df['Sell_Signal'] = (temp_df['Close'] >
temp_df['Upper_Band']) & (temp_df['RSI'] > 70)
temp_df['Exit_Buy'] = (temp_df['Close'] > temp_df['Mean'])
temp_df['Exit_Sell'] = (temp_df['Close'] < temp_df['Mean'])

# Convert signals to boolean arrays


entries = temp_df['Buy_Signal'].to_numpy()
exits = temp_df['Exit_Buy'].to_numpy()

# Backtest using vectorbt


portfolio = vbt.Portfolio.from_signals(
close=temp_df['Close'],
entries=entries,
exits=exits,
init_cash=100_000,
fees=0.001
)

# Store results (final portfolio value, can also include


Sharpe ratio or other metrics)
results.append((window, std_dev, period,
portfolio.stats().loc['Total Return [%]']))

# Convert results to DataFrame for easy plotting


results_df = pd.DataFrame(results, columns=['Window', 'Std_Dev', 'Period',
'Total Return [%]'])

 For each combination of parameters (window, std_dev,

and period), the following steps are performed:


1. Bollinger Bands and RSI Calculation: For each
combination, the Bollinger Bands and RSI are calculated
using the respective functions.
2. Define Entry and Exit Signals:

 Buy Signal: The code generates a signal to buy when


the closing price is below the lower Bollinger
Band and the RSI is below 30 (indicating that the
asset is oversold).

 Exit Buy: A sell signal is triggered when the closing


price crosses above the moving average (the
“Mean”).
3. Backtesting with vectorbt:

 A portfolio is created using the from_signals method


from vectorbt, which simulates the strategy based on
the entry and exit signals, an initial cash amount of
100,000, and a fee of 0.1%.
4. Store Results: The results (final total return) are
stored in an array results for later analysis.

6. Data Preparation for 3D Plotting

# Extract data for 3D plotting


x = results_df['Window']
y = results_df['Std_Dev']
z = results_df['Period']
value = results_df['Total Return [%]']

# Create a 3D plot of the optimization results


fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111, projection='3d')
ax.scatter(x, y, z, c=value, cmap='viridis', marker='o')
ax.set_xlabel('Window')
ax.set_ylabel('Std Dev')
ax.set_zlabel('Period')
ax.set_title('Optimization of Bollinger Bands and RSI Parameters')
plt.show()
 Results DataFrame: After the loop, the results are
stored in a DataFrame (results_df) that includes
the window, std_dev, period, and the Total Return [%] for
each combination.

 3D Plot: The code is then used matplotlib to create a


3D scatter plot of the results:

 x-axis: Window size for the Bollinger Bands.

 y-axis: Standard deviation multiplier for the Bollinger


Bands.

 z-axis: Period for the RSI.

 Color: The color of the points represents the total


return percentage.

7. Identify the Best Parameters

# Find the best parameter combination (highest final portfolio value)


best_params = results_df.loc[results_df['Total Return [%]'].idxmax()]

# Print the best parameters and corresponding final portfolio value


print(f"Best Parameters:")
print(f"Window: {best_params['Window']}")
print(f"Std Dev: {best_params['Std_Dev']}")
print(f"Period: {best_params['Period']}")
print(f"Final Portfolio Value: {best_params['Total Return [%]']}")

 Best Parameter Combination: The code identifies


the combination of window, std_dev, and period that
results in the highest total return by finding the row
with the maximum value in the Total Return

[%] column.

 Print the Best Parameters: The best parameters


and the corresponding final portfolio value are
printed to the console.
Full Code Here:

import numpy as np
import pandas as pd
import yfinance as yf
import vectorbt as vbt
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

# Function to calculate Bollinger Bands


def calculate_bollinger_bands(df, window=20, std_dev=2):
"""Calculate Bollinger Bands."""
df['Mean'] = df['Close'].rolling(window=window).mean()
df['Std'] = df['Close'].rolling(window=window).std()
df['Upper_Band'] = df['Mean'] + (std_dev * df['Std'])
df['Lower_Band'] = df['Mean'] - (std_dev * df['Std'])
return df

# Function to calculate RSI


def calculate_rsi(df, period=14):
"""Calculate Relative Strength Index (RSI)."""
delta = df['Close'].diff()
gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
rs = gain / loss
df['RSI'] = 100 - (100 / (1 + rs))
return df

# Define the symbol and time period


symbol = 'SI=F'
start_date = '2014-01-01'
end_date = '2024-12-27'

# Download the data


df = yf.download(symbol, start=start_date, end=end_date)
df.columns = ['Adj Close', 'Close', 'High', 'Low', 'Open', 'Volume']
df.ffill(inplace=True)

# Initialize arrays to store results


results = []
window_range = range(1, 50) # Example range for window
std_dev_range = np.arange(1, 4, 0.5) # Example range for std_dev
period_range = range(1, 30) # Example range for period

# Loop through all combinations of window, std_dev, and period


for window in window_range:
for std_dev in std_dev_range:
for period in period_range:
# Calculate Bollinger Bands and RSI for current parameter set
temp_df = df.copy()
temp_df = calculate_bollinger_bands(temp_df, window=window,
std_dev=std_dev)
temp_df = calculate_rsi(temp_df, period=period)

# Define entry and exit signals


temp_df['Buy_Signal'] = (temp_df['Close'] <
temp_df['Lower_Band']) & (temp_df['RSI'] < 30)
temp_df['Exit_Buy'] = (temp_df['Close'] > temp_df['Mean'])

# Convert signals to boolean arrays


entries = temp_df['Buy_Signal'].to_numpy()
exits = temp_df['Exit_Buy'].to_numpy()

# Backtest using vectorbt


portfolio = vbt.Portfolio.from_signals(
close=temp_df['Close'],
entries=entries,
exits=exits,
init_cash=100_000,
fees=0.001
)

# Store results (final portfolio value, can also include


Sharpe ratio or other metrics)
results.append((window, std_dev, period,
portfolio.stats().loc['Total Return [%]']))

# Convert results to DataFrame for easy plotting


results_df = pd.DataFrame(results, columns=['Window', 'Std_Dev', 'Period',
'Total Return [%]'])

# Extract data for 3D plotting


x = results_df['Window']
y = results_df['Std_Dev']
z = results_df['Period']
value = results_df['Total Return [%]']

# Create a 3D plot of the optimization results


fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111, projection='3d')
ax.scatter(x, y, z, c=value, cmap='viridis', marker='o')
ax.set_xlabel('Window')
ax.set_ylabel('Std Dev')
ax.set_zlabel('Period')
ax.set_title('Optimization of Bollinger Bands and RSI Parameters')
plt.show()
# Find the best parameter combination (highest final portfolio value)
best_params = results_df.loc[results_df['Total Return [%]'].idxmax()]

# Print the best parameters and corresponding final portfolio value


print(f"Best Parameters:")
print(f"Window: {best_params['Window']}")
print(f"Std Dev: {best_params['Std_Dev']}")
print(f"Period: {best_params['Period']}")
print(f"Final Portfolio Value: {best_params['Total Return [%]']}")

After running the Python code, the result will be as


follows:

Best Parameters:

 Window: 41.0

 Std Dev: 1.0

 Period: 21.0

 Final Portfolio Value: 154.8820997183831


Now, we will take this best parameter and backtest it
again to see the various statistics.

import numpy as np
import pandas as pd
import yfinance as yf
import vectorbt as vbt
import matplotlib.pyplot as plt

# Function to calculate Bollinger Bands


def calculate_bollinger_bands(df, window=20, std_dev=2):
"""Calculate Bollinger Bands."""
df['Mean'] = df['Close'].rolling(window=window).mean()
df['Std'] = df['Close'].rolling(window=window).std()
df['Upper_Band'] = df['Mean'] + (std_dev * df['Std'])
df['Lower_Band'] = df['Mean'] - (std_dev * df['Std'])
return df

# Function to calculate RSI


def calculate_rsi(df, period=14):
"""Calculate Relative Strength Index (RSI)."""
delta = df['Close'].diff()
gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
rs = gain / loss
df['RSI'] = 100 - (100 / (1 + rs))
return df

# Define the symbol and time period


symbol = 'SI=F'
start_date = '2014-01-01'
end_date = '2024-12-27'

# Download the data


df = yf.download(symbol, start=start_date, end=end_date)
df.columns = ['Adj Close', 'Close', 'High', 'Low', 'Open', 'Volume']
df.ffill(inplace=True)

# Calculate Bollinger Bands and RSI


df = calculate_bollinger_bands(df, window=41, std_dev=1)
df = calculate_rsi(df, period=21)

# Define entry and exit signals


df['Buy_Signal'] = (df['Close'] < df['Lower_Band']) & (df['RSI'] < 30)
df['Exit_Buy'] = (df['Close'] > df['Mean'])

# Convert signals to boolean arrays


entries = df['Buy_Signal'].to_numpy()
exits = df['Exit_Buy'].to_numpy()

# Backtest using vectorbt


portfolio = vbt.Portfolio.from_signals(
close=df['Close'],
entries=entries,
exits=exits,
init_cash=100_000,
fees=0.001
)

# Display performance metrics


print(portfolio.stats())

# Plot equity curve


portfolio.plot().show()

This Python code has the same indicator functions, data


download, and trading conditions as before. The only
addition is the backtest using the best parameters. The
results are as follows:

Performance Metrics

Performance Visualization

From testing the trading system with silver for almost 11


years, it was observed that although the system had
periods of negative returns, these were due to the drop in
silver prices.
Even though the prices fell, the trading system was still
able to perform better than buy and hold from that point
onward. After 2016, the system started making profits and
has continued to do so up to the present, clearly
outperforming the market (the green line beats the gray
line).
After this, I will test the same trading conditions with other
assets to identify the strengths and weaknesses of this
system to understand it as much as possible.

Backtest and Optimize with Intel Stock (INTC)

# Define the stock symbol and time period


symbol = 'INTC'
start_date = '2014-01-01'
end_date = '2024-12-27'
Best Parameters:

 Window: 48.0

 Std Dev: 1.0

 Period: 29.0

 Final Portfolio Value: 140.6236377023705

Performance Metrics

Performance Visualization

From the backtest with Intel stock, we can see the


weaknesses of the system. In the early stages, when the
stock was in an uptrend, the system hardly generated any
entry conditions because it didn’t meet the criteria.
However, once the system started going sideways in 2020
and eventually turned bearish, it was able to generate
profits and outperform the market over nearly 11 years.

Backtest and Optimize with Gold (GC=F)

# Define the Gold symbol and time period


symbol = 'GC=F'
start_date = '2014-01-01'
end_date = '2024-12-27'
Best Parameters:

 Window: 37.0

 Std Dev: 1.5

 Period: 15.0

 Final Portfolio Value: 96.16868596766638

Performance Metrics

Performance Visualization

From the backtest with gold, it can be seen that the system
generates more consistent profits compared to buy and
hold but buy and hold yields a higher profit over the 11
years.
The trading system and buy-and-hold alternate in
profitability, with the system performing better during
periods when gold enters consolidation or retracement
phases, as observed from 2014 to 2019.
However, when gold entered a bullish phase from 2020 to
2021, the system could not generate profits comparable to
the market’s uptrend. But once it entered a consolidation
phase again, the system outperformed buy and hold.
From the experiment with the mean reversion system
across all 3 markets, the conclusion is that this
system performs well in sideways and bearish
markets, as it can filter out losses.
Therefore, if we want to apply this system, it is essential to
understand the overall market fundamentals and predict
when the market is in an uptrend to switch to a buy-and-
hold strategy.
Conversely, when the market enters a consolidation phase,
we should switch back to using this trading system. This
approach will make trading more effective. Understanding
the overall market fundamentals is not an impossible task,
and you can further study it through the article below:
M2 as a Predictor for Market Trends
Building an Investment Strategy Around Money Supply (M2)
wire.insiderfinance.io

If you read this article to the end, you will notice that
almost every image I included has a watermark with
my own logo. I want to explain that this is necessary
because someone copied my article and posted it on
their own website under their name, without giving
credit. To prevent anyone from copying my work, I
had to add these watermarks. I truly apologize for
this, as I originally wrote the entire article as a
personal study record. However, I felt the need to
add these watermarks in order to maintain my
dignity. I apologize again and thank you for your
understanding.
The purpose of this article is solely educational. It is not
intended as investment advice or recommendations.
Readers are encouraged to use their own judgment and
test the system independently before making any
decisions.

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy