Signals·TA Strategy Implementations·Intermediate

Volume Momentum Confirmation

Build a volume-confirmed momentum strategy requiring above-average trading volume alongside price momentum signals, filtering out low-conviction moves on thin participation and improving signal reliability in liquid markets.

ta-strategy-implementationstrading-signals

Volume-Confirmed Momentum Strategy


1. Strategy Overview

This strategy evaluates price movements in conjunction with trading volume to identify high-conviction signals. Signals are generated only when two conditions are simultaneously satisfied:

  1. Momentum Condition: Rate of Change (ROC) is positive or negative.
  2. Volume Condition: Current trading volume exceeds its rolling average by a specified multiplier (e.g., 1.2x).

A price movement supported by above-average volume indicates broad market participation, suggesting higher signal reliability and follow-through compared to movements on below-average volume.

2. Required Libraries and Setup

[ ]
!pip install pandas numpy plotly
import warnings; warnings.filterwarnings("ignore")
import pandas as pd
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
Requirement already satisfied: pandas in /usr/local/lib/python3.12/dist-packages (2.2.2)
Requirement already satisfied: numpy in /usr/local/lib/python3.12/dist-packages (2.0.2)
Requirement already satisfied: plotly in /usr/local/lib/python3.12/dist-packages (5.24.1)
Requirement already satisfied: python-dateutil>=2.8.2 in /usr/local/lib/python3.12/dist-packages (from pandas) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.12/dist-packages (from pandas) (2025.2)
Requirement already satisfied: tzdata>=2022.7 in /usr/local/lib/python3.12/dist-packages (from pandas) (2026.1)
Requirement already satisfied: tenacity>=6.2.0 in /usr/local/lib/python3.12/dist-packages (from plotly) (9.1.4)
Requirement already satisfied: packaging in /usr/local/lib/python3.12/dist-packages (from plotly) (26.1)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.12/dist-packages (from python-dateutil>=2.8.2->pandas) (1.17.0)

3. Data Generation Function

This function generates synthetic OHLCV (Open, High, Low, Close, Volume) data for demonstration. The data simulates minute-by-minute price movements and associated trading volume characteristics.

[ ]
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 # Ensure high is always > low
        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)

4. Volume Momentum Confirmation Function

This function implements the core logic of the volume-confirmed momentum strategy. It computes the Rate of Change (ROC) for momentum and analyzes volume against its rolling average to identify significant price movements supported by high trading activity.

Strategy Logic

The core principle of this strategy relies on the confluence of price momentum and significant trading volume. A price movement is considered a high-conviction signal only when both the momentum and volume conditions are met, suggesting strong market participation behind the move.

  • Momentum Condition (Rate of Change - ROC): The Rate of Change (ROC) indicator measures the percentage change in price over a specified period. A positive ROC indicates upward price momentum, while a negative ROC indicates downward price momentum. Signals are only considered when there is clear momentum (either positive or negative).

  • Volume Condition: This condition validates the momentum by assessing whether the current trading volume is significantly above its recent average. Specifically, the current volume must exceed a rolling average of volume by a predefined multiplier (e.g., 1.2 times). This ensures that price movements are supported by substantial buying or selling pressure, rather than being a result of low liquidity or minor market fluctuations.

When these two conditions align, the strategy generates a signal:

  • Buy Signal (+1): Generated when the ROC is positive (upward momentum) AND the current volume is significantly higher than its average.
  • Sell Signal (-1): Generated when the ROC is negative (downward momentum) AND the current volume is significantly higher than its average.
  • No Signal (0): Occurs when either the momentum condition is not met (ROC is flat or insignificant) or the volume condition is not met (volume is not significantly higher than average).
[ ]
def volume_momentum_confirmation(
    df: pd.DataFrame,
    roc_period: int = 5,
    vol_window: int = 20,
    vol_multiplier: float = 1.2,
) -> pd.DataFrame:
    """
    Calculates volume-confirmed momentum signals.

    Args:
        df (pd.DataFrame): Input DataFrame with 'datetime', 'close', and 'volume' columns.
        roc_period (int): The period over which to calculate the Rate of Change (ROC).
        vol_window (int): The window size for calculating the rolling average of volume.
        vol_multiplier (float): Multiplier for the average volume to determine 'high volume'.

    Returns:
        pd.DataFrame: The original DataFrame with added 'roc', 'avg_volume', 'high_volume',
                      and 'signal' columns.
                      'signal' will be 1 for buy, -1 for sell, and 0 for no signal.
    """
    df = df.copy().sort_values("datetime", ignore_index=True)

    # Calculate Rate of Change (Momentum Condition)
    df["roc"] = df["close"].pct_change(roc_period) * 100

    # Calculate Average Volume and High Volume Condition
    df["avg_volume"] = df["volume"].rolling(vol_window).mean()
    df["high_volume"] = df["volume"] > vol_multiplier * df["avg_volume"]

    # Generate Signals
    # A buy signal (1) occurs when ROC is positive and volume is high.
    # A sell signal (-1) occurs when ROC is negative and volume is high.
    # Otherwise, there is no signal (0).
    df["signal"] = np.where(
        (df["roc"] > 0) & df["high_volume"],  1,
        np.where(
        (df["roc"] < 0) & df["high_volume"], -1, 0))
    return df

5. Strategy Application and Signal Analysis

The volume_momentum_confirmation function is applied to the generated data. The distribution of buy, sell, and neutral signals is then analyzed. The vol_multiplier parameter can be adjusted to fine-tune signal quality versus frequency.

[ ]
df_signals = volume_momentum_confirmation(df)

# The `vol_multiplier` parameter (e.g., 1.2) specifies the factor by which current volume must exceed the
# `vol_window`-period average for a momentum signal to be considered valid.
# A higher `vol_multiplier` value results in fewer but potentially higher-quality signals,
# whereas a lower value increases signal frequency.

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]
--- Signal Distribution ---
signal
 0    328
 1     96
-1     76
Name: count, dtype: int64

6. Signal Visualization

This section generates an interactive Plotly visualization. The plot displays a candlestick chart of price action, overlaid with buy and sell signals. A secondary subplot illustrates the trading volume in comparison to its rolling average, visually confirming the volume condition for generated signals.

[ ]
fig = make_subplots(rows=2, cols=1, shared_xaxes=True,
    subplot_titles=["Price + Signals", "Volume vs Average Volume"],
    row_heights=[0.65, 0.35])

# Candlestick chart for price
fig.add_trace(go.Candlestick(x=df_signals["datetime"],
    open=df_signals["open"], high=df_signals["high"],
    low=df_signals["low"],   close=df_signals["close"], name="Price"), row=1, col=1)

# Buy signals
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)"), row=1, col=1)

# Sell signals
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)"), row=1, col=1)

# Volume bars
fig.add_trace(go.Bar(x=df_signals["datetime"], y=df_signals["volume"],
    name="Volume", marker_color="steelblue"), row=2, col=1)

# Average volume line
fig.add_trace(go.Scatter(x=df_signals["datetime"], y=df_signals["avg_volume"],
    mode="lines", name="Avg Volume", line=dict(color="orange", width=1.5)), row=2, col=1)

fig.update_layout(title_text="Volume-Confirmed Momentum Strategy",
    xaxis_rangeslider_visible=False, height=700, yaxis=dict(autorange=True))

fig.show()

7. Conclusion

This notebook demonstrates a volume-confirmed momentum strategy. By combining price momentum (Rate of Change) with above-average trading volume, the strategy aims to identify high-conviction buy and sell signals. The visualization helps in understanding how these signals are generated and their relationship to price action and volume.

Further enhancements could include:

  • Optimizing roc_period, vol_window, and vol_multiplier parameters.
  • Incorporating other technical indicators or market conditions.
  • Backtesting the strategy on historical data to evaluate performance metrics.