Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 37

Detecting Swing Highs and Lows in Price Data

Recommended resources

Hello! Welcome to the first lesson of our new module, "Automating Core ICT Concepts." In the previous modules, you've built a solid foundation by learning how to fetch, handle, and visualize market data with Python. Now, we begin the exciting process of translating sophisticated trading concepts into automated logic.

In our last session, you learned how to use Plotly to create professional, interactive charts. This skill will be invaluable today, as it allows us to visually verify the logic we build.

Today's learning outcome is to write a Python function to detect swing highs and swing lows in price data. These points are the absolute bedrock of ICT market structure analysis. Identifying them programmatically is the first and most critical step towards automating the detection of breaks of structure, liquidity sweeps, and other key patterns.

1. What Are Swing Highs and Swing Lows?

Before we write any code, let's refresh the core concept. In its simplest form, a swing point is a local peak or trough in price action.

  • A Swing High is a candle that has a higher high than the candles immediately to its left and right.
  • A Swing Low is a candle that has a lower low than the candles immediately to its left and right.

This creates a simple 3-bar fractal pattern, which is the most basic form of a swing point.

Visual Example of Swing High and Swing Low
This image illustrates the basic 3-candle structure for a Swing High (a high with lower highs on each side) and a Swing Low (a low with higher lows on each side).

While this 3-bar pattern is a good starting point, we can generalize this idea. A swing point can be defined by comparing a candle to a larger number of neighboring candles on each side. This is where the "rolling window" algorithm comes in.


2. The Rolling Window Algorithm

The rolling window method is an intuitive way to formalize the detection of swing points. It works by checking if a candle's high or low is the most extreme price within a defined window of candles around it.

This approach has one key parameter, often called order or window size, which defines how many candles to look at on either side of the candle in question.

  • A swing high of order = 1 is our basic 3-bar pattern (1 candle on the left, 1 on the right).
  • A swing high of order = 5 means a candle's high is the highest among the 5 candles before it, the candle itself, and the 5 candles after it (a total window of 11 candles).

A larger order makes the algorithm more selective, identifying only the more significant turning points.

To get a clear visual explanation of this algorithm, please watch the first part of the following video.

3 Must-Know Algorithms for Automating Chart Pattern Trading in Python

The video '3 Must-Know Algorithms for Automating Chart Pattern Trading in Python' from the neurotrader channel provides an excellent explanation of the rolling window method.

Watch the segment from 00:42 to 02:58. Focus on how the 'order' parameter changes which points are identified as tops and bottoms. Also, pay close attention to the concept of 'lag'β€”the delay before a swing point can be confirmed.


3. The Critical Pitfall: Look-Ahead Bias

As the video highlighted, there's a crucial detail we must handle correctly. To confirm a candle is a swing high of order = 5, we must wait for the next 5 candles to close. We cannot know at the moment the candle closes that it will be a swing high; we are using future data to make that determination.

In a live trading bot or a realistic backtest, using future data is impossible and is known as look-ahead bias. It can make a strategy appear far more profitable than it actually is.

Another excellent video explains this pitfall and why it's so important to avoid.

Automated Break Out Detection in Python for Algorithmic Trading Systems

The video 'Automated Break Out Detection in Python' from CodeTrading explicitly discusses the look-ahead bias problem when detecting pivots (another term for swing points).

Watch the short clip from 07:56 to 08:29. The presenter explains why you cannot use data from 'the right of the candle' in a live system.

For our purposes today, we will first write a function that identifies all historical swing points for analysis. Then, we will discuss how to correctly handle the time lag for a real-time application.


4. Implementing the Swing Point Detection in Python

We can implement the rolling window logic efficiently using the pandas library. We'll create a function that takes a DataFrame and a window parameter, and adds new columns to label the swing points.

Here is the plan for our function:

  1. Define a function find_swings that accepts a pandas DataFrame and a window size n.
  2. Use the rolling() method on the 'High' and 'Low' columns to find the maximum and minimum price in a centered window.
  3. A swing high occurs where the candle's 'High' is equal to the rolling maximum.
  4. A swing low occurs where the candle's 'Low' is equal to the rolling minimum.
  5. The function will return the DataFrame with new boolean columns, is_swing_high and is_swing_low.

Here is the code:

import pandas as pd
import yfinance as yf

def find_swings(df: pd.DataFrame, n: int = 5):
    """
    Finds swing highs and lows in a price DataFrame.

    A swing high is a candle that has a higher high than the 'n' candles 
    before and after it.
    A swing low is a candle that has a lower low than the 'n' candles 
    before and after it.

    Args:
        df: DataFrame with 'High' and 'Low' columns.
        n: The number of candles on each side to check.

    Returns:
        The original DataFrame with 'is_swing_high' and 'is_swing_low' columns.
    """
    # Make a copy to avoid modifying the original DataFrame
    df_copy = df.copy()

    # The window size is n candles on each side, plus the candle itself
    window_size = 2 * n + 1

    # Find local max/min using a rolling window
    # center=True means the window is centered on the current candle
    df_copy['rolling_max'] = df_copy['High'].rolling(window=window_size, center=True).max()
    df_copy['rolling_min'] = df_copy['Low'].rolling(window=window_size, center=True).min()

    # Identify swing highs
    df_copy['is_swing_high'] = (df_copy['High'] == df_copy['rolling_max'])
    
    # Identify swing lows
    df_copy['is_swing_low'] = (df_copy['Low'] == df_copy['rolling_min'])

    # Drop the temporary rolling columns
    df_copy.drop(columns=['rolling_max', 'rolling_min'], inplace=True)
    
    return df_copy

# --- Example Usage ---
# Fetch some data
data = yf.download('EURUSD=X', period='60d', interval='4h')

# Find the swing points with a window of 5 candles on each side
data_with_swings = find_swings(data, n=5)

# Display the candles that are swing highs or lows
print(data_with_swings[data_with_swings['is_swing_high'] | data_with_swings['is_swing_low']])

Important Note: Because we used center=True, this function uses future data. It is perfect for historical analysis and identifying all swing points on a chart. If you were building a live bot, you would only be able to confirm a swing high at candle i once candle i+n has closed.


5. Visualizing the Detected Swings with Plotly

Now, let's use your Plotly skills from the last lesson to see our function in action. Visual feedback is the best way to confirm that our logic is working as expected and to understand how the n parameter affects the results.

We will plot the candlestick chart and then add scatter markers for the detected swing points.

import plotly.graph_objects as go

def plot_swings(df: pd.DataFrame):
    """Plots a candlestick chart with markers for swing highs and lows."""

    fig = go.Figure()

    # Add candlestick trace
    fig.add_trace(go.Candlestick(x=df.index,
                               open=df['Open'],
                               high=df['High'],
                               low=df['Low'],
                               close=df['Close'],
                               name='Candlesticks'))

    # Extract swing high and low points to plot markers
    swing_highs = df[df['is_swing_high']]
    swing_lows = df[df['is_swing_low']]

    # Add markers for swing highs
    fig.add_trace(go.Scatter(x=swing_highs.index, 
                             y=swing_highs['High'] * 1.0005, # Place slightly above the high
                             mode='markers',
                             marker=dict(symbol='triangle-down', color='red', size=10),
                             name='Swing High'))

    # Add markers for swing lows
    fig.add_trace(go.Scatter(x=swing_lows.index, 
                             y=swing_lows['Low'] * 0.9995, # Place slightly below the low
                             mode='markers',
                             marker=dict(symbol='triangle-up', color='lime', size=10),
                             name='Swing Low'))

    # Update layout
    fig.update_layout(title='EUR/USD with Detected Swing Points',
                      xaxis_title='Date',
                      yaxis_title='Price',
                      template='plotly_dark',
                      xaxis_rangeslider_visible=False)
    fig.show()

# --- Use the function to plot our results ---
plot_swings(data_with_swings)

When you run this code, you'll see your interactive candlestick chart with clear markers on the identified swing points. Try changing the n parameter in the find_swings function (e.g., from 5 to 10 or 3) and see how it changes the number and significance of the detected swings.

Test your understanding!

In the plot_swings function, the markers are placed slightly above or below the high/low by multiplying by 1.0005 or 0.9995. Why is this done instead of plotting them directly on the High or Low? What might happen if we didn't do this?

Show answer

This is done purely for visual clarity. If we plotted the marker directly on the high or low of the candle, it would be obscured by the wick of the candlestick itself, making it hard to see. By shifting it slightly, we ensure the marker is clearly visible just above the peak or just below the trough of the swing.


6. Using a Pre-built Library

Now that you understand the logic behind detecting swing points, it's good to know that you don't always have to write it from scratch. For many common concepts, there are specialized libraries available. The smartmoneyconcepts library is one such tool built specifically for ICT traders.

First, install the library:
pip install smartmoneyconcepts

You can use it to find swing points with just a few lines of code. This is useful for rapid development once you've grasped the underlying principles.

This is a python package for smart money concept indicators

The GitHub page for the smartmoneyconcepts library provides documentation for its functions. Let's look at how to use its swing point detection.

Review the 'Installation', 'Usage', and 'Swing Highs and Lows' sections. Notice the swing_length parameter, which is equivalent to our n parameter, and how the function is called.

Here's how you would use it:

from smartmoneyconcepts import smc

# The library expects lowercase column names
data_smc = data.copy()
data_smc.columns = [col.lower() for col in data_smc.columns]

# Use the library to find swings
swing_points = smc.swing_highs_lows(data_smc, swing_length=5)

# The result is a DataFrame with 'HighLow' (1 for high, -1 for low) and 'Level' columns
print(swing_points)

This shows the power of leveraging existing tools once you understand how they work.


Conclusion

Excellent work! You have successfully taken a core visual concept of tradingβ€”swing highs and lowsβ€”and translated it into a robust Python function. This is a massive step forward in your journey to building trading bots.

Key Takeaways:

  • Swing points are the foundational elements of market structure.
  • The rolling window algorithm is a common and effective method for programmatically identifying swing points.
  • Look-ahead bias is a critical pitfall to be aware of; a swing point can only be confirmed after a certain number of subsequent candles have formed.
  • Using Plotly to visualize your algorithm's output is essential for debugging and validation.
  • Specialized libraries like smartmoneyconcepts can accelerate development once you understand the underlying logic.

Preview for the Next Lesson:
Now that we have a reliable way to identify swing highs and lows, what's next? We use them to define the trend. In our next lesson, you will implement an algorithm to programmatically identify a break of structure (BOS). We will build directly on today's work to determine when price action breaks through a previous swing point, confirming a trend continuation.