Momentum and Reversion Trading Signals Analysis (2024)

In the world of quantitative trading, various strategies are employed to gain a competitive edge in financial markets. Most popular trading strategies involve momentum and trend - being popular means they will not generate much alpha, unless you're planning to sell these as youtube Financial courses.

Momentum and trend approach capitalizes on the idea that assets that have exhibited recent price strength, will continue to do so in the near future.

There is a slight detail between trend and momentum worth noting:

  • Trend-following is directional absolute returns that looks at beta across the market.
  • Momentum is relative returns and market-neutral as it looks at a class or sector cross-section.

Reversion is the assumption that prices reverts back to a mean or means, over a period of time.

In this article, we will explore these strategies, and provide Python code implementations for you to try out.

We will keep to the latest post-pandemic market regime, therefore most stock prices will start in the 2021s. All strategies are long-only, and unleveraged.

Prepare Your Python

Have a jupyter environment ready, and pip install these libraries:

  • numpy
  • pandas
  • yfinance

Momentum & Trend Strategies

Let's describe it with an example, say if an asset's price has risen for the past week, it's likely to continue upward. This approach capitalizes on the belief that the future mirrors the past, either in an upward or downward trend. As the WSB crowd used to say, stonks only go up.

While the strategy is easy to grasp and implement, it does have drawbacks:

  • Ignores market noise and special events, potentially smoothing out important fluctuations.
  • Transaction fees can accumulate due to frequent orders.
  • Everyone does it, there is little to no edge.

Let's implement a few of these strategies.

Simple Moving Average (SMA) Crossover

The Moving Average Crossover strategy involves calculating two or more moving averages for an asset's price: a short-term moving average (called a fast SMA) and a long-term moving average (slow SMA).

The code below implements this strategy:

def double_simple_moving_average_signals(ticker_ts_df, short_window=5, long_window=30): """ Generate trading signals based on a double simple moving average (SMA) strategy. Parameters: - aapl_ts_df (pandas.DataFrame): A DataFrame containing historical stock data. - short_window (int): The window size for the short-term SMA. - long_window (int): The window size for the long-term SMA. Returns: - signals (pandas.DataFrame): A DataFrame containing the trading signals. """ signals = pd.DataFrame(index=ticker_ts_df.index) signals['signal'] = 0.0 signals['short_mavg'] = ticker_ts_df['Close'].rolling(window=short_window, min_periods=1, center=False).mean() signals['long_mavg'] = ticker_ts_df['Close'].rolling(window=long_window, min_periods=1, center=False).mean() # Generate signal when SMAs cross signals['signal'] = np.where( signals['short_mavg'] > signals['long_mavg'], 1, 0) signals['orders'] = signals['signal'].diff() signals.loc[signals['orders'] == 0, 'orders'] = None return signals 

This function takes a timeseries of any stock, and sets a short (fast SMA) and long (slow SMA) rolling windonw, and compares the 2 across the timeline. When the fast SMA is higher, a buy signal is generated (1), and when it's lower, a sell signal is produced (-1).

We will create some utility functions to calculate our capital over the strategy's timeline, and a graphing function to help us visualize the entries and exit signals against the stock's timeseries.

def load_ticker_ts_df(ticker, start_date, end_date): """ Load and cache time series financial data from Yahoo Finance API. Parameters: - ticker (str): The stock ticker symbol (e.g., 'AAPL' for Apple Inc.). - start_date (str): The start date in 'YYYY-MM-DD' format for data retrieval. - end_date (str): The end date in 'YYYY-MM-DD' format for data retrieval. Returns: - df (pandas.DataFrame): A DataFrame containing the financial time series data. """ dir_path = './data' cached_file_path = f'{dir_path}/{ticker}_{start_date}_{end_date}.pkl' try: if os.path.exists(cached_file_path): df = pd.read_pickle(cached_file_path) else: df = yf.download(ticker, start=start_date, end=end_date) if not os.path.exists(dir_path): os.makedirs(dir_path) df.to_pickle(cached_file_path) except FileNotFoundError: print( f'Error downloading and caching or loading file with ticker: {ticker}') return dfdef calculate_profit(signals, prices): """ Calculate cumulative profit based on trading signals and stock prices. Parameters: - signals (pandas.DataFrame): A DataFrame containing trading signals (1 for buy, -1 for sell). - prices (pandas.Series): A Series containing stock prices corresponding to the signal dates. Returns: - cum_profit (pandas.Series): A Series containing cumulative profit over time. """ profit = pd.DataFrame(index=prices.index) profit['profit'] = 0.0 buys = signals[signals['orders'] == 1].index sells = signals[signals['orders'] == -1].index while sells[0] < buys[0]: # These are long only strategies, we cannot start with sell sells = sells[1:] if len(buys) == 0 or len(sells) == 0: # no actions taken return profit if len(sells) < len(buys): # Assume we sell at the end sells = sells.append(pd.Index(prices.tail(1).index)) buy_prices = prices.loc[buys] sell_prices = prices.loc[sells] profit.loc[sells, 'profit'] = sell_prices.values - buy_prices.values profit['profit'] = profit['profit'].fillna(0) # Make profit cumulative profit['cum_profit'] = profit['profit'].c*msum() return profit['cum_profit']def plot_strategy(prices_df, signal_df, profit): """ Plot a trading strategy with buy and sell signals and cumulative profit. Parameters: - prices (pandas.Series): A Series containing stock prices. - signals (pandas.DataFrame): A DataFrame with buy (1) and sell (-1) signals. - profit (pandas.Series): A Series containing cumulative profit over time. Returns: - ax1 (matplotlib.axes.Axes): The top subplot displaying stock prices and signals. - ax2 (matplotlib.axes.Axes): The bottom subplot displaying cumulative profit. """ fig, (ax1, ax2) = plt.subplots(2, 1, gridspec_kw={'height_ratios': (3, 1)}, figsize=(18, 12)) ax1.set_xlabel('Date') ax1.set_ylabel('Price in $') ax1.plot(prices_df.index, prices_df, color='g', lw=0.25) # Plot the Buy and Sell signals ax1.plot(signal_df.loc[signal_df.orders == 1.0].index, prices_df[signal_df.orders == 1.0], '^', markersize=12, color='blue', label='Buy') ax1.plot(signal_df.loc[signal_df.orders == -1.0].index, prices_df[signal_df.orders == -1.0], 'v', markersize=12, color='red', label='Sell') ax2.plot(profit.index, profit, color='b') ax2.set_ylabel('Cumulative Profit (%)') ax2.set_xlabel('Date') return ax1, ax2 

Let's run everything together:

aapl_ts_df = load_ticker_ts_df('AAPL', start_date='2021-01-01', end_date='2023-01-01')signal_df = double_simple_moving_average_signals(aapl_ts_df, 5, 30)profit_series = calculate_profit(signal_df, aapl_ts_df["Adj Close"])ax1, ax2 = plot_strategy(aapl_ts_df["Adj Close"], signal_df, profit_series)# Add short and long moving averagesax1.plot(signal_df.index, signal_df['short_mavg'], linestyle='--', label='Fast SMA')ax1.plot(signal_df.index, signal_df['long_mavg'], linestyle='--', label='Slow SMA')ax1.legend(loc='upper left', fontsize=10)plt.show() 
Momentum and Reversion Trading Signals Analysis (1)

Throughout 2 years, this strategy gave us 30% return. Comparing it agianst the S&P 500's latest return of 10% - that is quite a good strategy.

Naive Momentum

This strategy is based on the number of times a price increases or decreases. It assumes that when a price increases for a certain number of consecutive days, it's a signal to buy, and when it decreases, it's a signal to sell.

We willl reuse some of the utility functions from the SMA strategy above.

Here's the code to implement a naive version of it:

def naive_momentum_signals(ticker_ts_df, nb_conseq_days=2): """ Generate naive momentum trading signals based on consecutive positive or negative price changes. Parameters: - ticker_ts_df (pandas.DataFrame): A DataFrame containing historical stock data. - nb_conseq_days (int): The number of consecutive positive or negative days to trigger a signal. Returns: - signals (pandas.DataFrame): A DataFrame with 'orders' column containing buy (1) and sell (-1) signals. """ signals = pd.DataFrame(index=ticker_ts_df.index) signals['orders'] = 0 price = ticker_ts_df['Adj Close'] price_diff = price.diff() signal = 0 cons_day = 0 for i in range(1, len(ticker_ts_df)): if price_diff[i] > 0: cons_day = cons_day + 1 if price_diff[i] > 0 else 0 if cons_day == nb_conseq_days and signal != 1: signals['orders'].iloc[i] = 1 signal = 1 elif price_diff[i] < 0: cons_day = cons_day - 1 if price_diff[i] < 0 else 0 if cons_day == -nb_conseq_days and signal != -1: signals['orders'].iloc[i] = -1 signal = -1 return signalssignal_df = naive_momentum_signals(aapl_ts_df)profit_series = calculate_profit(signal_df, aapl_ts_df["Adj Close"])ax1, _ = plot_strategy(aapl_ts_df["Adj Close"], signal_df, profit_series)ax1.legend(loc='upper left', fontsize=10)plt.show() 

Recommended by LinkedIn

Occam's Razor can guide the development of a… George Pruitt 4 months ago
Unlock the Secrets of Quantitative Trading: From Math… Victor Ma 1 year ago
From Sharpe Ratio to Max-Drawdown, a numerical… Francesco Landolfi 1 year ago
Momentum and Reversion Trading Signals Analysis (5)

This strategy is a losing one, it didn't give us any returns.

We would have done better inversing this model (just like you ought to do with most of WSB's stock analysis). Hint to achieve this: signals['orders'] = signals['orders'] * -1

Granted, this strategy is usually based on the market in general rather than a single instrument, and is in a shorter timeframe.

Reversion Strategies

An example: Elon tweets that he will install blockchain in teslas, the market gets overzelous in its buying of Tesla stock.

The next day, everyone realizes that fundamentally nothing has changed, so the market loses interest and the price reverts back to an acceptable level.

Therefore, any instrument that divereges too fast from a benchmark in either direction, will eventually revert back to the benchmark in the longer timeframe.

Just like trends and momentum - this is a simple strategy, it smooths everything and is gernerally used by all market participants.

Mean Reversion

Here we assume that the price of a stock will stay in the vicinity of its mean.

Below is the signal code:

def mean_reversion_signals(ticker_ts_df, entry_threshold=1.0, exit_threshold=0.5): """ Generate mean reversion trading signals based on moving averages and thresholds. Parameters: - ticker_ts_df (pandas.DataFrame): A DataFrame containing historical stock data. - entry_threshold (float): The entry threshold as a multiple of the standard deviation. - exit_threshold (float): The exit threshold as a multiple of the standard deviation. Returns: - signals (pandas.DataFrame): A DataFrame with 'orders' column containing buy (1) and sell (-1) signals. """ signals = pd.DataFrame(index=ticker_ts_df.index) signals['mean'] = ticker_ts_df['Adj Close'].rolling( window=20).mean() # Adjust the window size as needed signals['std'] = ticker_ts_df['Adj Close'].rolling( window=20).std() # Adjust the window size as needed signals['signal'] = np.where(ticker_ts_df['Adj Close'] > ( signals['mean'] + entry_threshold * signals['std']), 1, 0) signals['signal'] = np.where(ticker_ts_df['Adj Close'] < ( signals['mean'] - exit_threshold * signals['std']), -1, 0) signals['orders'] = signals['signal'].diff() signals.loc[signals['orders'] == 0, 'orders'] = None return signals 

In this function, we find the standard deviation and the mean.

if the price diverges from its mean by a standard deviaion and a factor, it will generate a signal.

Let's test it together with the functions we created:

signal_df = mean_reversion_signals(aapl_ts_df)profit_series = calculate_profit(signal_df, aapl_ts_df["Adj Close"])ax1, _ = plot_strategy(aapl_ts_df["Adj Close"], signal_df, profit_series)ax1.plot(signal_df.index, signal_df['mean'], linestyle='--', label="Mean")ax1.plot(signal_df.index, signal_df['mean'] + signal_df['std'], linestyle='--', label="Ceiling STD")ax1.plot(signal_df.index, signal_df['mean'] - signal_df['std'], linestyle='--', label="Floor STD")ax1.legend(loc='upper left', fontsize=10)plt.show() 
Momentum and Reversion Trading Signals Analysis (6)

10% on paper is not bad return, though in truth you would have done just as well using an S&P500 broad index.

In future articles, we will look at more advanced versions of reversion strategies, mainly: Pair-Trading and Statistical-Arbitrage. We will also look at strategies' metrics, like the sharpe-ratio, which is why this strategy is weak, even though it returned the same as the S&P500.

Conclusion

We've studied simple stratgies of momentum and mean reversion trading strategies, and used python to make an analysis. Momentum strategies harness the power of trends to predict future price movements. On the other hand, mean reversion strategies are grounded in the idea that price or returns tend to revert to the mean after experiencing extreme movements.

In real portoflio trading, no single strategy guarantees success, unless you are shilling it on youtube as a finfluencer!

Momentum and Reversion Trading Signals Analysis (7)

References

  • https://numpy.org/doc/stable/reference/
  • https://www.investopedia.com/terms/t/trendtrading.asp
  • https://www.investopedia.com/terms/m/meanreversion.asp

Github

Article here is also available on Github

Kaggle notebook available here

Media

All media used (in the form of code or images) are either solely owned by me, acquired through licensing, or part of the Public Domain and granted use through Creative Commons License.

CC Licensing and Use

This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.

Made with :heartpulse: by Adam

Momentum and Reversion Trading Signals Analysis (2024)
Top Articles
Win/Loss Statement W/Jackpots
New iPhone 15 Pro or Pro Max? Do These 3 Things First When Setting Up
3 Tick Granite Osrs
Friskies Tender And Crunchy Recall
Uca Cheerleading Nationals 2023
Pangphip Application
Kokichi's Day At The Zoo
T Mobile Rival Crossword Clue
Blue Beetle Showtimes Near Regal Swamp Fox
Notisabelrenu
Morgan And Nay Funeral Home Obituaries
New Stores Coming To Canton Ohio 2022
Lancasterfire Live Incidents
Craigslist Mt Pleasant Sc
Ukc Message Board
Heart and Vascular Clinic in Monticello - North Memorial Health
Heart Ring Worth Aj
Johnnie Walker Double Black Costco
Ford F-350 Models Trim Levels and Packages
Talk To Me Showtimes Near Marcus Valley Grand Cinema
Www.paystubportal.com/7-11 Login
Gina Wilson Angle Addition Postulate
Fleet Farm Brainerd Mn Hours
Sand Dollar Restaurant Anna Maria Island
Mikayla Campinos: Unveiling The Truth Behind The Leaked Content
Table To Formula Calculator
Evil Dead Rise Ending Explained
Co10 Unr
Busch Gardens Wait Times
417-990-0201
Bursar.okstate.edu
Mrstryst
Egg Crutch Glove Envelope
Cbs Trade Value Chart Week 10
Lil Durk's Brother DThang Killed in Harvey, Illinois, ME Confirms
Vanessa West Tripod Jeffrey Dahmer
The Bold And The Beautiful Recaps Soap Central
3400 Grams In Pounds
Bbc Gahuzamiryango Live
Cheetah Pitbull For Sale
Linda Sublette Actress
Nsav Investorshub
Wait List Texas Roadhouse
Letter of Credit: What It Is, Examples, and How One Is Used
Pain Out Maxx Kratom
Vérificateur De Billet Loto-Québec
Movie Hax
What your eye doctor knows about your health
Where and How to Watch Sound of Freedom | Angel Studios
Divisadero Florist
Latest Posts
Article information

Author: Otha Schamberger

Last Updated:

Views: 5629

Rating: 4.4 / 5 (55 voted)

Reviews: 94% of readers found this page helpful

Author information

Name: Otha Schamberger

Birthday: 1999-08-15

Address: Suite 490 606 Hammes Ferry, Carterhaven, IL 62290

Phone: +8557035444877

Job: Forward IT Agent

Hobby: Fishing, Flying, Jewelry making, Digital arts, Sand art, Parkour, tabletop games

Introduction: My name is Otha Schamberger, I am a vast, good, healthy, cheerful, energetic, gorgeous, magnificent person who loves writing and wants to share my knowledge and understanding with you.