Volume Spike Detection — identifies institutional volume events and enters on momentum spikes or low-volume pullback re-entries.
Timeframe
1h
Direction
Long Only
Stoploss
-1.0%
Trailing Stop
No
ROI
0m: 1.5%, 15m: 1.0%, 30m: 0.8%
Interface Version
3
Startup Candles
30
Indicators
2
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401
"""
Strategy 13: Volume Spike Detection
=====================================
Author : Halal Algo Trading Suite
Timeframe : 1h
Asset class : Crypto SPOT only — no shorting, no margin, no futures
Overview
--------
Volume is often considered the single most important leading indicator in price
action analysis. Large volume spikes reflect genuine institutional interest or
crowd behaviour shifts that frequently precede sustained directional moves.
This strategy detects three tiers of volume events and maps them to high-
probability entry setups:
Tier 1 — Mild Spike : Volume > 1.5× 20-period average
Tier 2 — Strong Spike : Volume > 2.0× 20-period average
Tier 3 — Climax Spike : Volume > 3.0× 20-period average (potential reversal)
Entry Modes
-----------
A. Initial Spike Entry
──────────────────
A single candle closes with:
• Price change >= +0.8 % (momentum candle, not just noise)
• Volume >= 1.5× average (at minimum Tier 1)
• RSI > 50 (momentum regime, not deeply oversold)
• Follow-through confirmation: the NEXT candle volume >= average
(avoids false breakouts that immediately reverse)
This is implemented by looking back one bar to confirm the prior
spike candle had follow-through (i.e., the confirmation is retrospective).
B. Pullback Re-entry (Highest Confidence)
───────────────────────────────────────
After an initial volume spike confirmed by follow-through:
• Price pulls back over 1–5 bars on LOW volume (< 70 % of average)
• Price then resumes the prior move: close > close of the spike bar
• Volume on the resumption bar >= average
This setup identifies accumulation during the pullback, with weak
sellers quickly absorbed by strong hands.
Exit Conditions
---------------
1. Volume exhaustion: Price continues advancing but volume has been
declining for 3 or more consecutive candles — buyers are running out
of steam (checked via custom declining-volume logic).
2. Climax reversal: A Tier 3 climax volume bar appears. Climax volume
often marks the final wave of a move as weak holders exit and no new
buyers emerge. Exit is triggered to lock in profits.
3. RSI drops below 45 — momentum has shifted.
4. ROI schedule provides the primary profit target.
Risk Parameters
---------------
Stoploss : -1.0 % (very tight — momentum trades require swift exits)
ROI schedule : 1.5 % immediately, 1.0 % after 15 min, 0.8 % after 30 min
Trailing : disabled (tight SL + ROI schedule is sufficient)
Reference (PumpDetection.py)
----------------------------
Take profit : 1.5 %
Stop loss : 1.0 %
Halal Compliance
----------------
✓ SPOT trading only
✓ No leverage or margin
✓ No short selling
✓ No interest-bearing instruments
"""
from datetime import datetime
from typing import Optional
import numpy as np
import pandas as pd
import pandas_ta as ta
from freqtrade.strategy import IStrategy, DecimalParameter, IntParameter
from freqtrade.persistence import Trade
# ---------------------------------------------------------------------------
# Helper utilities
# ---------------------------------------------------------------------------
def crossed_above(s1: pd.Series, s2: pd.Series) -> pd.Series:
"""Return True on bars where s1 crosses above s2."""
return (s1 > s2) & (s1.shift(1) <= s2.shift(1))
def crossed_below(s1: pd.Series, s2: pd.Series) -> pd.Series:
"""Return True on bars where s1 crosses below s2."""
return (s1 < s2) & (s1.shift(1) >= s2.shift(1))
# ---------------------------------------------------------------------------
# Strategy
# ---------------------------------------------------------------------------
class Strategy13_VolumeSpikeDetection(IStrategy):
"""
Volume Spike Detection — identifies institutional volume events and
enters on momentum spikes or low-volume pullback re-entries.
See module docstring for full description.
"""
INTERFACE_VERSION = 3
# ------------------------------------------------------------------
# Metadata
# ------------------------------------------------------------------
timeframe = "1h"
can_short = False # SPOT only
# ------------------------------------------------------------------
# ROI — momentum trades are short-duration; capture quickly or exit
# ------------------------------------------------------------------
minimal_roi = {
"0": 0.015, # 1.5 % immediately
"15": 0.010, # 1.0 % after 15 min
"30": 0.008, # 0.8 % after 30 min
}
# ------------------------------------------------------------------
# Stoploss — tight for momentum trades
# ------------------------------------------------------------------
stoploss = -0.01
trailing_stop = False
# ------------------------------------------------------------------
# Startup candle count — need 20+ bars for volume MA
# ------------------------------------------------------------------
startup_candle_count = 30
# ------------------------------------------------------------------
# Hyper-opt parameters
# ------------------------------------------------------------------
# Volume look-back window for average calculation
vol_window = IntParameter(10, 30, default=20, space="buy", optimize=True)
# Minimum volume multiplier for initial spike entry
vol_spike_min = DecimalParameter(1.2, 2.0, default=1.5, decimals=1, space="buy", optimize=True)
# Minimum price change (%) on spike candle
price_change_min = DecimalParameter(0.3, 1.5, default=0.8, decimals=1, space="buy", optimize=True)
# Low-volume threshold for pullback (fraction of average)
pullback_vol_max = DecimalParameter(0.5, 0.9, default=0.7, decimals=1, space="buy", optimize=True)
# RSI minimum for spike entry
rsi_min = IntParameter(45, 60, default=50, space="buy", optimize=True)
# ------------------------------------------------------------------
# Indicator population
# ------------------------------------------------------------------
def populate_indicators(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
"""
Compute all volume and momentum indicators.
Columns added
-------------
vol_ma : 20-bar SMA of volume
vol_ratio : Current volume / vol_ma
spike_mild : vol_ratio > 1.5
spike_strong : vol_ratio > 2.0
spike_climax : vol_ratio > 3.0
price_change_pct: Percentage change of close vs prior close
rsi : 14-period RSI
follow_through : Prior bar volume >= vol_ma (confirms spike bar)
spike_entry_bar : Bar that qualifies as an initial spike entry
pullback_active : True during a low-volume pullback after a spike
"""
window = self.vol_window.value
# -- Volume averages -----------------------------------------------
dataframe["vol_ma"] = dataframe["volume"].rolling(window=window).mean()
dataframe["vol_ratio"] = dataframe["volume"] / dataframe["vol_ma"]
# -- Volume tiers --------------------------------------------------
dataframe["spike_mild"] = dataframe["vol_ratio"] > 1.5
dataframe["spike_strong"] = dataframe["vol_ratio"] > 2.0
dataframe["spike_climax"] = dataframe["vol_ratio"] > 3.0
# -- Price change per candle (%) -----------------------------------
dataframe["price_change_pct"] = (
(dataframe["close"] - dataframe["close"].shift(1))
/ dataframe["close"].shift(1)
* 100
)
# -- RSI -----------------------------------------------------------
dataframe["rsi"] = ta.rsi(dataframe["close"], length=14)
# -- Follow-through confirmation -----------------------------------
# The bar AFTER a spike should also have volume >= average
dataframe["follow_through"] = dataframe["vol_ratio"].shift(1) >= 1.0
# -- Initial spike entry bar definition ----------------------------
dataframe["spike_entry_bar"] = (
(dataframe["price_change_pct"] >= self.price_change_min.value)
& (dataframe["vol_ratio"] >= self.vol_spike_min.value)
& (dataframe["rsi"] > self.rsi_min.value)
)
# -- Volume exhaustion: price rising but volume declining 3+ bars --
price_rising = dataframe["close"] > dataframe["close"].shift(1)
vol_declining = dataframe["volume"] < dataframe["volume"].shift(1)
dataframe["vol_exhaustion"] = (
price_rising
& vol_declining
& (dataframe["close"].shift(1) > dataframe["close"].shift(2))
& (dataframe["volume"].shift(1) < dataframe["volume"].shift(2))
& (dataframe["close"].shift(2) > dataframe["close"].shift(3))
& (dataframe["volume"].shift(2) < dataframe["volume"].shift(3))
)
# -- Rolling spike lookback (for pullback detection) ---------------
# Was there a qualifying spike in the previous 1–5 bars?
dataframe["recent_spike"] = (
dataframe["spike_entry_bar"].rolling(window=5).max().shift(1).fillna(0).astype(bool)
)
# -- Pullback: low-volume after spike, then close above prior close --
# Low-volume pullback over last bar
dataframe["low_vol_pullback"] = (
dataframe["recent_spike"]
& (dataframe["vol_ratio"] < self.pullback_vol_max.value)
)
# --- NaN Safety: convert any None to NaN so pandas comparisons work ---
for col in dataframe.columns:
if col not in ['open', 'high', 'low', 'close', 'volume', 'date']:
try:
dataframe[col] = pd.to_numeric(dataframe[col], errors='coerce')
except (ValueError, TypeError):
pass
return dataframe
# ------------------------------------------------------------------
# Entry signals
# ------------------------------------------------------------------
def populate_entry_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
"""
Two entry modes:
Mode A — Immediate spike entry (with next-bar follow-through check)
Mode B — Pullback re-entry (highest confidence)
Both require RSI > rsi_min to avoid buying into already-exhausted moves.
"""
conditions = []
# -- Mode A: Confirmed spike entry ---------------------------------
# Current bar had a prior spike (1 bar ago) AND this bar confirms
# follow-through (its own volume >= average)
mode_a = (
(dataframe["spike_entry_bar"].shift(1).fillna(False)) # prior bar was spike
& (dataframe["follow_through"]) # this bar confirms
& (dataframe["rsi"] > self.rsi_min.value)
& (dataframe["vol_ratio"] >= 1.0) # still above-avg vol
& (~dataframe["spike_climax"]) # not a climax bar
)
conditions.append(mode_a)
# -- Mode B: Pullback re-entry (highest confidence) ----------------
# After a spike, price pulls back on LOW volume, then resumes
mode_b = (
(dataframe["low_vol_pullback"])
& (dataframe["close"] > dataframe["close"].shift(1)) # price resuming up
& (dataframe["vol_ratio"] >= 0.8) # some volume on resume
& (dataframe["rsi"] > 45)
& (~dataframe["spike_climax"])
)
conditions.append(mode_b)
# Combine: either mode triggers
dataframe.loc[
pd.concat(conditions, axis=1).any(axis=1),
"enter_long"
] = 1
return dataframe
# ------------------------------------------------------------------
# Exit signals
# ------------------------------------------------------------------
def populate_exit_trend(self, dataframe: pd.DataFrame, metadata: dict) -> pd.DataFrame:
"""
Exit conditions:
1. Volume exhaustion: price advancing but volume declining 3+ bars
— buyers running out of fuel.
2. Climax reversal: a Tier-3 climax volume bar appears, signalling
potential blow-off top / final wave of the move.
3. RSI drops below 45 — momentum regime has shifted.
The tight -1 % stoploss and ROI schedule are the primary risk
managers; these signals provide early exit for deteriorating trades.
"""
dataframe.loc[
(
# Volume exhaustion (price up, volume down 3 consecutive bars)
dataframe["vol_exhaustion"]
)
| (
# Climax reversal bar — could be blow-off top
dataframe["spike_climax"]
)
| (
# RSI lost momentum
(dataframe["rsi"] < 45)
),
"exit_long"
] = 1
return dataframe