DEV Community

FMZQuant
FMZQuant

Posted on

Multi-Timeframe Price Structure Recognition and Fair Value Gap Quantitative Trading System

Image description

Image description

Overview
The Multi-Timeframe Price Structure Recognition and Fair Value Gap Quantitative Trading System is an automated trading strategy based on price action principles, combining two key trading concepts: Change of Character (CHoCH) and Fair Value Gap (FVG). This strategy captures high-probability trading opportunities by identifying market structure change points and imbalance zones, entering trades when price retraces to fair value gaps. This systematic approach allows traders to analyze markets objectively, eliminate emotional factors, and implement clear risk management rules.

Strategy Principles
This quantitative trading system operates based on the following core principles:

Price Structure Identification: The system uses Pivot Points technique to identify swing highs and swing lows in the market, which are key components of market structure. It employs a parameterized swing length (default 5 periods) to determine these critical points.

Change of Character (CHoCH) Detection:

  • Bullish CHoCH: Price breaks above a prior swing high after making a lower low
  • Bearish CHoCH: Price breaks below a prior swing low after making a higher high

The system requires a minimum distance between CHoCH formations (default 10 bars) to filter out invalid signals.

Fair Value Gap (FVG) Identification:

  • Bullish FVG: When the low of the current candle is higher than the high of a candle 2 bars ago
  • Bearish FVG: When the high of the current candle is lower than the low of a candle 2 bars ago

The system sets a minimum FVG size threshold (default 2 ticks) to ensure it only captures meaningful price imbalances.

  • Entry Logic:

  • Long Entry: After confirming a bullish CHoCH, wait for price to retrace to the bullish FVG zone

  • Short Entry: After confirming a bearish CHoCH, wait for price to retrace to the bearish FVG zone

Entry price is set at the midpoint of the FVG region, providing a balanced entry price.

Risk Management Mechanism:

  • Stop loss is placed at the recent swing low (for longs) or swing high (for shorts)
  • Take profit is based on either risk-reward ratio (default 2.0) or a fixed target in ticks
  • Optional automatic position sizing based on account risk percentage and stop loss distance

Strategy Advantages
After deep analysis of the code, this strategy exhibits the following significant advantages:

  1. Structured Market Analysis: The strategy is based on price structure changes and market imbalance principles rather than simple indicator crossovers, giving it a unique advantage in identifying market turning points.

  2. Precise Entry Timing: By waiting for FVG formation after CHoCH, the strategy can enter at favorable price levels, avoiding chasing highs or lows, thus improving entry quality.

  3. Adaptive Risk Management: The strategy automatically adjusts stop loss placement based on actual market structure rather than using fixed points, a method more aligned with real market volatility characteristics.

  4. Visualization of Trading Elements: The strategy provides comprehensive visualization features including CHoCH labels, FVG boxes, swing points, and trade lines, allowing traders to intuitively understand market structure and strategy logic.

  5. Flexible Position Management: By automatically adjusting position size based on risk percentage, the system both protects account capital and automatically adjusts risk exposure according to volatility.

  6. Performance-Optimized Design: The code includes mechanisms for cleaning up old FVG boxes, ensuring system performance doesn't degrade during long-term operation.

  7. Comprehensive Performance Monitoring: The strategy provides a real-time performance table including strategy status, win rate, profit factor, and other key metrics, making it convenient for traders to evaluate strategy performance.

Strategy Risks
Despite being well-designed, the strategy still has some potential risks and limitations:

  1. False Breakout Risk: CHoCH signals may be false breakouts, causing price to quickly retrace and trigger stop losses. To mitigate this risk, consider adding confirmation mechanisms, such as waiting for multiple candles to confirm the breakout.

  2. Gap Risk: In volatile markets or overnight trading, price may gap beyond stop loss levels, resulting in actual losses exceeding expectations. Consider using guaranteed stop orders (if available) or reducing position size.

  3. Parameter Sensitivity: Strategy performance is highly dependent on parameters such as swing length, minimum CHoCH distance, and FVG size. Different markets and timeframes may require different parameter combinations, so comprehensive backtesting optimization is recommended.

  4. Market Environment Dependency: This strategy performs better in trending markets and may produce frequent false signals in ranging markets. Consider adding trend filters or market state recognition mechanisms.

  5. Computational Complexity: The strategy uses multiple arrays and condition checks, which may cause performance issues on lower-spec devices. Although the code includes cleanup mechanisms, resource consumption should be monitored during long-term operation.

  6. Drawdown Management Insufficiency: The current strategy doesn't consider dynamic position sizing adjustments under different market conditions, which may lead to significant drawdowns in continuously adverse environments. Implementing account drawdown limits and gradual position reduction mechanisms is recommended.

Optimization Directions
Based on code analysis, the following optimization directions are proposed:

Multi-Timeframe Confirmation: Introduce higher timeframe market structure analysis, trading only in the direction of the main trend. For example, add a daily trend filter and only execute trades consistent with the daily trend direction.

Dynamic Parameter Optimization: Implement a parameter system that automatically adjusts based on market volatility. For example, increase minimum FVG size and CHoCH distance requirements during high volatility periods and decrease these parameters during low volatility periods.

Entry Optimization:

  • Implement a scaled entry strategy, for example, setting multiple entry points at different levels within the FVG zone
  • Add additional entry confirmations, such as volume breakouts or momentum indicator confirmations

Risk Management Enhancement:

  • Implement trailing stop functionality that automatically adjusts stop loss positions as trades move into profit
  • Add partial profit-taking functionality, closing part of the position when certain profit levels are reached
  • Introduce maximum drawdown limits, automatically reducing position size or pausing trading when account drawdown reaches thresholds

Market State Adaptation:

  • Add market state recognition (trending/ranging/high volatility) and adjust strategy parameters according to different states
  • Reduce or avoid trading in ranging markets
  • Adopt more conservative position sizing when volatility suddenly increases

Machine Learning Enhancement: Introduce machine learning algorithms to analyze historical CHoCH and FVG patterns, identify pattern features with higher success rates, and adjust entry decision weights accordingly.

Trading Time Filters: Add trading time filters to avoid high volatility periods around major news announcements and market openings/closings, focusing on trading sessions with better liquidity.

Summary
The Multi-Timeframe Price Structure Recognition and Fair Value Gap Quantitative Trading System is a complete trading solution that combines advanced price action theories. It identifies market structure changes (CHoCH) and price imbalance areas (FVG) to enter at ideal price levels, while employing systematic risk management methods to protect trading capital.

The strategy's greatest advantage lies in its analysis method based on actual market structure rather than lagging indicators, allowing it to identify market turning points earlier. Meanwhile, comprehensive visualization features and performance monitoring systems enable traders to intuitively understand strategy logic and evaluate its effectiveness.

Although risks such as false breakouts and parameter sensitivity exist, through the proposed optimization directions—especially multi-timeframe confirmation, dynamic parameter adjustments, and enhanced risk management functions—the strategy's stability and performance can be significantly improved.

For investors looking to adopt a systematic approach to trading, this strategy provides a solid framework that incorporates both the essence of traditional price action trading and the objectivity and discipline advantages of quantitative systems. Through continuous parameter optimization and market adaptability adjustments, this strategy has the potential to achieve stable trading performance across various market environments.

Strategy source code

/*backtest
start: 2024-06-03 00:00:00
end: 2025-06-02 00:00:00
period: 2h
basePeriod: 2h
exchanges: [{"eid":"Futures_Binance","currency":"ETH_USDT"}]
*/

//@version=5
strategy("ICT CHoCH & FVG Strategy - NQ1!", overlay=true, pyramiding=0, calc_on_every_tick=false, calc_on_order_fills=false, max_boxes_count=500, max_lines_count=100, max_labels_count=100)

// ============================================================================
// INPUT PARAMETERS
// ============================================================================

// Strategy Settings
riskRewardRatio = input.float(2.0, title="Risk:Reward Ratio", minval=0.5, maxval=10.0, group="Strategy Settings")
fixedTarget = input.int(40, title="Fixed Target (Ticks)", minval=5, maxval=200, group="Strategy Settings")
useRRTarget = input.bool(true, title="Use Risk:Reward Target", tooltip="If false, uses fixed target", group="Strategy Settings")
riskPercent = input.float(2.0, title="Risk % of Account", minval=0.1, maxval=10.0, group="Strategy Settings")
useAutoSize = input.bool(false, title="Auto Size Positions", tooltip="Size based on risk % and stop distance", group="Strategy Settings")

// Visual Settings
showCHoCH = input.bool(true, title="Show CHoCH Labels", group="Visual Settings")
showFVG = input.bool(true, title="Show FVG Boxes", group="Visual Settings")
showSwings = input.bool(true, title="Show Swing Points", group="Visual Settings")
showTradeLines = input.bool(true, title="Show Entry/SL/TP Lines", group="Visual Settings")

// CHoCH Detection Settings
swingLength = input.int(5, title="Swing Detection Length", minval=2, maxval=20, group="CHoCH Settings")
minCHoCHDistance = input.int(10, title="Min CHoCH Distance (bars)", minval=5, maxval=50, group="CHoCH Settings")

// FVG Settings
minFVGSize = input.float(2.0, title="Min FVG Size (ticks)", minval=0.25, maxval=10.0, group="FVG Settings")
maxFVGAge = input.int(50, title="Max FVG Age (bars)", minval=10, maxval=200, group="FVG Settings")

// ============================================================================
// VARIABLES AND ARRAYS
// ============================================================================

// Swing point detection
var float lastSwingHigh = na
var float lastSwingLow = na
var int lastSwingHighBar = na
var int lastSwingLowBar = na

// CHoCH tracking
var bool bullishCHoCH = false
var bool bearishCHoCH = false
var float chochLevel = na
var int chochBar = na
var bool waitingForFVG = false

// FVG tracking
var array<box> bullishFVGs = array.new<box>()
var array<box> bearishFVGs = array.new<box>()
var float activeFVGTop = na
var float activeFVGBottom = na
var bool lookingForEntry = false

// Trade management
var float stopLossLevel = na
var float takeProfitLevel = na
var bool inPosition = false

// ============================================================================
// HELPER FUNCTIONS
// ============================================================================

// Convert ticks to price for NQ
ticksToPrice(ticks) => ticks * 0.25

// Calculate position size based on risk
calcPositionSize(stopDistance) =>
    if useAutoSize and strategy.equity > 0
        accountValue = strategy.equity
        riskAmount = accountValue * (riskPercent / 100)
        stopDistancePrice = stopDistance * syminfo.mintick
        math.max(1, math.floor(riskAmount / stopDistancePrice))
    else
        1

// ============================================================================
// SWING POINT DETECTION
// ============================================================================

// Detect swing highs and lows
swingHigh = ta.pivothigh(high, swingLength, swingLength)
swingLow = ta.pivotlow(low, swingLength, swingLength)

// Update swing points
if not na(swingHigh)
    lastSwingHigh := swingHigh
    lastSwingHighBar := bar_index - swingLength
    if showSwings
        label.new(bar_index - swingLength, swingHigh, "SH", style=label.style_triangledown, color=color.red, size=size.tiny)

if not na(swingLow)
    lastSwingLow := swingLow
    lastSwingLowBar := bar_index - swingLength
    if showSwings
        label.new(bar_index - swingLength, swingLow, "SL", style=label.style_triangleup, color=color.green, size=size.tiny)

// ============================================================================
// CHoCH DETECTION
// ============================================================================

// Check for bullish CHoCH (break above prior swing high after making lower low)
bullishCHoCHCondition = not na(lastSwingHigh) and not na(lastSwingLow) and 
                       high > lastSwingHigh and 
                       lastSwingLow < lastSwingHigh and
                       bar_index - lastSwingHighBar > minCHoCHDistance and
                       strategy.position_size == 0

// Check for bearish CHoCH (break below prior swing low after making higher high)
bearishCHoCHCondition = not na(lastSwingHigh) and not na(lastSwingLow) and 
                       low < lastSwingLow and 
                       lastSwingHigh > lastSwingLow and
                       bar_index - lastSwingLowBar > minCHoCHDistance and
                       strategy.position_size == 0

// Process CHoCH signals
if bullishCHoCHCondition and not bullishCHoCH
    bullishCHoCH := true
    bearishCHoCH := false
    chochLevel := lastSwingHigh
    chochBar := bar_index
    waitingForFVG := true
    lookingForEntry := false



if bearishCHoCHCondition and not bearishCHoCH
    bearishCHoCH := true
    bullishCHoCH := false
    chochLevel := lastSwingLow
    chochBar := bar_index
    waitingForFVG := true
    lookingForEntry := false



// ============================================================================
// FVG DETECTION
// ============================================================================

// Check for FVG formation (3-candle pattern)
if bar_index >= 2
    // Bullish FVG: low[0] > high[2]
    bullishFVG = low[0] > high[2] and (low[0] - high[2]) >= ticksToPrice(minFVGSize)

    // Bearish FVG: high[0] < low[2]  
    bearishFVG = high[0] < low[2] and (low[2] - high[0]) >= ticksToPrice(minFVGSize)

    // Process bullish FVG after bullish CHoCH
    if bullishFVG and bullishCHoCH and waitingForFVG and bar_index > chochBar
        fvgTop = low[0]
        fvgBottom = high[2]



        // Set active FVG for entry
        activeFVGTop := fvgTop
        activeFVGBottom := fvgBottom
        waitingForFVG := false
        lookingForEntry := true

    // Process bearish FVG after bearish CHoCH
    if bearishFVG and bearishCHoCH and waitingForFVG and bar_index > chochBar
        fvgTop = low[2]
        fvgBottom = high[0]



        // Set active FVG for entry
        activeFVGTop := fvgTop
        activeFVGBottom := fvgBottom
        waitingForFVG := false
        lookingForEntry := true

// ============================================================================
// ENTRY LOGIC
// ============================================================================

// Long entry: price touches bullish FVG after bullish CHoCH
longCondition = lookingForEntry and bullishCHoCH and 
               not na(activeFVGTop) and not na(activeFVGBottom) and
               low <= activeFVGTop and high >= activeFVGBottom and
               strategy.position_size == 0

// Short entry: price touches bearish FVG after bearish CHoCH  
shortCondition = lookingForEntry and bearishCHoCH and  not na(activeFVGTop) and not na(activeFVGBottom) and low <= activeFVGTop and high >= activeFVGBottom and  strategy.position_size == 0

// Process long entries
if longCondition
    var float entryPrice = na
    var float stopLoss = na
    var float takeProfit = na

    entryPrice := math.avg(activeFVGTop, activeFVGBottom)
    stopLoss := lastSwingLow
    stopDistance = entryPrice - stopLoss

    if useRRTarget
        takeProfit := entryPrice + (stopDistance * riskRewardRatio)
    else
        takeProfit := entryPrice + ticksToPrice(fixedTarget)

    // Calculate position size
    qty = calcPositionSize(stopDistance / syminfo.mintick)

    // Enter trade
    strategy.entry("Long", strategy.long, qty=qty)
    strategy.exit("Long Exit", "Long", stop=stopLoss, limit=takeProfit)

    // Update tracking
    stopLossLevel := stopLoss
    takeProfitLevel := takeProfit
    inPosition := true
    lookingForEntry := false

    // Reset CHoCH state
    bullishCHoCH := false
    activeFVGTop := na
    activeFVGBottom := na



// Process short entries
if shortCondition
    var float entryPrice = na
    var float stopLoss = na
    var float takeProfit = na

    entryPrice := math.avg(activeFVGTop, activeFVGBottom)
    stopLoss := lastSwingHigh
    stopDistance = stopLoss - entryPrice

    if useRRTarget
        takeProfit := entryPrice - (stopDistance * riskRewardRatio)
    else
        takeProfit := entryPrice - ticksToPrice(fixedTarget)

    // Calculate position size
    qty = calcPositionSize(stopDistance / syminfo.mintick)

    // Enter trade
    strategy.entry("Short", strategy.short, qty=qty)
    strategy.exit("Short Exit", "Short", stop=stopLoss, limit=takeProfit)

    // Update tracking
    stopLossLevel := stopLoss
    takeProfitLevel := takeProfit
    inPosition := true
    lookingForEntry := false

    // Reset CHoCH state
    bearishCHoCH := false
    activeFVGTop := na
    activeFVGBottom := na


// ============================================================================
// POSITION MANAGEMENT
// ============================================================================

// Reset position state when trade is closed
if inPosition and strategy.position_size == 0
    inPosition := false
    stopLossLevel := na
    takeProfitLevel := na

// ============================================================================
// VISUAL SIGNALS
// ============================================================================

// Plot entry signals
plotshape(longCondition, title="Long Entry", location=location.belowbar, color=color.green, 
          style=shape.triangleup, size=size.normal)

plotshape(shortCondition, title="Short Entry", location=location.abovebar, color=color.red, 
          style=shape.triangledown, size=size.normal)

// Plot active stop loss and take profit levels
plot(inPosition ? stopLossLevel : na, title="Stop Loss", color=color.red, linewidth=2, style=plot.style_linebr)
plot(inPosition ? takeProfitLevel : na, title="Take Profit", color=color.green, linewidth=2, style=plot.style_linebr)

// ============================================================================
// CLEANUP
// ============================================================================

// Clean up old FVG boxes (helps with performance)
if bar_index % 100 == 0
    while array.size(bullishFVGs) > 20
        box.delete(array.shift(bullishFVGs))
    while array.size(bearishFVGs) > 20
        box.delete(array.shift(bearishFVGs))

// ============================================================================
// ALERTS
// ============================================================================

// Alert conditions
alertcondition(longCondition, title="Long Entry Signal", message="ICT Strategy: Long entry at FVG - SL: {{strategy.position_avg_price}}")
alertcondition(shortCondition, title="Short Entry Signal", message="ICT Strategy: Short entry at FVG - SL: {{strategy.position_avg_price}}")

Enter fullscreen mode Exit fullscreen mode

Strategy parameters

Image description

Image description

The original address: Multi-Timeframe Price Structure Recognition and Fair Value Gap Quantitative Trading System

Top comments (1)

Collapse
 
gavin_g_ebd1d919ab190af19 profile image
Grace

Impressive work! Combining multi-timeframe structure with FVGs brings a solid edge to the strategy. The logic is clean and well-explained, making it approachable even for intermediate quants. Excited to see how it performs in live markets!