Strategy Overview
The Volume Reversal Trend Capture Strategy is a quantitative trading method based on abnormal trading volume and price behavior, designed to identify key moments when the market is likely to reverse direction. The core of this strategy is to find candles with significantly higher-than-average volume and, upon confirming a volume decrease, make trading decisions opposite to the previous trend direction. It leverages the psychological shifts that occur after high volume activity, known as the "middle finger pattern"—where markets typically experience short-term reversals after heavy participation. The strategy employs limit orders to enter the market and sets stop-loss and take-profit levels based on fixed points or ATR, while also including time filtering functionality to avoid low-liquidity market periods.
Strategy Principles
The core principle of this strategy is based on the trend reversal phenomenon following abnormal market volume. The specific operational logic is as follows:
Identifying Abnormal Volume: The system detects whether the previous candle has significantly higher-than-average volume. During regular trading hours (RTH), volume needs to exceed the recent average volume by 3 times (adjustable); during after-hours or special sessions (ETH), it needs to exceed 5 times. The average volume calculation automatically excludes RTH edge periods, 4-6 PM after closing, and Sunday pre-market sessions.
Confirming Volume Decrease: The current candle's volume must be lower than the previous abnormal volume candle, indicating that large trading activity has ended.
Determining Trend Direction: The trend direction is determined by comparing the closing price before the abnormal volume candle with its relationship to the SMA (Simple Moving Average).
Counter-trend Entry Signals:
- Long signal: When the trend before the abnormal volume candle is bearish (closing price below SMA), and the current candle shows decreased volume.
Short signal: When the trend before the abnormal volume candle is bullish (closing price above SMA), and the current candle shows decreased volume.
Entry Execution:Long: Set a limit buy order at the lowest price of the abnormal volume candle.
Short: Set a limit sell order at the highest price of the abnormal volume candle.
Risk Management: Depending on different instrument characteristics, the system provides two methods for setting stop-loss/take-profit:
- For specific instruments (such as NQ): Use fixed points to set stop-loss and take-profit.
- For other instruments: Option to choose dynamic stop-loss/take-profit based on ATR, or use fixed points.
Time Filtering: The strategy can optionally filter trading signals during the first and last 15 minutes of RTH, and always filters signals during the after-hours closing period (4-6 PM) and Sunday pre-market sessions.
Strategy Advantages
Capturing Key Turning Points: The strategy focuses on capturing market turning points accompanied by abnormal trading volume, which often represent significant shifts in market sentiment and provide high-probability trading opportunities.
Precise Entry Points: By using limit orders at the high/low points of abnormal volume candles, the strategy ensures trading at technically important price levels, improving entry precision.
Adaptive Volume Identification: The strategy dynamically adjusts abnormal volume judgment criteria based on different trading sessions (regular trading hours vs. after-hours/special sessions), better aligning with actual market conditions.
Flexible Risk Management: Provides stop-loss/take-profit options based on fixed points and ATR, allowing for personalized settings according to different instrument characteristics and volatility.
Intelligent Time Filtering: Automatically identifies and filters low-liquidity and unstable trading periods, avoiding false signals that often occur near market opening and closing.
Clear Visual Feedback: The strategy provides intuitive visual indications on the chart, including highlighted abnormal volume candles, trend SMA lines, stop-loss and take-profit levels, facilitating monitoring and analysis by traders.
Automated Execution: Once conditions are met, the system automatically executes limit order placement and stop-loss/take-profit settings, reducing human intervention and maintaining trading discipline.
Strategy Risks
False Breakout Risk: Abnormal volume may cause prices to temporarily break through important levels, but may quickly retrace afterward, creating false signals. To mitigate this risk, consider adding confirmation indicators, such as RSI overbought/oversold confirmation or breakout duration requirements.
News-Driven Event Impact: Major economic data or company announcements may cause abnormal trading volume, but these reactions often persist longer rather than immediately reversing. It is recommended to pause the strategy or add filtering conditions before and after important economic data releases.
Market Environment Change Risk: Counter-trend trading in strong trending markets may face continuously adverse price movements. Consider adding long-term trend filters to avoid counter-trend operations in strong trending environments.
Limit Order Non-Execution Risk: If the price does not reach the set limit level in the next candle, the trading signal may become invalid. Consider setting a maximum validity period, or converting to market orders under specific conditions.
Low Liquidity Risk: Despite the strategy's time filtering functionality, certain instruments may still face liquidity issues during specific periods. It is advisable to adjust trading session restrictions based on the characteristics of the trading instrument.
Parameter Optimization Risk: Excessive optimization of strategy parameters may lead to overfitting historical data and poor future performance. Ensure parameters are within reasonable ranges and validate strategy robustness through out-of-sample testing.
Strategy Optimization Directions
Multi-Timeframe Confirmation: Add higher timeframe trend filters to ensure higher win rates in the direction of larger trends. For example, check the daily trend direction and only enter positions when aligned with the daily trend.
Volume Quality Assessment: Beyond pure volume size, consider adding volume quality assessment, such as Volume Weighted Average Price (VWAP) deviation, to better understand market behavior behind large trading volumes.
Dynamic Stop-Loss Strategy: Implement volatility-based dynamic stop-losses that automatically adjust stop-loss positions as trades move favorably, locking in partial profits. For example, use trailing stops or move stops to breakeven after breaking through key levels.
Multi-Instrument Correlation Filtering: For correlated instruments (such as stock index futures and spot, gold and silver, etc.), adding confirmation indicators from related instruments can improve signal quality. Signals may be more reliable when multiple correlated instruments simultaneously display abnormal volume and price behavior.
Machine Learning Optimization: Analyze the characteristics of the most successful abnormal volume patterns in historical data through machine learning algorithms, dynamically adjusting entry conditions and parameters. For example, use decision trees or random forests to predict the best action given specific abnormal volume characteristics.
Volatility Adjustment: Adjust abnormal volume judgment criteria and stop-loss/take-profit levels based on the current market volatility state. Increase abnormal volume threshold and reduce stop-loss distance in high-volatility environments; do the opposite in low-volatility environments.
Add Fundamental Filtering: Adjust strategy parameters or pause trading during major economic data release days or quarterly earnings seasons to avoid false signals caused by news interference.
Summary
The Volume Reversal Trend Capture Strategy is a quantitative trading system focusing on trading volume and price behavior, capturing potential reversal points by identifying changes in market sentiment following abnormal trading volume. The strategy clearly defines entry, exit conditions, and risk management rules technically, and includes intelligent time filtering mechanisms to avoid low-quality market periods.
The core advantage of the strategy lies in its precise capture of the market's "middle finger pattern"—short-term reversal opportunities that often form when market participants surge in and subsequently withdraw. By precisely positioning limit orders at key price levels and coupling with reasonable stop-loss and take-profit management, the strategy provides a disciplined trading method.
However, users should be aware of the strategy's potential risks in strong trending markets and its sensitivity to news events. By adding multi-timeframe confirmation, dynamically adjusting parameters, and enhancing risk management mechanisms, the strategy can further optimize its performance stability and adaptability.
Overall, the Volume Reversal Trend Capture Strategy provides traders with a trading system based on market behavior and psychological principles, particularly suitable for volatile markets and range-bound conditions. With reasonable settings and continuous optimization, this strategy has the potential to become an effective tool in a trading portfolio.
Strategy source code
/*backtest
start: 2024-05-13 00:00:00
end: 2025-05-11 08:00:00
period: 1h
basePeriod: 1h
exchanges: [{"eid":"Futures_Binance","currency":"BTC_USDT"}]
*/
//@version=5
// Strategy Title Reflects Latest Logic
strategy(title="Middle Finger Trading Strategy",
shorttitle="Middle_Finger",
overlay=true,
pyramiding=0, // Only one entry at a time
default_qty_type=strategy.percent_of_equity,
default_qty_value=1, // Trade 1% of equity
commission_value=0.04, // Example commission (adjust as needed)
commission_type=strategy.commission.percent,
initial_capital = 10000, // Example starting capital
process_orders_on_close=false // Important for limit orders to potentially fill intra-bar
)
// --- Inputs ---
// Volume Settings Group
grp_vol = "Volume Settings"
float rthHugeVolMultiplier = input.float(3.0, title="1. RTH Huge Vol. Multiplier (> Avg)", minval=1.1, step=0.1, group=grp_vol, tooltip="Multiplier for core RTH (9:45-15:44 ET)")
float ethHugeVolMultiplier = input.float(5.0, title="2. ETH/Excluded Huge Vol. Multiplier (> Avg)", minval=1.1, step=0.1, group=grp_vol, tooltip="Multiplier for ETH and first/last 15min RTH (default 5x)")
int volLookback = input.int(20, title="3. Volume SMA Lookback", minval=1, group=grp_vol, tooltip="Lookback for calculating the filtered average volume (Used ONLY for identifying the HUGE spike)")
// Removed normalVolMultiplier as it's no longer used for entry confirmation
// Trend Settings Group
grp_trend = "Trend Settings"
int trendLookback = input.int(20, title="1. Trend SMA Lookback", minval=2, group=grp_trend, tooltip="Lookback period for the Simple Moving Average used to determine the trend before the spike")
// Risk Management Group
grp_risk = "Risk Management (SL/TP)"
string nqTargetTickerId = input.string("CME:NQ1!", title="1. Target Ticker ID for Fixed NQ Points", group=grp_risk, tooltip="Specify the exact Ticker ID (e.g., CME:NQ1!, TVC:NDX) for fixed SL/TP. Found in Symbol Info.")
float nqFixedStopPoints = input.float(20.0, title="2. Fixed SL Points (for Target Ticker)", group=grp_risk, minval=0.1, step=0.1)
float nqFixedTpPoints = input.float(50.0, title="3. Fixed TP Points (for Target Ticker)", group=grp_risk, minval=0.1, step=0.1)
// General SL/TP Settings (used if NOT the target ticker)
bool useAtrStops = input.bool(true, title="4. Use ATR for SL/TP (Other Tickers)?", group=grp_risk)
int atrLookback = input.int(14, title="5. ATR Lookback", group=grp_risk, inline="atr_other")
float atrStopMultiplier = input.float(2.0, title="6. ATR SL Multiplier", group=grp_risk, inline="atr_other", minval=0.1, step=0.1)
float atrTpMultiplier = input.float(4.0, title="7. ATR TP Multiplier", group=grp_risk, inline="atr_other", minval=0.1, step=0.1)
float fixedStopPoints = input.float(100.0, title="6. Fixed SL Points (Other Tickers)", group=grp_risk, inline="fixed_other", minval=1)
float fixedTpPoints = input.float(200.0, title="7. Fixed TP Points (Other Tickers)", group=grp_risk, inline="fixed_other", minval=1)
// Time Filter Settings Group
grp_time = "Time Filter (ET)"
bool enableEntryFilterRthEdges = input.bool(true, title="1. Filter Entries First/Last 15 Min RTH (ET)?", group=grp_time, tooltip="If checked, ignores entries from 9:30-9:44 ET and 15:45-15:59 ET. Avg Vol calc *always* filters these times, 4-6PM ET, and Sun pre-6PM ET.")
string targetTimezone = "America/New_York" // Specify Eastern Time zone
// --- Time Calculation Function ---
isTimeInSession(t, tz, sessionString) =>
not na(time(timeframe.period, sessionString, tz))
// --- Time Context Functions ---
getTimeContext(t, tz) =>
h = hour(t, tz)
m = minute(t, tz)
d = dayofweek(t, tz)
// Core RTH: 9:45 AM to 15:44 PM ET (Mon-Fri)
bool isCoreRTH = d >= dayofweek.monday and d <= dayofweek.friday and
((h == 9 and m >= 45) or (h >= 10 and h <= 14) or (h == 15 and m <= 44))
// Excluded RTH Edges: 9:30-9:44 ET and 15:45-15:59 ET (Mon-Fri)
bool isExcludedRTH = d >= dayofweek.monday and d <= dayofweek.friday and
((h == 9 and m >= 30 and m <= 44) or (h == 15 and m >= 45))
// After Hours Closed: 4:00 PM to 5:59 PM ET (Mon-Fri)
bool isAfterHoursClosed = d >= dayofweek.monday and d <= dayofweek.friday and
(h >= 16 and h < 18)
// Sunday Pre-Market: Sunday before 6:00 PM ET
bool isSundayPreMarket = d == dayofweek.sunday and h < 18
// Combine ALL periods where activity should be ignored or volume excluded from avg
bool isExcludedPeriod = isExcludedRTH or isAfterHoursClosed or isSundayPreMarket
[isCoreRTH, isExcludedRTH, isAfterHoursClosed, isSundayPreMarket, isExcludedPeriod]
// --- Get Time Context for Current and Previous Bar ---
[isCurrentBarCoreRTH, isCurrentBarExcludedRTH, isCurrentBarAfterHoursClosed, isCurrentBarSundayPreMarket, isCurrentBarExcludedPeriod] = getTimeContext(time, targetTimezone)
[isPreviousBarCoreRTH, isPreviousBarExcludedRTH, isPreviousBarAfterHoursClosed, isPreviousBarSundayPreMarket, isPreviousBarExcludedPeriod] = getTimeContext(time[1], targetTimezone)
// --- Calculations ---
// Volume Averaging: Exclude RTH edges, 4-6 PM ET, and Sunday Pre-6 PM ET ALWAYS
// This average is *only* used to define the huge volume spike threshold
bool excludeCurrentVolFromAvg = isCurrentBarExcludedPeriod
float volumeForAvgCalc = excludeCurrentVolFromAvg ? na : volume
float avgVolume = ta.sma(volumeForAvgCalc, volLookback)
// Dynamic Huge Volume Multiplier: Based on *previous* bar's time (Core RTH or not)
float activeHugeVolMultiplier = isPreviousBarCoreRTH ? rthHugeVolMultiplier : ethHugeVolMultiplier
// Use avgVolume[1] as current avgVolume excludes current bar, and we compare previous volume to avg *before* it
float hugeVolThreshold = nz(avgVolume[1]) * activeHugeVolMultiplier
// --- MODIFIED Volume Conditions ---
// 1. Check if the *previous* bar had huge volume compared to its preceding average
bool isHugeVolumePrevBar = volume[1] > hugeVolThreshold and hugeVolThreshold > 0
// 2. Check if the *current* bar's volume is simply lower than the previous (huge) bar's volume
bool isVolumeLowerThanSpike = volume < volume[1]
// Trend Condition
float priceSma = ta.sma(close, trendLookback)
// Ensure trend condition uses close[2] vs sma[2] (trend state *before* the spike bar)
bool isBullishTrendBeforeSpike = close[2] > nz(priceSma[2])
bool isBearishTrendBeforeSpike = close[2] < nz(priceSma[2])
// --- Entry Time Filtering ---
// Always filter After Hours Closed and Sunday Pre-Market.
// Optionally filter RTH Edges based on input.
bool shouldFilterRthEdges = enableEntryFilterRthEdges and isCurrentBarExcludedRTH
bool isIgnoreEntryTime = shouldFilterRthEdges or isCurrentBarAfterHoursClosed or isCurrentBarSundayPreMarket
// --- MODIFIED Base Conditions ---
// Uses the simplified `isVolumeLowerThanSpike` check
bool baseLongCondition = isBearishTrendBeforeSpike and isHugeVolumePrevBar and isVolumeLowerThanSpike
bool baseShortCondition = isBullishTrendBeforeSpike and isHugeVolumePrevBar and isVolumeLowerThanSpike
// Final Conditions (Apply Time Filter)
bool finalLongCondition = baseLongCondition and not isIgnoreEntryTime
bool finalShortCondition = baseShortCondition and not isIgnoreEntryTime
// --- Stop Loss & Take Profit Calculation (Conditional Logic) ---
// This part remains the same
float atrValue = ta.atr(atrLookback)
float tickValue = syminfo.mintick
int stopLossTicks = 100 // Default fallback SL ticks
int takeProfitTicks = 200 // Default fallback TP ticks
// Check if the current symbol matches the target ticker ID
bool isTargetTicker = str.upper(syminfo.tickerid) == str.upper(nqTargetTickerId) // Case-insensitive comparison
if (isTargetTicker and tickValue > 0)
// --- Target Ticker Logic (e.g., NQ Fixed Points) ---
float ticksPerPoint = 1.0 / tickValue
stopLossTicks := math.max(1, math.round(nqFixedStopPoints * ticksPerPoint))
takeProfitTicks := math.max(1, math.round(nqFixedTpPoints * ticksPerPoint))
else if tickValue > 0 // Use only if tickValue is valid
// --- Standard Logic (Other Tickers: ATR or Fixed) ---
float stopLossDistance = useAtrStops ? atrValue * atrStopMultiplier : fixedStopPoints * tickValue
float takeProfitDistance = useAtrStops ? atrValue * atrTpMultiplier : fixedTpPoints * tickValue
// Calculate ticks, ensuring it's at least 1 tick
stopLossTicks := na(stopLossDistance) ? 100 : math.max(1, math.round(stopLossDistance / tickValue))
takeProfitTicks := na(takeProfitDistance) ? 200 : math.max(1, math.round(takeProfitDistance / tickValue))
// Final check to ensure SL/TP are not na
stopLossTicks := nz(stopLossTicks, 100)
takeProfitTicks := nz(takeProfitTicks, 200)
// --- Strategy Execution ---
// Uses Limit Orders based on previous bar's low/high - Remains the same
float limitEntryPriceLong = low[1] // Target entry at the low of the huge volume bar
float limitEntryPriceShort = high[1] // Target entry at the high of the huge volume bar
if (finalLongCondition and strategy.position_size == 0)
strategy.cancel("S") // Cancel any pending short limit order first
strategy.entry("L", strategy.long, limit = limitEntryPriceLong)
strategy.exit("L SL/TP", from_entry="L", loss=stopLossTicks, profit=takeProfitTicks)
if (finalShortCondition and strategy.position_size == 0)
strategy.cancel("L") // Cancel any pending long limit order first
strategy.entry("S", strategy.short, limit = limitEntryPriceShort)
strategy.exit("S SL/TP", from_entry="S", loss=stopLossTicks, profit=takeProfitTicks)
// --- Plotting & Visuals ---
plot(avgVolume, title="Filtered Avg Volume", color=color.new(color.blue, 60), style=plot.style_line)
// Removed the plot for the normal volume threshold as it's no longer used
// Highlight huge volume bar (previous bar that triggered the signal)
bgcolor(isHugeVolumePrevBar[1] ? color.new(color.yellow, 85) : na, title="Huge Volume Bar [-1]")
// Highlight bars excluded from volume average calculation
bgcolor(excludeCurrentVolFromAvg ? color.new(color.teal, 90) : na, title="Vol Excluded from Avg Calc")
// Highlight bars where entries are ignored due to time filters
bgcolor(isIgnoreEntryTime and (baseLongCondition or baseShortCondition) ? color.new(color.gray, 75) : na, title="Entry Time Filtered Bar")
// --- MODIFIED Highlight base conditions met ---
// Reflects the updated base conditions using isVolumeLowerThanSpike
bgcolor(baseLongCondition and not isIgnoreEntryTime ? color.new(color.green, 90) : na, title="Base Long Condition Met")
bgcolor(baseShortCondition and not isIgnoreEntryTime ? color.new(color.red, 90) : na, title="Base Short Condition Met")
plot(priceSma, title="Trend SMA", color=color.gray)
// Plot SL/TP levels for visualization - Remains the same
var float entryPrice = na
var float slLevel = na
var float tpLevel = na
if (strategy.opentrades > 0 and strategy.opentrades[1] == 0) // Just entered a trade
entryPrice := strategy.opentrades.entry_price(0)
if (strategy.position_size > 0) // Long
slLevel := entryPrice - stopLossTicks * tickValue
tpLevel := entryPrice + takeProfitTicks * tickValue
else // Short
slLevel := entryPrice + stopLossTicks * tickValue
tpLevel := entryPrice - takeProfitTicks * tickValue
else if (strategy.opentrades == 0 and strategy.opentrades[1] > 0) // Position closed
entryPrice := na
slLevel := na
tpLevel := na
else if (strategy.opentrades > 0) // Position still open
entryPrice := strategy.opentrades.entry_price(0)
if (strategy.position_size > 0) // Long
slLevel := entryPrice - stopLossTicks * tickValue
tpLevel := entryPrice + takeProfitTicks * tickValue
else // Short
slLevel := entryPrice + stopLossTicks * tickValue
tpLevel := entryPrice - takeProfitTicks * tickValue
plot(strategy.opentrades > 0 ? slLevel : na, title="Stop Loss Level", color=color.red, style=plot.style_linebr)
plot(strategy.opentrades > 0 ? tpLevel : na, title="Take Profit Level", color=color.green, style=plot.style_linebr)
// Optional Debugging Plots
// plotchar(isHugeVolumePrevBar, "HugeVol[1]", "H", location.bottom, color.yellow, size=size.tiny)
// plotchar(isVolumeLowerThanSpike, "VolLow", "v", location.bottom, color.purple, size=size.tiny) // Changed char
// plotchar(finalLongCondition, "FinalLong", "L", location.top, color.green, size=size.tiny)
// plotchar(finalShortCondition, "FinalShort", "S", location.top, color.red, size=size.tiny)
// plot(finalLongCondition ? limitEntryPriceLong : na, "Long Limit Target", color.lime, style=plot.style_circles, linewidth=2)
// plot(finalShortCondition ? limitEntryPriceShort : na, "Short Limit Target", color.fuchsia, style=plot.style_circles, linewidth=2)
Strategy parameters
The original address: Volume Reversal Trend Capture Strategy
Volatile markets and range-bound conditions need strategies like this indeed! I will optimize it in real tradings.