Algorithmic Trading in Python Code - Mean Reversion Strategy
- Intrendias

- Dec 11, 2023
- 7 min read
Updated: Dec 22, 2023
On my YouTube channel I have a Algorithmic Trading Course series where we use python and create a mean reversion inspired trading algorithm. The algorithm provides buy and sell signals along with backtesting results. For your convenience, I have provided the complete jupyter notebook with all the required code here!
Algorithmic Trading Python Code Here for Intrendias Essentials Members (Free Tier)!
If the download does not work, please refer to the raw code below - Thank you!
# Import lines
# Yahoo Finance
import yfinance as yf
import yahoo_fin.stock_info as yaf
# for statistics
import statistics as st
#Visuals, Dataframe, Calculations
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
#Time and Date
from datetime import datetime, timedelta, date
import time
# Custom Variables ------------------------------------------------------------
currentDay = datetime.now().day
currentMonth = datetime.now().month
currentYear = datetime.now().year
currentDate = datetime(currentYear, currentMonth, currentDay)
#Add one day to currentDate (when performing algorithm the last day is cut off)
currentDate = currentDate + timedelta(days=1)
currentDate = currentDate.strftime('%Y-%m-%d')
pastTwoYears = currentYear - 2
pastMonthDay = '01-01'
pastDate = str(pastTwoYears)+'-'+str(pastMonthDay)
# Custom Functions ------------------------------------------------------------
def percent_change_symbol(change):
'''
Parameters
----------
change : float
Intended as the percent change in asset price.
Returns
-------
arrow : string
Returns an up arrow for positive percent change; downarrow if negative
return; dash if percent change is zero.
'''
if change < 0:
arrow = '▼'
elif change > 0:
arrow = '▲'
else:
arrow = '-'
return arrow
def get_quote(ticker):
'''
Parameters
----------
ticker : string
User enters the ticker or abbreviation that represents the financial asset
to query from yahoo finance. This function uses the yahoo_fin.stock_info
library.
Returns
-------
ticker : string
Returns the user entered ticker or abbreviation.
quote : float
Current price of the financial asset.
change : string
Current percent change of the financial asset with "%" and arrow string.
'''
table = yaf.get_quote_table(ticker)
quote = table.get('Quote Price')
prevc = table.get('Previous Close')
per_chg = round(((quote - prevc)/prevc)*100,2)
arrow = percent_change_symbol(per_chg)
change = str(per_chg)+'% '+arrow
quote = "{:,}".format(round(quote,2))
#404-Client Error 10/30/2023
#information = yf.Ticker(ticker)
#company_name = information.info['longName']
company_name = ticker
return company_name, quote, change
# AVERAGE TRUE RANGE FOR STOP LOSS --------------------------------------------
def create_atr(df, period=5):
'''
Parameters
----------
df : data frame
data frame with High, Low, and Close of a financial asset to calculate
Average True Range for ATR and Stop Loss.
period : TYPE, optional
DESCRIPTION. The default is 5.
Returns
-------
df : dataframe
returns the original data frame plus a constant ATR value column representing the stop loss line if
one were to enter a trade on the end_date.
'''
high = df['High']
low = df['Low']
prev_close = df['Close'].shift(1)
tr_values = np.maximum.reduce([
high - low,
np.abs(high - prev_close),
np.abs(low - prev_close)
])
atr = pd.Series(tr_values).rolling(window=period).mean()
atr = atr.iloc[-1]
df['atr'] = atr
# Assuming you have 'daily' DataFrame with 'High', 'Low', and 'Close' columns
df['StopLoss'] = df['Close'].iloc[-1] - (df['atr'].iloc[-1] * 2)
return df
# EMA and BOLLINGER BAND Function ---------------------------------------------
# Technical Analysis Functions ################################################
def ema(df, field, period):
'''
Parameters
----------
dataframe : pandas data frame
Data frame with column user wants to perform Exponential Moving Average
calculation on.
field : string
The field name of the data frame the user wants to calculate ema on.
period : int
The number of periods for ema calculation.
Returns
-------
ema : pandas Series float64
The pandas series housing the ema calulation.
'''
ema = df[f'{field}'].ewm(span=period, adjust=False).mean()
return ema
def create_ema_and_bollinger_bands(df, period = 20):
'''
Parameters
----------
df : data frame
data frame to create ema and bollinger bands columns in.
Returns
-------
df : data frame
data frame with original columns plus ema and bollinger bands lower and upper.
'''
df[f'exp{period}'] = ema(df, 'Close', period)
# Calculate the standard deviation
df[f'std_dev{period}'] = df[f'exp{period}'].std()
# Calculate the upper Bollinger Band
df[f'upper_band{period}'] = df[f'exp{period}'] + 1 * df[f'std_dev{period}']
# Calculate the lower Bollinger Band
df[f'lower_band{period}'] = df[f'exp{period}'] - 1 * df[f'std_dev{period}']
return df
# Back testing function compare Bought Held versus Algorithm performance ------
def back_test(df):
# Bought and Held Performance -------------------------------------------------------
# CALCULATE: WHAT IF I BOUGHT AND HELD
balance = 10000 #Assume starting investment of $10,000
df['Bought_And_Held'] = df['Close'] / df['Close'].iloc[0] * balance
Bought_And_Held_Return = df['Bought_And_Held'].iloc[-1]
Bought_And_Held_Percent = round(((Bought_And_Held_Return - balance)/balance)*100,2)
Bought_And_Held_Percent_Sign = percent_change_symbol(Bought_And_Held_Percent)
Bought_And_Held_Return = f"${Bought_And_Held_Return:,.2f}"
Bought_And_Held_Percent = f"{Bought_And_Held_Percent}% {Bought_And_Held_Percent_Sign}"
win_count = 0
total_trades = 0
previous_action = None
# Back testing Algorithm performance
buy_sell_returns = []
stocks_held = 0
for _,row in df.iterrows():
if row['action'] == 'Buy':
stocks_bought = balance / row['Close']
balance = 0
stocks_held += stocks_bought
elif row['action'] == 'Sell':
balance += stocks_held * row['Close']
stocks_held = 0
elif row['action'] == 'Hold':
pass
buy_sell_returns.append(balance + stocks_held * row['Close'])
df['Buy_Sell_Return'] = buy_sell_returns
balance = 10000 #Assume starting investment of $10,000
Buy_Sell_Return = df['Buy_Sell_Return'].iloc[-1]
Max_Draw_Down = min(df['Buy_Sell_Return'])
Max_Draw_Down_Percent = round(((Max_Draw_Down - balance)/balance)*100,2)
Max_Draw_Down_Percent_Sign = percent_change_symbol(Max_Draw_Down_Percent)
Max_Draw_Down = f"${Max_Draw_Down:,.2f}"
Max_Draw_Down_Percent = f"{Max_Draw_Down_Percent}% {Max_Draw_Down_Percent_Sign}"
Buy_Sell_Percent_Return = round(((Buy_Sell_Return - balance)/balance)*100,2)
Buy_Sell_Percent_Return_Sign = percent_change_symbol(Buy_Sell_Percent_Return)
Buy_Sell_Return = f"${Buy_Sell_Return:,.2f}"
Buy_Sell_Percent_Return = f"{Buy_Sell_Percent_Return}% {Buy_Sell_Percent_Return_Sign}"
return df, Bought_And_Held_Return, Bought_And_Held_Percent, Buy_Sell_Return, Buy_Sell_Percent_Return, Max_Draw_Down, Max_Draw_Down_Percent
# Algorithms for trading ------------------------------------------------------
def mean_reversion_algorithm(ticker, start_date = pastDate, end_date = currentDate, interval = '1d'):
'''
Parameters
----------
ticker : string
the ticker or abbreviation of the financial asset.
start_date : date, optional
the start date for historic data. The default is pastDate.
end_date : date, optional
the end date for historic data. The default is currentDate.
Returns
-------
df : data frame
stores the yahoo finance high, low, open, close, adj close, and vol
along with the technical analysis and buy, sell, or hold decision
based on the mie algorithm.
Bought_And_Held_Return : str
The dollar value of the bought and held strategy.
Bought_And_Held_Percent : str
The percent change of the bought and held strategy.
Buy_Sell_Return : str
The dollar value of the mie algorithm strategy.
Buy_Sell_Percent_Return : str
The percent change of the mie algorithm strategy.
'''
start_time = time.time()
# Gather historic prices from yahoo finance
try:
df = yf.download(ticker, start_date, end_date, interval)
except:
error = f'{ticker} could not be found. Symbol may be incorrect or delisted. Please consult Yahoo Finance symbols.'
return error
company_name, last_price, last_change = get_quote(ticker)
#technical analysis -------------------------------------------------------
df = create_ema_and_bollinger_bands(df,period = 128)
df = create_atr(df, 5)
#buy sell signal logic ----------------------------------------------------
buy_signal = []
sell_signal = []
action = []
# Extract relevant columns for calculations
low = df['Low']
high = df['High']
close = df['Close']
bollingerlower = df['lower_band128']
bollingerupper = df['upper_band128']
for i in range(len(df)):
# gathering data points one day at a time
low_val = low[i]
high_val = high[i]
close_val = close[i]
bollingerlower_val = bollingerlower[i]
bollingerupper_val = bollingerupper[i]
bullish_mean_reversion = close_val < bollingerlower_val
bearish_mean_reversion = close_val > bollingerupper_val
if bullish_mean_reversion:
buy_signal.append(low_val * 0.95) #ensures the buy signal does not overlap the candle stick
sell_signal.append(np.nan) #null for sell signal since this is a buy signal scenario
action.append('Buy')
elif bearish_mean_reversion:
buy_signal.append(np.nan)
sell_signal.append(high_val * 1.05) #ensures the sell signal does not overlap the candle stick
action.append('Sell')
else:
#none of the criteria from above occured so the action is Hold
buy_signal.append(np.nan)
sell_signal.append(np.nan)
action.append('Hold')
df['buy_signal'] = buy_signal
df['sell_signal'] = sell_signal
df['action'] = action
# Back testing Algorithm performance
df, Bought_And_Held_Return, Bought_And_Held_Percent, Buy_Sell_Return, Buy_Sell_Percent_Return, Max_Draw_Down, Max_Draw_Down_Percent = back_test(df)
end_time = time.time()
execution_time = str(round(end_time - start_time,2))+' seconds'
df_stats = {
f'{ticker}':[company_name],
'Last Price':[last_price],
'Last % Chg':[last_change],
'Assume Initial Investment':['$10,000'],
f'{ticker} $ Performance':[Bought_And_Held_Return],
f'{ticker} % Performance':[Bought_And_Held_Percent],
'Intrendias $ Performance':[Buy_Sell_Return],
'Intrendias % Performance':[Buy_Sell_Percent_Return],
'Max $ Draw Down':[Max_Draw_Down],
'Max % Draw Down':[Max_Draw_Down_Percent],
'Algorithm Speed':[execution_time]
}
df_stats = pd.DataFrame(df_stats)
df_stats = df_stats.T
# Reset the index and make it a new column
df_stats = df_stats.reset_index()
df_stats = df_stats.rename(columns={0: 'Data', 'index':'Info'})
return df, df_stats, action, Bought_And_Held_Percent, Buy_Sell_Percent_Return
df, df_stats, action, Bought_And_Held_Percent, Buy_Sell_Percent_Return = mean_reversion_algorithm(
ticker = 'AAPL',
start_date = pastDate,
end_date = currentDate,
interval = '1d'
)
df
def chart_from_mean_reversion(ticker, start_date, end_date, interval = '1d'):
df, df_stats, action, Bought_And_Held_Percent, Buy_Sell_Percent_Return = mean_reversion_algorithm(
ticker = ticker,
start_date = start_date,
end_date = end_date,
interval = interval
)
columns = [{'name': col, 'id': col} for col in df_stats.columns]
data = df_stats.to_dict('records')
# BUY SELL SIGNALS
# Plot the candlestick chart
# Plotting the candlestick chart
fig, ax = plt.subplots(figsize=(20, 6))
# Candlestick plot
ax.plot(df.index, df['Open'], label='Open', color='black', linestyle='dashed', linewidth=1)
ax.plot(df.index, df['Close'], label='Close', color='black', linestyle='dashed', linewidth=1)
ax.vlines(df.index, df['Low'], df['High'], color='black')
# Scatter plot for buy signals
buy_signals = df[df['action'] == 'Buy']
ax.scatter(buy_signals.index, buy_signals['Low']*0.95, marker='^', color='green', label='Buy Signal')
# Scatter plot for sell signals
sell_signals = df[df['action'] == 'Sell']
ax.scatter(sell_signals.index, sell_signals['High']*1.05, marker='v', color='red', label='Sell Signal')
# Set labels and title
ax.set_xlabel('Date')
ax.set_ylabel('Price')
ax.set_title('Candlestick Chart with Buy/Sell Signals')
# Display legend
ax.legend()
# Show the plot
plt.show()
# Plot stock and algorithm performance
fig2, ax2 = plt.subplots(figsize=(20, 6))
ax2.plot(df.index, df['Bought_And_Held'], label=f'{ticker}', color='gray')
ax2.plot(df.index, df['Buy_Sell_Return'], label='Intrendias', color='blue')
ax2.set_title(f'Performance for {ticker} between {pastDate} and {currentDate}')
ax2.set_xlabel('Date')
ax2.set_ylabel('Price')
ax2.legend()
plt.show()
chart_from_mean_reversion(ticker = 'TSLA', start_date = pastDate, end_date = currentDate, interval = '1d')
What is Mean Reversion?
Mean reversion is a financial concept that refers to the tendency of a financial instrument's price, such as a stock, bond, or commodity, to move toward the average or mean over time. In other words, when the price of an asset deviates significantly from its historical average, there is an expectation that it will eventually revert to its average or historical mean.
This concept is often applied in various financial and economic contexts, and it's based on the belief that extreme price movements are temporary and that the price will eventually move back towards its long-term average or trend. Traders and investors who follow mean reversion strategies may take positions based on the expectation that if an asset's price has moved too far from its historical average, it is likely to move back in the opposite direction.
It's important to note that mean reversion is a general concept, and the specific mechanisms behind price movements can vary depending on the asset, market conditions, and other factors. While mean reversion can be observed in some financial markets, it's not a universal law, and prices can also exhibit trends or momentum over extended periods.
Real Life Example of Mean Reversion
Below we see the candle chart for Delta stock DAL on the one day interval. The chart also has the 128 day Simple Moving Average (SMA) with upper and lower Bollinger Bands.
What is a Simple Moving Average?
A Simple Moving Average or SMA is a commonly used statistical calculation that is used to analyze data points by creating a series of averages of different subset of the full data set - in this case the close price of DAL stock.
What are Bollinger Bands?
Bollinger Bands are technical analysis tools introduced by John Bollinger. They consist of a set of three bands plotted on a price chart. These bands are based on the volatility of a financial instrument and are used to analyse and identity portential overbought or oversold conditions, as well as to gauge the likelihood of future price movements. The three components of the Bollinger Bands are SMA, Upper band, and Lower band where the bands are calculated by adding a specified number of standard deviations (usually two).

Here we see instances where the close price return back to the long-term mean. When the close price of Delta surpasses the upper bollinger band there is a repeated tendancy to return downward; when the stock price dips below the lower bollinger band, the price has a bullish return upward.
The code above ends with a python function where the user can input the financial asset's symbol like a stock ticker "AAPL" or crypto symbol "BTC-USD" to return buy and sell signals and backtesting results. Happy coding! If you enjoy algorithmic trading and coding in python, then consider subscribing to the YouTube channel to not miss another video! Thank you for reading and using Intrendias!
Helpful Tutorial Series?
Yes
No




My issue fixed after downgrading panda by running
pip install "pandas<2"
So it is working. Thank you intrendias
table = yaf.get_quote_table(ticker) failed
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) ~\AppData\Local\Temp\ipykernel_35748\2554079129.py in ?() ----> 1 df, df_stats, action, Bought_And_Held_Percent, Buy_Sell_Percent_Return = mean_reversion_algorithm( 2 ticker = 'AAPL', 3 start_date = pastDate, 4 end_date = currentDate, ~\AppData\Local\Temp\ipykernel_35748\1035753292.py in ?(ticker, start_date, end_date, interval) 33 except: 34 error = f'{ticker} could not be found. Symbol may be incorrect or delisted. Please consult Yahoo Finance symbols.' 35 return error 36 ---> 37 company_name, last_price, last_change = get_quote(ticker) 38 39 #technical analysis ------------------------------------------------------- 40 df = create_ema_and_bollinger_bands(df,period = 128) ~\AppData\Local\Temp\ipykernel_35748\471745816.py in ?(ticker) 15 Current price of the financial asset. 16 change : string 17 Current percent change of the financial asset with "%" and arrow string. 18 ''' ---> 19 table = yaf.get_quote_table(ticker) 20 quote = table.get('Quote Price') 21 prevc = table.get('Previous Close') 22 per_chg = round(((quote - prevc)/prevc)*100,2) C:\anaconda3\Lib\site-packages\yahoo_fin\stock_info.py in ?(ticker, dict_result, headers) 291 site = "https://finance.yahoo.com/quote/" + ticker + "?p=" + ticker 292 293 tables = pd.read_html(requests.get(site, headers=headers).text) 294 --> 295 data = tables[0].append(tables[1]) 296 297 data.columns = ["attribute" , "value"] 298 C:\anaconda3\Lib\site-packages\pandas\core\generic.py in ?(self, name) 5985 and name not in self._accessors 5986 and self._info_axis._can_hold_identifiers_and_holds_name(name) 5987 ): 5988 return self[name] -> 5989 return object.__getattribute__(self, name)…
Hi, it seen like the download link is invalid. can not get it downloaded. Any clues ?