SoFunction
Updated on 2024-10-30

Stock Price Chart Pattern Recognition Implementation Using Python Code

main body (of a book)

In the dynamic environment of stock market trading, the convergence of technology and finance has given rise to advanced methods for analyzing market trends and predicting future price movements, in this paper we will use Python for stock pattern recognition

from collections import defaultdict
 
 import numpy as np
 import pandas as pd
 import  as plt
 from  import argrelextrema
 from .kernel_regression import KernelReg
 from yahoofinancials import YahooFinancials

Introduction to python libraries

There are a few key points to cover in the library above:

Returns the default value when the key is missing. Use it to store and organize data efficiently, such as keys reflecting identifiable metrics such as dates or asset symbols, and values representing corresponding variables.

The argrelextrema function is a function in the SciPy library used to perform scientific and technical calculations. It helps to identify local maxima and minima in price data, indicate potential turning points or support and resistance levels in price data.

.kernel_regression.KernelReg: This submodule from statmodels provides non-parametric kernel regression. Trades can use this to fit a smoothed curve to price data to determine a trend without assuming that the curve has a specific parametric form.

YahooFinancials:This module accesses financial data from Yahoo Finance. We have access to a large amount of financial data, including stock prices, financial statements and other market data, which can be used to analyze and decide what to do with a portfolio.

start_date = '2017-01-01'
 end_date = '2017-12-31'
 stock_code = 'FB' # . AMZN, GOOG, FB, NVDA

We obtained stock data for the period 2017-01-01 through 2017-12-31. As the stock_code variable, Facebook, Inc. is set to FB, the stock's code.

Within the specified date range, the trading algorithm will perform operations such as data analysis, trading signals, or actual trades for that stock code. The purpose of this code is to establish the basic parameters for the trading algorithm: the target time frame and the specific stock to be traded.

Variables will eventually be used in code to obtain historical data, perform financial analysis and backtest trading strategies. For any trading system focused on the stock market, these parameters are key inputs for evaluating historical performance and executing real-time trades.

def preprocess_data(start_date, end_date, stock_code):
     stock_data = YahooFinancials(stock_code).get_historical_price_data(start_date, end_date, 'daily')
     price_data = stock_data[stock_code]['prices']
     columns = ['formatted_date', 'open', 'high', 'low', 'close', 'adjclose', 'volume']
     new_columns = ['Date', 'Open', 'High', 'Low', 'Close', 'Adj Close', 'Volume']
     df = (data=price_data)[columns] # order dataframe columns
     df = (index=str, columns=dict(zip(columns, new_columns))) # rename dataframe columns
     return df, df['Close'], df['Date']

The preprocess_data parameter

preprocess_data has three parameters: start_date, end_date, and stock_code, which specify the time range and stock type. The main goal of this function is to retrieve historical stock prices from Financials for a given stock for a specified date range.

Obtain comprehensive financial information including daily stock prices, opening prices, high and low prices, and adjusted closing prices. After obtaining the data and organizing it into a pandas DataFrame.

By renaming columns, better readability and consistency with common financial data standards can be achieved. The function returns the processed DataFrame as well as two Series one-dimensional arrays containing the closing price and the date on which the closing price occurred.

df, prices, dates = preprocess_data(start_date, end_date, stock_code)
  = (1, len(prices), len(prices))
  = (1, len(dates), len(dates))

We index two sets of data (price and date). Then comes the analysis of prices and the identification of local maxima and minima, which is invaluable to traders. The code uses a core regression model that eliminates cyclical price fluctuations, making it easier to spot important trends.

# /doi/full/10.1111/0022-1082.00265
 # reference: /posts/an-empirical-algorithmic-evaluation-of-technical-analysis
 def find_max_min(prices):
     model = KernelReg(, , var_type='c', bw='cv_ls')
     smooth_prices = (data=([])[0], index=) # index also from 1
     # use the minima and maxima from the smoothed timeseries
     # to identify true local minima and maxima in the original timeseres
     # by taking the maximum/minimum price within a t-1, t+1 window in the smoothed timeseries
     smooth_prices_max_indices = argrelextrema(smooth_prices.values, )[0]
     smooth_prices_min_indices = argrelextrema(smooth_prices.values, )[0]
     price_max_indices = []
     for i in smooth_prices_max_indices:
         if 1 < i < len(prices)-1:
             price_max_indices.append([i-2:i+2].idxmax())
     price_min_indices = []
     for i in smooth_prices_min_indices:
         if 1 < i < len(prices)-1:
             price_min_indices.append([i-2:i+2].idxmin())
     price_max = [price_max_indices]
     price_min = [price_min_indices]
     max_min = ([price_max, price_min]).sort_index()
     max_min = max_min[~max_min.duplicated()] # deduplicate points that are both maximum and minimum
     max_min
     return smooth_prices, smooth_prices_max_indices, smooth_prices_min_indices, \
             price_max_indices, price_min_indices, max_min

Algorithm identifies points where the price curve changes direction based on smoothed price data

Using an algorithm to identify points where the price curve changes direction based on smoothed price data, the code searches for relative maxima and minima in this smoothed time series. The code attempts to map these extreme values back to the original non-smoothed price data after finding them in the smoothed data.

It does this by examining the small window around each extreme point in the smoothed data and determining the highest or lowest price within that window-these are the true local maximums and minimums. After the smoothing and windowing process is complete, the code organizes these points into a cohesive output that removes any duplicate points that may be present at both the maximum and minimum values.

The results can be used to determine entry and exit points for trades. In addition to being used in code, the code can also be used in larger strategies to trigger buy or sell signals based on these findings.

smooth_prices, smooth_prices_max_indices, smooth_prices_min_indices, \
             price_max_indices, price_min_indices, max_min = find_max_min(prices

smooth_prices package

smooth_prices contains a smoothed version of the price data that removes noise and makes trends easier to recognize.

There are a variety of techniques available for smoothing, including moving averages and other algorithms. The variables smooth_prices_max_indices and smooth_prices_min_indices may indicate the position of the smoothed price index in a list of local maxima and minima. When prices reach these levels, it is critical to recognize potential buy or sell signals before prices reverse. As with the previous variables, price_max_indices and price_min_indices are calculated from raw, unsmoothed prices.

max_min may be an array or list containing information about identified maxima and minima, possibly combined with smoothed and unsmoothed data, used to determine whether to enter or exit a position based on local price extremes. Financial price data can be analyzed to identify peaks and troughs and prepare the data for use in algorithmic trading. As part of a larger technical analysis system, it can be used for automated trading activities based on historical price patterns.

Let's look at the results obtained from the above code calculations:

fig, ax = (figsize=(20,10), dpi=200)
 (dates, prices, label='Prices')
 (dates, smooth_prices, label='Smoothed Prices', linestyle='dashed')
 ax.set_xticks((0, len(dates), 30))
 smooth_prices_max = smooth_prices.loc[smooth_prices_max_indices]
 smooth_prices_min = smooth_prices.loc[smooth_prices_min_indices]
 price_max = [price_max_indices]
 price_min = [price_min_indices]
 ([smooth_prices_max.index], smooth_prices_max.values, s=20, color='red', label='Smoothed Prices Maxima')
 ([smooth_prices_min.index], smooth_prices_min.values, s=20, color='purple', label='Smoothed Prices Minima')
 ([price_max.index], price_max.values, s=50, color='green', label='Prices Maxima')
 ([price_min.index], price_min.values, s=50, color='blue', label='Prices Minima')
 (loc='upper left')
 ()

The code plots actual and smoothed prices with different line styles. The graph also shows the location of local maxima and minima in the actual and smoothed price data, potentially identifying trade entry and exit signals.

To distinguish between maximum and minimum values, larger symbols and different colors are used. The time axis is displayed on the x-axis at intervals to make it clearer. The legend of the chart explains the plot elements and the grid helps to analyze the price changes over time, which are essential tasks in plotting.

The next function is Plot_window, which generates a line graph showing actual and smoothed prices over time. Smoothing may help identify trends and filter out noise. Several key points can be distinguished on this graph. Color and size are used to identify local maxima and minima highs and lows of the actual and smoothed price curves. Trading strategies often focus on these key points as they may signal a reversal or continuation of the trend.

def plot_window(dates, prices, smooth_prices, 
                 smooth_prices_max_indices, smooth_prices_min_indices,
                 price_max_indices, price_min_indices, 
                 start, end, ax=None):
     if ax is None: fig, ax = (figsize=(20,10), dpi=200)
 
     ([start:end], [start:end], label='Prices')
     ([start:end], smooth_prices.loc[start:end], label='Smoothed Prices', linestyle='dashed')
     ax.set_xticks((0, len([start:end]), 10))
     ax.tick_params(axis='x', rotation=45)
 
     smooth_prices_max = smooth_prices.loc[smooth_prices_max_indices].loc[start:end]
     smooth_prices_min = smooth_prices.loc[smooth_prices_min_indices].loc[start:end]
     price_max = [price_max_indices].loc[start:end]
     price_min = [price_min_indices].loc[start:end]
 
     ([smooth_prices_max.index], smooth_prices_max.values, s=20, color='red', label='Smoothed Prices Maxima')
     ([smooth_prices_min.index], smooth_prices_min.values, s=20, color='purple', label='Smoothed Prices Minima')
 
     ([price_max.index], price_max.values, s=50, color='green', label='Prices Maxima')
     ([price_min.index], price_min.values, s=50, color='blue', label='Prices Minima')
     (fontsize='small')
     ()

It is possible to specify a window of time from start to finish in larger data sets so that subsets of the data can be viewed. For clarity, a legend and a grid are displayed along with the date on the x-axis.

plot_window(dates, prices, smooth_prices, 
             smooth_prices_max_indices, smooth_prices_min_indices,
             price_max_indices, price_min_indices, 
             start=18, end=34, ax=None)

simple model

Here are some simple patterns we can look for:

def find_patterns(max_min):
     patterns = defaultdict(list)
     for i in range(5, len(max_min)):
         window = max_min.iloc[i-5:i]
         # pattern must play out in less than 36 days
         if [-1] - [0] > 35:
             continue
         # Using the notation from the paper to avoid mistakes
         e1, e2, e3, e4, e5 = [:5]
         rtop_g1 = ([e1, e3, e5])
         rtop_g2 = ([e2, e4])
         # Head and Shoulders
         if (e1 > e2) and (e3 > e1) and (e3 > e5) and \
             (abs(e1 - e5) <= 0.03*([e1,e5])) and \
             (abs(e2 - e4) <= 0.03*([e1,e5])):
                 patterns['HS'].append(([0], [-1]))
         # Inverse Head and Shoulders
         elif (e1 < e2) and (e3 < e1) and (e3 < e5) and \
             (abs(e1 - e5) <= 0.03*([e1,e5])) and \
             (abs(e2 - e4) <= 0.03*([e1,e5])):
                 patterns['IHS'].append(([0], [-1]))
         # Broadening Top
         elif (e1 > e2) and (e1 < e3) and (e3 < e5) and (e2 > e4):
             patterns['BTOP'].append(([0], [-1]))
         # Broadening Bottom
         elif (e1 < e2) and (e1 > e3) and (e3 > e5) and (e2 < e4):
             patterns['BBOT'].append(([0], [-1]))
         # Triangle Top
         elif (e1 > e2) and (e1 > e3) and (e3 > e5) and (e2 < e4):
             patterns['TTOP'].append(([0], [-1]))
         # Triangle Bottom
         elif (e1 < e2) and (e1 < e3) and (e3 < e5) and (e2 > e4):
             patterns['TBOT'].append(([0], [-1]))
         # Rectangle Top
         elif (e1 > e2) and (abs(e1-rtop_g1)/rtop_g1 < 0.0075) and \
             (abs(e3-rtop_g1)/rtop_g1 < 0.0075) and (abs(e5-rtop_g1)/rtop_g1 < 0.0075) and \
             (abs(e2-rtop_g2)/rtop_g2 < 0.0075) and (abs(e4-rtop_g2)/rtop_g2 < 0.0075) and \
             (min(e1, e3, e5) > max(e2, e4)):
             patterns['RTOP'].append(([0], [-1]))
         # Rectangle Bottom
         elif (e1 < e2) and (abs(e1-rtop_g1)/rtop_g1 < 0.0075) and \
             (abs(e3-rtop_g1)/rtop_g1 < 0.0075) and (abs(e5-rtop_g1)/rtop_g1 < 0.0075) and \
             (abs(e2-rtop_g2)/rtop_g2 < 0.0075) and (abs(e4-rtop_g2)/rtop_g2 < 0.0075) and \
             (max(e1, e3, e5) > min(e2, e4)):
             patterns['RBOT'].append(([0], [-1]))
     return patterns

Iterate over the entries in the DataFrame while considering 5 data points. Determine if the pattern for each 5-point window occurs within 36 days. If not, move to the next window. We have several types of technical analysis chart patterns here.

Head and Shoulders.

This is a reversal chart pattern that usually indicates that a stock is about to reverse in an uptrend. It consists of a middle peak (head) and two lower peaks (shoulders) that form a signal for the end of an uptrend.

Inverse Head and Shoulders(inverted head and shoulders bottom (physics)):

Contrary to a head and shoulders top, this is a bottom reversal chart pattern. It consists of a middle depression (inverted head) and two lower depressions (inverted shoulders) that form a signal for the end of a downtrend.

Broadening Top.

This is a chart pattern that indicates an unstable market, formed by two trendlines spread out. It may indicate an increase in market volatility, signaling price uncertainty.

Broadening Bottom.

The opposite of a spreading top pattern, this is a chart pattern that indicates an unstable market, consisting of two trendlines converging gradually. It may indicate increased market volatility and signal price uncertainty.

Triangle Top.

This is a chart pattern formed in an uptrend by two trendlines converging to form a triangle. It may indicate that the price is about to fall.

Triangle Bottom.

The opposite of a triangle top, this is a chart pattern formed in a downtrend by two trendlines converging to form a triangle. It may indicate that price is about to rise.

Rectangle Top.

This is a chart pattern that forms in an uptrend and consists of horizontal lines forming a rectangle. It indicates that the market may be going through a period of sideways consolidation and that prices may fall.

Rectangle Bottom.

The opposite of a rectangular top, this is a chart pattern that forms in a downtrend and consists of horizontal lines forming a rectangle. It indicates that the market may be going through a period of sideways consolidation and prices may rise.

These patterns above are recognized based on the relative positions and values of these local maxima and minima, and each pattern detected is stored in a dictionary, with the pattern name as the key and the start and end indices of the window as the values. These index tuples are stored in the dictionary at the end of each pattern.

Such code is useful in algorithmic trading when it automatically detects historical patterns associated with certain market behaviors, allowing traders to make informed decisions based on the presence of these patterns.

patterns = find_patterns(max_min)
 patterns

visualization

The above proprietary names may not be easy to understand, so we can visualize them using code

def visualize_patterns(dates, prices, smooth_prices, 
                        smooth_prices_max_indices, smooth_prices_min_indices, 
                        price_max_indices, price_min_indices, 
                        patterns, shorthand_fullname_dict):
     for name, end_day_nums in ():
         print('Pattern Identified: {} \nNumber of Observations: {}'.format(shorthand_fullname_dict[name], len(end_day_nums)))
         rows = int((len(end_day_nums)/2))
         fig, axes = (rows, 2, figsize=(20,5*rows), dpi=200)
         fig.subplots_adjust(hspace=0.5)
         axes = ()
         i = 0
         for start_date, end_date in end_day_nums:
             plot_window(dates, prices, smooth_prices, 
                 smooth_prices_max_indices, smooth_prices_min_indices,
                 price_max_indices, price_min_indices, 
                 start=start_date-1, end=end_date+1, ax=axes[i])
             i += 1
         ()
 visualize_patterns(dates, prices, smooth_prices, 
                        smooth_prices_max_indices, smooth_prices_min_indices, 
                        price_max_indices, price_min_indices, 
                        patterns, shorthand_fullname_dict)

You can see that the various patterns are different, which makes it easier to understand for those of us who are new to the financial industry. Tools that graphically analyze stock price movements and algorithmic recognition of patterns can help us understand market behavior over time more intuitively, which is crucial for algorithmic trading.

Above is the use of Python code to identify stock price chart patterns to achieve the details, more information about Python to identify stock price chart patterns please pay attention to my other related articles!