Signals·TA Strategy Implementations·Intermediate

Breakout Momentum Follow Through

Implement a breakout follow-through strategy that enters on confirmed momentum breakouts and uses continuation filters to avoid false breakouts in choppy markets, with trailing stops to capture extended directional trends.

ta-strategy-implementationstrading-signals

Strategy — Breakout Momentum Continuation

Breakout Momentum Strategy

This breakout_momentum_followthrough function implements the core trading strategy. It calculates resistance and support levels based on a lookback period, and Rate of Change (roc) as a momentum indicator. It then generates buy (+1), sell (-1), or no signal (0) based on price breaking these levels with confirmed momentum. The use of shift(1) for resistance and support prevents lookahead bias.

[ ]
import warnings; warnings.filterwarnings("ignore")
import pandas as pd
import numpy as np
import plotly.graph_objects as go

Data Generation

This function generate_data creates a synthetic DataFrame of candlestick price data (open, high, low, close, volume) for a specified number of periods. It simulates price movements based on a normal distribution, making it useful for testing trading strategies without real-world market data.

[ ]
def generate_data(periods):
    start_date = pd.to_datetime("2024-01-01 00:00:00+00:00")
    datetime_index = pd.date_range(start_date, periods=periods, freq="1min", tz="UTC")
    price_data = []; last_close = 42000
    for i in range(periods):
        open_price  = last_close + np.random.normal(0, last_close * 0.0005)
        close_price = open_price + np.random.normal(0, last_close * 0.005)
        body_high   = max(open_price, close_price)
        body_low    = min(open_price, close_price)
        high_price  = max(body_high + abs(np.random.normal(0, last_close * 0.002)), open_price, close_price)
        low_price   = min(body_low  - abs(np.random.normal(0, last_close * 0.002)), open_price, close_price)
        if high_price < low_price: high_price, low_price = low_price, high_price
        price_data.append({"open": max(1,int(open_price)), "high": max(1,int(high_price)),
                            "low":  max(1,int(low_price)),  "close": max(1,int(close_price))})
        last_close = close_price
    df = pd.DataFrame(price_data, index=datetime_index)
    df.index.name = "datetime"
    df["volume"] = np.random.uniform(100.0, 500.0, periods)
    df["datetime"] = df.index.to_series()
    return df.reset_index(drop=True)

df = generate_data(500)

Strategy Overview

After price breaks above a recent resistance level with confirmed momentum (ROC above threshold), it tends to continue in the same direction. This strategy combines a price breakout condition with a momentum filter to reduce false breakouts:

  • Close > N-period rolling high of prior candles AND ROC > threshold → Buy (+1)
  • Close < N-period rolling low AND ROC < −threshold → Sell (−1)
  • Otherwise → No signal (0)

The dual condition prevents entering on low-momentum breakouts that frequently reverse — a genuine breakout is characterized by both price exceeding the prior range extreme and strong recent momentum confirming directional intent.

[ ]
def breakout_momentum_followthrough(
    df: pd.DataFrame,
    lookback: int = 20,
    roc_period: int = 5,
    roc_threshold: float = 0.1,
) -> pd.DataFrame:
    """
    Implements a breakout momentum continuation trading strategy.

    Calculates resistance and support levels and Rate of Change (ROC) to generate
    buy (+1), sell (-1), or no signal (0) based on price breaking these levels
    with confirmed momentum. Prevents lookahead bias by shifting prior data.

    Args:
        df (pd.DataFrame): Input DataFrame with 'open', 'high', 'low', 'close',
                           and 'datetime' columns.
        lookback (int): Number of periods to look back for calculating resistance and support.
        roc_period (int): Number of periods for Rate of Change (ROC) calculation.
        roc_threshold (float): Threshold for ROC to confirm momentum.

    Returns:
        pd.DataFrame: The input DataFrame with added 'resistance', 'support', 'roc',
                      and 'signal' columns.
    """
    df = df.copy().sort_values("datetime", ignore_index=True)

    # Calculate resistance as the maximum high over the lookback period, shifted by 1 to prevent lookahead bias.
    df["resistance"] = df["high"].shift(1).rolling(lookback).max()
    # Calculate support as the minimum low over the lookback period, shifted by 1 to prevent lookahead bias.
    df["support"]    = df["low"].shift(1).rolling(lookback).min()
    # Calculate Rate of Change (ROC) as a momentum indicator.
    df["roc"]        = (df["close"] - df["close"].shift(roc_period)) / df["close"].shift(roc_period) * 100

    # Generate trading signals:
    # Buy (1) if close price breaks above resistance and ROC is positive above threshold.
    # Sell (-1) if close price breaks below support and ROC is negative below threshold.
    # Otherwise, no signal (0).
    df["signal"]     = np.where(
        (df["close"] > df["resistance"]) & (df["roc"] >  roc_threshold),  1,
        np.where(
        (df["close"] < df["support"])    & (df["roc"] < -roc_threshold), -1, 0))

    return df

df_signals = breakout_momentum_followthrough(df)

Signal Distribution and Visualization

This section applies the breakout_momentum_followthrough function to the generated data and then visualizes the results. It prints the count of each signal type (buy, sell, no signal) and creates an interactive candlestick chart using Plotly. The chart displays the price data, calculated resistance and support lines, and marks the buy and sell signals on the chart for easy analysis.

[ ]
print("--- Signal Distribution ---"); print(df_signals["signal"].value_counts())

buy_signals  = df_signals[df_signals["signal"] ==  1]
sell_signals = df_signals[df_signals["signal"] == -1]
fig = go.FigureWidget(data=[go.Candlestick(
    x=df_signals["datetime"], open=df_signals["open"], high=df_signals["high"],
    low=df_signals["low"], close=df_signals["close"], name="Price")])
fig.add_trace(go.Scatter(x=df_signals["datetime"], y=df_signals["resistance"],
    mode="lines", name="Resistance", line=dict(color="red",  width=1, dash="dash")))
fig.add_trace(go.Scatter(x=df_signals["datetime"], y=df_signals["support"],
    mode="lines", name="Support",    line=dict(color="green",width=1, dash="dash")))
fig.add_trace(go.Scatter(x=buy_signals["datetime"],  y=buy_signals["low"]  * 0.999,
    mode="markers", marker=dict(symbol="triangle-up",   size=10, color="green"), name="Buy (+1)"))
fig.add_trace(go.Scatter(x=sell_signals["datetime"], y=sell_signals["high"] * 1.001,
    mode="markers", marker=dict(symbol="triangle-down", size=10, color="red"),   name="Sell (−1)"))
fig.update_layout(title_text="Breakout Momentum Continuation",
    xaxis_rangeslider_visible=False, height=600, yaxis=dict(autorange=True))
fig.show()
--- Signal Distribution ---
signal
 0    444
 1     32
-1     24
Name: count, dtype: int64

Conclusion

This notebook demonstrates a breakout momentum continuation trading strategy. It involves calculating resistance and support levels, along with the Rate of Change (ROC) as a momentum indicator. Trading signals are generated when the price breaks through these levels with confirmed momentum, aiming to reduce false breakouts. The strategy was applied to synthetic data and the signals were visualized using an interactive candlestick chart.