Multi-Timeframe MACD-RSI Crossover Volatility-Filtered Quantitative Trading Strategy

MACD RSI VWAP ATR SMA
Created on: 2025-06-09 15:50:55 Modified on: 2025-06-09 15:50:55
Copy: 0 Number of hits: 326
avatar of ianzeng123 ianzeng123
2
Follow
319
Followers

 Multi-Timeframe MACD-RSI Crossover Volatility-Filtered Quantitative Trading Strategy  Multi-Timeframe MACD-RSI Crossover Volatility-Filtered Quantitative Trading Strategy

Overview

This strategy is a quantitative trading system based on multi-timeframe analysis, primarily utilizing MACD indicator, RSI indicator, VWAP moving averages, and ATR volatility filter to execute trades based on comprehensive signals from 30-minute and 1-hour timeframes. The strategy supports both long and short positions, confirming signals through technical indicator crossovers across different timeframes, combined with volatility conditions filtering to improve trade quality. The strategy incorporates fixed percentage take-profit and stop-loss mechanisms, while also exiting trades when technical indicators reverse, aiming to capture medium to short-term price movements.

Strategy Principles

The core principle of this strategy is to filter low-quality signals through multiple confirmation conditions, primarily including the following key components:

  1. Multi-Timeframe MACD Crossover Signals:

    • Using MACD(12,26,9) on 30-minute charts to identify primary entry signals
    • Optionally using 1-hour MACD trend as a confirmation condition
  2. RSI Overbought/Oversold Filtering:

    • Long positions require 30-minute RSI > 55
    • Short positions require 30-minute RSI < 45
    • 1-hour RSI serves as additional trend confirmation
  3. Dual VWAP Price Position Confirmation:

    • Long entries require price to be simultaneously above both 30-minute and 1-hour VWAPs
    • Short entries require price to be simultaneously below both 30-minute and 1-hour VWAPs
  4. Volatility Filter:

    • Using ATR(14) on 30-minute charts compared to its 20-period moving average
    • Only entering trades when current volatility is greater than or equal to its average value, avoiding false signals in low-volatility environments
  5. Multi-Level Exit Mechanism:

    • Fixed percentage take-profit (1.5%) and stop-loss (0.5%)
    • Exit on 30-minute MACD reverse crossover
    • Exit on 1-hour MACD trend reversal

Through this multi-level condition filtering and confirmation, the strategy aims to capture medium to short-term movements with clear directionality, while filtering out low-quality signals to improve win rate and risk-reward ratio.

Advantages Analysis

  1. Multi-Timeframe Confirmation: By combining signals from 30-minute and 1-hour timeframes, the strategy can better identify genuine trends and reduce the impact of false signals. The 1-hour MACD trend confirmation feature is particularly helpful in avoiding counter-trend trading.

  2. Volatility Adaptability: The ATR volatility filter ensures that the strategy only enters trades when the market has sufficient momentum, avoiding trading in low-volatility ranges, which effectively reduces the risk of sideways oscillation.

  3. Flexible Exit Mechanism: The strategy includes not only fixed take-profit and stop-loss levels but also dynamic exit mechanisms based on indicator reversals, allowing for timely exits when the market begins to reverse before reaching the take-profit level, thereby protecting profits.

  4. Dual Price Position Confirmation: Requiring price to be simultaneously above (for longs) or below (for shorts) VWAPs in two timeframes further confirms price momentum and direction, reducing false breakouts.

  5. Built-in Risk Management: The strategy incorporates stop-loss mechanisms and position sizing (default 5% of account equity per trade), which helps control risk exposure for each trade and protects capital.

Risk Analysis

  1. Low Win Rate Challenge: As noted in the code comments, the strategy may face a low win rate issue. This is because while multiple condition filtering improves signal quality, it also significantly reduces trading frequency, resulting in a smaller sample size with limited statistical significance.

  2. Parameter Sensitivity: The strategy uses multiple adjustable parameters, including MACD lengths, RSI thresholds, and ATR filter parameters. Small changes in these parameters may significantly impact strategy performance, posing a risk of over-optimization.

  3. Limitations of Fixed Percentage Take-Profit/Stop-Loss: Using the same take-profit (1.5%) and stop-loss (0.5%) ratios for all market environments may not adapt to different volatility conditions. In high-volatility markets, the stop-loss may be too tight; in low-volatility markets, the take-profit may be too distant.

  4. Multi-Timeframe Lag: Using signals from longer timeframes (such as 1-hour) as confirmation may introduce lag, causing missed entry opportunities or delayed exits.

  5. Lack of Market Environment Adaptability: The strategy does not include mechanisms to differentiate between different market environments (trending/ranging), which may lead to poor performance under certain market conditions.

Solutions: - Consider introducing adaptive take-profit/stop-loss mechanisms based on ATR or other volatility indicators - Add market environment recognition modules to adjust strategy parameters or trading logic under different conditions - Implement more rigorous backtesting and forward testing validation to avoid over-optimization - Consider adding trading filters, such as time filters or trend strength filters, to further improve signal quality

Optimization Directions

  1. Dynamic Take-Profit/Stop-Loss Optimization: Change the fixed percentage take-profit/stop-loss to dynamic values based on ATR, for example, using 1.5×ATR as stop-loss and 3×ATR as take-profit. This allows the strategy to better adapt to different market volatility environments, providing looser stops in high-volatility periods and tighter profit targets in low-volatility periods.

  2. Market Environment Classification: Introduce market environment recognition mechanisms to distinguish between trending and ranging markets. ADX, Bollinger Band width, or the relationship between price and long-term moving averages can be used to identify market states and adjust strategy parameters or even completely switch trading logic accordingly.

  3. Entry Timing Optimization: The current strategy enters on the current candle when a MACD crossover occurs, potentially facing slippage or execution delays. Consider entering at the open of the next candle after crossover confirmation, or setting limit orders to enter at specific price zones for better execution prices.

  4. Time Filter: Add trading time filters to avoid specific inefficient trading sessions. For example, avoid trading during the end of the Asian session or the European-American transition periods when liquidity may be lower or volatility irregular.

  5. Indicator Parameter Adaptability: Design MACD, RSI, and ATR parameters as adaptive values that automatically adjust based on recent market volatility or cyclicality. For example, use shorter MACD parameters in high-volatility markets and longer parameters in low-volatility markets.

  6. Signal Strength Grading: Establish a strength scoring system for entry signals based on multiple factors (such as MACD histogram size, RSI deviation, VWAP distance, etc.) to score signals, only executing trades with strength exceeding specific thresholds, or dynamically adjusting position size based on signal strength.

  7. Machine Learning Enhancement: Introduce machine learning models to predict which signals are more likely to generate profitable trades, training models to identify the most valuable pattern combinations based on historical data. This can improve the strategy’s adaptability and win rate.

These optimization directions aim to enhance the strategy’s robustness, adaptability, and long-term performance while maintaining its core logic. Through these improvements, the strategy can better respond to changes in different market environments and conditions.

Summary

The Multi-Timeframe MACD-RSI Crossover Volatility-Filtered Quantitative Trading Strategy is a comprehensively designed trading system that identifies high-quality trading opportunities by combining multiple technical indicators and signals from multiple timeframes. The core advantages of this strategy lie in its multi-level signal confirmation mechanism and built-in risk management features, enabling it to capture price movements while controlling risk.

Despite the challenge of a relatively low win rate, the strategy maintains a positive expected value by increasing the average profit of winning trades. By implementing the suggested optimization measures, particularly dynamic take-profit/stop-loss, market environment classification, and signal strength grading, strategy performance can be further enhanced.

This strategy is suitable for medium to short-term traders, especially those seeking a systematic trading approach based on technical analysis who value risk management. The strategy’s multi-condition confirmation mechanism, while reducing trading frequency, improves the quality of each trade, aligning with the trading philosophy that “less is more,” emphasizing quality over quantity.

In practical application, it is recommended that traders first test this strategy in a simulated environment, particularly testing the effects of various optimization measures, before cautiously applying it to live trading. Meanwhile, continuously monitoring changes in market conditions and adjusting strategy parameters accordingly will help maintain stable long-term performance.

Strategy source code
/*backtest
start: 2025-01-01 00:00:00
end: 2025-06-08 00:00:00
period: 1h
basePeriod: 1h
exchanges: [{"eid":"Futures_Binance","currency":"ETH_USDT"}]
*/

// This Pine Script® code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © GentlemanOfTrading

//@version=6
strategy(title = "ETH Day Trader", overlay = true, margin_long = 100, margin_short  = 100, default_qty_type  = strategy.percent_of_equity, default_qty_value = 5)

// ==== 1) USER INPUTS ====
// MACD/RSI lengths
fastLen   = input.int(12,   title="MACD Fast EMA Length", minval=1)
slowLen   = input.int(26,   title="MACD Slow EMA Length", minval=1)
signalLen = input.int(9,    title="MACD Signal EMA Length", minval=1)
rsiLen    = input.int(14,   title="RSI Length", minval=1)

// RSI thresholds
rsiThreshLong30 = input.int(55, title="RSI30m > (Long)", minval=1, maxval=100)
rsiThreshShort30= input.int(45, title="RSI30m < (Short)", minval=1, maxval=100)
rsiThresh1h     = input.int(50, title="RSI1h Threshold", minval=1, maxval=100)

// ATR filter (30m)
atrLen     = input.int(14, title="ATR Length (30m)", minval=1)
atrMaLen   = input.int(20, title="ATR MA Length (30m)", minval=1)

// Take Profit / Stop Loss (percent)
tpPerc      = input.float(1.5,  title="Take Profit (%)", minval=0.1) / 100
slPerc      = input.float(0.5,  title="Stop Loss (%)",   minval=0.1) / 100

// Toggle whether to use 1h trend confirmation
use1hTrend  = input.bool(true, title="Use 1h MACD Trend Confirmation?")

// ==== 2) FETCH INDICATORS ON 30m ====
// We assume this script is applied on a chart ≤ 30m (e.g. 15m or 5m), 
// but if you apply it on a 30m chart it still works: security() with "30" just returns the same bar.

[macd30m, macdSig30m, _] = ta.macd(close, fastLen, slowLen, signalLen)
rsi30m                  = ta.rsi(close, rsiLen)
atr30m                  = ta.atr(atrLen)

// ==== 3) FETCH INDICATORS ON 1h & VWAPs via request.security() ====
// --- 1h MACD & RSI ---
[macd1h, macdSig1h, _] = request.security(syminfo.tickerid, "60", ta.macd(close, fastLen, slowLen, signalLen), lookahead=barmerge.lookahead_off)
rsi1h = request.security(syminfo.tickerid, "60", ta.rsi(close, rsiLen), lookahead=barmerge.lookahead_off)

// --- 30m VWAP & 1h VWAP (session VWAP) ---
vwap30m = request.security(syminfo.tickerid, "30", ta.vwap(close), lookahead=barmerge.lookahead_off)
vwap1h  = request.security(syminfo.tickerid, "60", ta.vwap(close), lookahead=barmerge.lookahead_off)

// ==== 4) BUILD VOLATILITY FILTER (30m ATR vs ATR MA) ====
atr30m_ma = ta.sma(atr30m, atrMaLen)
volatilityOK = atr30m >= atr30m_ma

// ==== 5) MULTI-TIMEFRAME CROSS CONDITIONS ====
// 30m MACD cross signals
longCross30m  = ta.crossover(macd30m, macdSig30m)
shortCross30m = ta.crossunder(macd30m, macdSig30m)

// 1h MACD trend confirmation
macdTrendUp1h   = macd1h > macdSig1h
macdTrendDown1h = macd1h < macdSig1h

// ==== 6) ENTRY & EXIT CONDITIONS ====
// LONG ENTRY: 
//   • 30m MACD crossover 
//   • 30m RSI > rsiThreshLong30 
//   • (optionally) 1h MACD line > 1h MACD signal 
//   • Price > 30m VWAP AND Price > 1h VWAP 
//   • 30m ATR ≥ 30m ATR MA (volatility filter)
longEntryCond = 
     longCross30m 
  and (rsi30m > rsiThreshLong30) 
  and (close > vwap30m) 
  and (close > vwap1h) 
  and volatilityOK 
  and (use1hTrend ? macdTrendUp1h : true)

// LONG EXIT: 
//   • fixed TP/SL 
//   OR • 30m MACD crossunder 
//   OR • 1h MACD falls below signal (trend flipped)
var float entryPriceLong = na
longExitCond = false

if (strategy.position_size > 0)
    // Price-based TP / SL checks
    entryPriceLong := nz(entryPriceLong[1], strategy.position_avg_price)
    longTPprice = entryPriceLong * (1 + tpPerc)
    longSLprice = entryPriceLong * (1 - slPerc)
    
    // check TP/SL first
    longExitTP = high >= longTPprice
    longExitSL = low  <= longSLprice
    
    // fallback: MACD crossunder on 30m OR 1h trend flips
    macdTrendFlip1h = macdTrendUp1h and (macd1h < macdSig1h)
    macdCross30m    = shortCross30m
    
    longExitCond := longExitTP or longExitSL or macdCross30m or macdTrendFlip1h
else
    entryPriceLong := na  // reset when no position

// SHORT ENTRY: 
//   • 30m MACD crossunder 
//   • 30m RSI < rsiThreshShort30 
//   • (optionally) 1h MACD line < 1h MACD signal 
//   • Price < 30m VWAP AND Price < 1h VWAP 
//   • 30m ATR ≥ 30m ATR MA (volatility filter)
shortEntryCond = 
     shortCross30m 
  and (rsi30m < rsiThreshShort30) 
  and (close < vwap30m) 
  and (close < vwap1h) 
  and volatilityOK 
  and (use1hTrend ? macdTrendDown1h : true)

// SHORT EXIT: 
//   • fixed TP/SL 
//   OR • 30m MACD crossover 
//   OR • 1h MACD flips up
var float entryPriceShort = na
shortExitCond = false

if (strategy.position_size < 0)
    entryPriceShort := nz(entryPriceShort[1], strategy.position_avg_price)
    shortTPprice = entryPriceShort * (1 - tpPerc)
    shortSLprice = entryPriceShort * (1 + slPerc)
    
    // check TP/SL first
    shortExitTP = low <= shortTPprice
    shortExitSL = high >= shortSLprice
    
    macdTrendFlipUp1h = macdTrendDown1h and (macd1h > macdSig1h)
    macdCrossUp30m    = longCross30m
    
    shortExitCond := shortExitTP or shortExitSL or macdCrossUp30m or macdTrendFlipUp1h
else
    entryPriceShort := na  // reset when no position

// ==== 7) EXECUTE STRATEGY ORDERS WITH LABELS & ALERTS ====
// — Long Entry —
if (longEntryCond and strategy.position_size == 0)
    strategy.entry("Long", strategy.long)
    label.new(bar_index, low, text="Buy (LT)", style=label.style_label_up, color=color.new(color.green, 0), textcolor=color.white, yloc=yloc.belowbar)
    alert("Buy (LT)", alert.freq_once_per_bar_close)

// — Long Exit —
if (strategy.position_size > 0 and longExitCond)
    strategy.close("Long")
    label.new(bar_index, high, text="Sell (LT)", style=label.style_label_down, color=color.new(color.red, 0), textcolor=color.white, yloc=yloc.abovebar)
    alert("Sell (LT)", alert.freq_once_per_bar_close)

// — Short Entry —
if (shortEntryCond and strategy.position_size == 0)
    strategy.entry("Short", strategy.short)
    label.new(bar_index, high, text="Sell (ST)", style=label.style_label_down, color=color.new(color.red, 0), textcolor=color.white, yloc=yloc.abovebar)
    alert("Sell (ST)", alert.freq_once_per_bar_close)

// — Short Exit —
if (strategy.position_size < 0 and shortExitCond)
    strategy.close("Short")
    label.new(bar_index, low, text="Buy (ST)", style=label.style_label_up, color=color.new(color.green, 0), textcolor=color.white, yloc=yloc.belowbar)
    alert("Buy (ST)", alert.freq_once_per_bar_close)


// ==== 8) OPTIONAL PLOTTING (for debugging) ====
// We’ve removed any `transp`/`opacity` arguments. Instead, we use `color.new(baseColor, α)` 
// for transparency, where α = 0 is fully opaque and α = 255 is fully transparent.

// 30m VWAP
plot(vwap30m, title = "VWAP 30m", color = color.new(color.teal, 80)) // ~31% transparentlinewidth= 1

// 1h VWAP
plot(vwap1h,  title = "VWAP 1h",  color = color.new(color.fuchsia, 80), linewidth= 1) // ~31% transparent

// 30m ATR vs ATR_MA
plot(atr30m, title = "ATR 30m", color = color.new(color.orange, 80))
plot(atr30m_ma, title = "ATR30m MA", color = color.new(color.yellow, 80))

// 30m MACD Histogram (bars)
plot(macd30m - macdSig30m, title = "MACD Histogram (30m)", style = plot.style_columns, color = (macd30m - macdSig30m >= 0 ? color.new(color.green, 80) : color.new(color.red, 80)))

// 1h MACD Histogram (area)
h1 = request.security(syminfo.tickerid, "60", macd1h - macdSig1h, lookahead=barmerge.lookahead_off)
plot(1, title = "MACD Hist (1h)", style = plot.style_area, color = (h1 >= 0 ? color.new(color.green, 80) : color.new(color.red, 80)))