Advertisement IC Markets

Using MFE to Examine Your Targets and Exits

Document your personal trading journey. Track daily equity curves, review winning and losing streaks, share trade screenshots, and get constructive feedback.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Using MFE to Examine Your Targets and Exits

Post by PTScalper »

Here is a complete TradingView Pine Script (v5) designed to programmatically extract and display your Maximum Favorable Excursion (MFE) and Capture Ratio data.

Since your methodology relies on raw price action and market structure rather than lagging technical indicators, I have kept the entry and exit triggers as modular placeholders. You can seamlessly plug your own liquidity sweep or 15m/1m structural setups into those variables.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Recommended broker for automated trading & scalping IC Markets
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Using MFE to Examine Your Targets and Exits

Post by PTScalper »

The script automatically generates an on-chart dashboard displaying the exact metric we discussed: the average Capture Ratio of your recent winners, alongside how far your losers traveled in your favor before reversing.

Code: Select all

//@version=5
strategy("MFE & Capture Ratio Dashboard", overlay=true, calc_on_every_tick=true, max_labels_count=500)

// =========================================================================
// 1. MODULAR ENTRY/EXIT LOGIC (Replace with your Price Action setups)
// =========================================================================
// Placeholder: Simple moving average crossover for demonstration purposes
fastMA = ta.sma(close, 9)
slowMA = ta.sma(close, 21)

longSetup  = ta.crossover(fastMA, slowMA)
shortSetup = ta.crossunder(fastMA, slowMA)
exitLong   = ta.crossunder(fastMA, slowMA)
exitShort  = ta.crossover(fastMA, slowMA)

if (longSetup)
    strategy.entry("Long", strategy.long)
if (shortSetup)
    strategy.entry("Short", strategy.short)

if (exitLong)
    strategy.close("Long")
if (exitShort)
    strategy.close("Short")

// =========================================================================
// 2. MFE & CAPTURE RATIO CALCULATIONS (Using built-in trade history)
// =========================================================================
var float totalWinningCapture = 0.0
var int   winningTradeCount   = 0

var float totalLoserMFE       = 0.0
var int   losingTradeCount    = 0

// We use the strategy.closedtrades namespace to analyze trades the moment they close
if strategy.closedtrades > nz(strategy.closedtrades[1])
    // Get the index of the last closed trade
    lastTradeIndex = strategy.closedtrades - 1
    
    // Extract profit and maximum runup (MFE) in account currency
    tradeProfit = strategy.closedtrades.profit(lastTradeIndex)
    tradeMFE    = strategy.closedtrades.max_runup(lastTradeIndex)
    
    // Calculate Capture Ratio for Winners
    if tradeProfit > 0
        // Avoid division by zero if MFE happens to be exactly 0 (rare on winners)
        captureRatio = tradeMFE > 0 ? (tradeProfit / tradeMFE) * 100 : 0
        totalWinningCapture += captureRatio
        winningTradeCount += 1
        
        // Print a label on the chart at the exit point for visual review
        label.new(bar_index, high, text="Win CR:\n" + str.tostring(captureRatio, "#.##") + "%", 
                  color=color.new(color.green, 80), style=label.style_label_down, textcolor=color.green)

    // Calculate how far Losers went in our favor before stopping out
    else if tradeProfit <= 0
        totalLoserMFE += tradeMFE
        losingTradeCount += 1
        
        label.new(bar_index, low, text="Loss MFE:\n" + str.tostring(tradeMFE, "#.##") + " $", 
                  color=color.new(color.red, 80), style=label.style_label_up, textcolor=color.red)

// =========================================================================
// 3. ON-CHART DASHBOARD DISPLAY
// =========================================================================
var table mfeTable = table.new(position.bottom_right, 2, 4, border_width=1, border_color=color.gray, frame_color=color.gray, frame_width=1)

if barstate.islast
    avgWinningCapture = winningTradeCount > 0 ? (totalWinningCapture / winningTradeCount) : 0
    avgLoserMFE       = losingTradeCount > 0 ? (totalLoserMFE / losingTradeCount) : 0
    
    // Header
    table.cell(mfeTable, 0, 0, "Trade Efficiency Metrics", text_color=color.white, bgcolor=color.new(color.blue, 30), text_size=size.normal)
    table.merge_cells(mfeTable, 0, 0, 1, 0)
    
    // Winning Stats
    table.cell(mfeTable, 0, 1, "Avg Winner Capture Ratio:", text_color=color.white, bgcolor=color.new(color.black, 50))
    table.cell(mfeTable, 1, 1, str.tostring(avgWinningCapture, "#.##") + "%", text_color=color.green, bgcolor=color.new(color.black, 50))
    
    // Losing Stats
    table.cell(mfeTable, 0, 2, "Avg Loser Peak MFE ($):", text_color=color.white, bgcolor=color.new(color.black, 50))
    table.cell(mfeTable, 1, 2, str.tostring(avgLoserMFE, "#.##"), text_color=color.red, bgcolor=color.new(color.black, 50))
    
    // Trade Counts
    table.cell(mfeTable, 0, 3, "Total Trades Analyzed:", text_color=color.white, bgcolor=color.new(color.black, 50))
    table.cell(mfeTable, 1, 3, str.tostring(winningTradeCount + losingTradeCount), text_color=color.white, bgcolor=color.new(color.black, 50))
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Using MFE to Examine Your Targets and Exits

Post by PTScalper »

How to Apply This to Your Workflow

The Modular Setup (Section 1): Rip out the MA crossover code and insert your specific execution logic. For example, if you trigger short trades upon a break of an inside bar low after a liquidity sweep, code that logic into the shortSetup variable.

The Labels (Section 2): When a trade closes, the script renders a label directly on the candle. For winners, it shows the exact percentage of the peak profit you captured. For losers, it shows the maximum dollar amount the trade was in profit before it reversed and hit your stop. This visually highlights exactly where a breakeven stop or partial take-profit might have saved the trade.

The Dashboard (Section 3): On the bottom right of your screen, a table will calculate the lifetime average of these metrics for the loaded chart data, fulfilling the 20-trade baseline analysis instantly.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Using MFE to Examine Your Targets and Exits

Post by PTScalper »

To elevate this from a basic script to an institutional-grade tool, the architecture needs to reflect proper software engineering principles and quantitative risk management.

This upgraded version introduces User-Defined Types (UDTs), Arrays for rolling window analysis, realistic execution costs, and a modular structure designed for 1-minute and 15-minute price action environments.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Using MFE to Examine Your Targets and Exits

Post by PTScalper »

Architectural Upgrades in this Version:

Rolling Window Analysis: Instead of lifetime averages (which get skewed by old market regimes), it uses arrays to track only the last N trades (e.g., 20) to give you a dynamic read on your current edge.

Object-Oriented Structure: Uses Pine v5's type and method syntax to encapsulate trade data cleanly, avoiding global variable spaghetti.

Execution Realism: Bakes in commissions and slippage directly into the strategy declaration to ensure the Maximum Favorable Excursion (MFE) calculations reflect actual net profit, not gross theoretical profit.

Price Action Placeholder: Replaced the moving averages with a clean, modular placeholder based on session extremes and momentum, keeping the execution layer decoupled from the analytics layer.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Using MFE to Examine Your Targets and Exits

Post by PTScalper »

Pine script PRO version

Code: Select all

//@version=5
strategy("Pro MFE & Efficiency Engine", overlay=true, calc_on_every_tick=true, max_labels_count=50, commission_type=strategy.commission.cash_per_order, commission_value=3.0, slippage=1)

// =========================================================================
// 1. INPUTS & CONFIGURATION
// =========================================================================
grpStats = "Analytics Configuration"
i_rollingWindow = input.int(20, "Rolling Window (Last N Trades)", group=grpStats, tooltip="Calculates MFE metrics on a rolling basis to adapt to current market regimes.")
i_showLabels    = input.bool(true, "Show Trade Labels", group=grpStats)

// =========================================================================
// 2. USER-DEFINED TYPES (UDT) & STATE MANAGEMENT
// =========================================================================
type TradeMetrics
    float realizedProfit
    float maxRunup
    float captureRatio
    bool  isWinner

var array<TradeMetrics> recentTrades = array.new<TradeMetrics>()

// Method to add new trade to rolling array
method pushTrade(array<TradeMetrics> arr, TradeMetrics newTrade, int maxSize) =>
    array.push(arr, newTrade)
    if array.size(arr) > maxSize
        array.shift(arr)

// =========================================================================
// 3. MODULAR PRICE ACTION EXECUTION (Placeholder)
// =========================================================================
// Replace this block with custom liquidity sweep or order block logic. 
// Currently set up as a simple momentum continuation for structural testing.
lookbackHigh = ta.highest(high, 15)
lookbackLow  = ta.lowest(low, 15)

longTrigger  = ta.crossover(close, lookbackHigh[1])
shortTrigger = ta.crossunder(close, lookbackLow[1])

if longTrigger and strategy.position_size == 0
    strategy.entry("Long", strategy.long)
    // Dynamic structural stop and target
    strategy.exit("Exit Long", "Long", stop=low - ta.atr(14), profit=close + (ta.atr(14) * 2))

if shortTrigger and strategy.position_size == 0
    strategy.entry("Short", strategy.short)
    strategy.exit("Exit Short", "Short", stop=high + ta.atr(14), profit=close - (ta.atr(14) * 2))

// =========================================================================
// 4. MFE EXTRACTION & ROLLING CALCULATION
// =========================================================================
if strategy.closedtrades > nz(strategy.closedtrades[1])
    lastIndex = strategy.closedtrades - 1
    
    t_profit = strategy.closedtrades.profit(lastIndex)
    t_runup  = strategy.closedtrades.max_runup(lastIndex)
    t_winner = t_profit > 0
    
    // Calculate efficiency (avoid div by zero)
    t_efficiency = (t_winner and t_runup > 0) ? (t_profit / t_runup) * 100 : 0.0
    
    // Instantiate UDT and push to rolling array
    newMetric = TradeMetrics.new(t_profit, t_runup, t_efficiency, t_winner)
    recentTrades.pushTrade(newMetric, i_rollingWindow)
    
    // Clean, non-intrusive UI labels
    if i_showLabels
        if t_winner
            label.new(bar_index, high, str.tostring(t_efficiency, "#.#") + "% CR", color=color.new(color.teal, 20), style=label.style_label_down, textcolor=color.white, size=size.small)
        else
            label.new(bar_index, low, "$" + str.tostring(t_runup, "#.#") + " MFE", color=color.new(color.maroon, 20), style=label.style_label_up, textcolor=color.white, size=size.small)

// =========================================================================
// 5. QUANTITATIVE DASHBOARD (Aggregating Array Data)
// =========================================================================
var table dash = table.new(position.bottom_right, 2, 5, border_width=1, border_color=color.new(color.gray, 80), frame_color=color.new(color.gray, 80), frame_width=1)

if barstate.islast
    float sumWinEfficiency = 0.0
    float sumLossRunup     = 0.0
    int   winCount         = 0
    int   lossCount        = 0
    
    // Iterate through the custom object array
    if array.size(recentTrades) > 0
        for trade in recentTrades
            if trade.isWinner
                sumWinEfficiency += trade.captureRatio
                winCount += 1
            else
                sumLossRunup += trade.maxRunup
                lossCount += 1

    avgEfficiency = winCount > 0 ? (sumWinEfficiency / winCount) : 0.0
    avgLossRunup  = lossCount > 0 ? (sumLossRunup / lossCount) : 0.0
    winRate       = array.size(recentTrades) > 0 ? (winCount / array.size(recentTrades)) * 100 : 0.0

    // Render Dashboard
    cellBg = color.new(#1e222d, 10)
    
    table.cell(dash, 0, 0, "Rolling Edge (" + str.tostring(array.size(recentTrades)) + " Trades)", text_color=color.white, bgcolor=color.new(color.gray, 80), text_size=size.small)
    table.merge_cells(dash, 0, 0, 1, 0)
    
    table.cell(dash, 0, 1, "Win Rate:", text_color=color.gray, bgcolor=cellBg, text_size=size.small, text_halign=text.align_left)
    table.cell(dash, 1, 1, str.tostring(winRate, "#.#") + "%", text_color=color.white, bgcolor=cellBg, text_size=size.small, text_halign=text.align_right)
    
    table.cell(dash, 0, 2, "Winner Capture Ratio:", text_color=color.gray, bgcolor=cellBg, text_size=size.small, text_halign=text.align_left)
    table.cell(dash, 1, 2, str.tostring(avgEfficiency, "#.#") + "%", text_color=color.teal, bgcolor=cellBg, text_size=size.small, text_halign=text.align_right)
    
    table.cell(dash, 0, 3, "Loser Avg Peak MFE:", text_color=color.gray, bgcolor=cellBg, text_size=size.small, text_halign=text.align_left)
    table.cell(dash, 1, 3, "$" + str.tostring(avgLossRunup, "#.##"), text_color=color.maroon, bgcolor=cellBg, text_size=size.small, text_halign=text.align_right)
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Using MFE to Examine Your Targets and Exits

Post by PTScalper »

To track drawdown effectively alongside MFE, we need to measure two different layers of risk: Rolling Sequence Risk (the maximum closed-trade dollar drawdown experienced specifically within your last N trades) and Tick-by-Tick Account Heat (the real-time, open-equity percentage drawdown of the entire account).

This updated engine adds a localized equity curve calculation directly into the array loop to measure the rolling sequence risk, and uses strategy.equity to track real-time open drawdown.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Using MFE to Examine Your Targets and Exits

Post by PTScalper »

Pine script version 2.0 PRO

Code: Select all

//@version=5
strategy("Pro MFE & Drawdown Engine", overlay=true, calc_on_every_tick=true, max_labels_count=50, commission_type=strategy.commission.cash_per_order, commission_value=3.0, slippage=1)

// =========================================================================
// 1. INPUTS & CONFIGURATION
// =========================================================================
grpStats = "Analytics Configuration"
i_rollingWindow = input.int(20, "Rolling Window (Last N Trades)", group=grpStats, tooltip="Calculates MFE and localized drawdown on a rolling basis to adapt to current regimes.")
i_showLabels    = input.bool(true, "Show Trade Labels", group=grpStats)

// =========================================================================
// 2. USER-DEFINED TYPES (UDT) & STATE MANAGEMENT
// =========================================================================
type TradeMetrics
    float realizedProfit
    float maxRunup
    float captureRatio
    bool  isWinner

var array<TradeMetrics> recentTrades = array.new<TradeMetrics>()

// Method to push new trades and maintain the rolling window size
method pushTrade(array<TradeMetrics> arr, TradeMetrics newTrade, int maxSize) =>
    array.push(arr, newTrade)
    if array.size(arr) > maxSize
        array.shift(arr) // Removes the oldest trade (index 0)

// Tick-by-tick global equity trackers
var float maxAccountEquity = strategy.initial_capital
maxAccountEquity := math.max(maxAccountEquity, strategy.equity)

// =========================================================================
// 3. MODULAR PRICE ACTION EXECUTION (Placeholder)
// =========================================================================
// Replace with your specific 1m/15m structural sweep logic.
lookbackHigh = ta.highest(high, 15)
lookbackLow  = ta.lowest(low, 15)

longTrigger  = ta.crossover(close, lookbackHigh[1])
shortTrigger = ta.crossunder(close, lookbackLow[1])

if longTrigger and strategy.position_size == 0
    strategy.entry("Long", strategy.long)
    strategy.exit("Exit Long", "Long", stop=low - ta.atr(14), profit=close + (ta.atr(14) * 2))

if shortTrigger and strategy.position_size == 0
    strategy.entry("Short", strategy.short)
    strategy.exit("Exit Short", "Short", stop=high + ta.atr(14), profit=close - (ta.atr(14) * 2))

// =========================================================================
// 4. MFE EXTRACTION & ARRAY POPULATION
// =========================================================================
if strategy.closedtrades > nz(strategy.closedtrades[1])
    lastIndex = strategy.closedtrades - 1
    
    t_profit = strategy.closedtrades.profit(lastIndex)
    t_runup  = strategy.closedtrades.max_runup(lastIndex)
    t_winner = t_profit > 0
    
    // Capture Ratio Efficiency
    t_efficiency = (t_winner and t_runup > 0) ? (t_profit / t_runup) * 100 : 0.0
    
    // Instantiate UDT and push to rolling array
    newMetric = TradeMetrics.new(t_profit, t_runup, t_efficiency, t_winner)
    recentTrades.pushTrade(newMetric, i_rollingWindow)
    
    // Clean UI labels
    if i_showLabels
        if t_winner
            label.new(bar_index, high, str.tostring(t_efficiency, "#.#") + "% CR", color=color.new(color.teal, 20), style=label.style_label_down, textcolor=color.white, size=size.small)
        else
            label.new(bar_index, low, "$" + str.tostring(t_runup, "#.#") + " MFE", color=color.new(color.maroon, 20), style=label.style_label_up, textcolor=color.white, size=size.small)

// =========================================================================
// 5. QUANTITATIVE DASHBOARD (Aggregating Array & Equity Data)
// =========================================================================
var table dash = table.new(position.bottom_right, 2, 7, border_width=1, border_color=color.new(color.gray, 80), frame_color=color.new(color.gray, 80), frame_width=1)

if barstate.islast
    float sumWinEfficiency = 0.0
    float sumLossRunup     = 0.0
    int   winCount         = 0
    int   lossCount        = 0
    
    // Variables to calculate localized rolling drawdown
    float rollingPnl       = 0.0
    float rollingPeak      = 0.0
    float rollingMaxDD     = 0.0
    
    // Iterate chronologically (index 0 is oldest in window)
    if array.size(recentTrades) > 0
        for trade in recentTrades
            // 1. Aggregations for MFE
            if trade.isWinner
                sumWinEfficiency += trade.captureRatio
                winCount += 1
            else
                sumLossRunup += trade.maxRunup
                lossCount += 1
                
            // 2. Localized Equity Curve (Sequence Risk)
            rollingPnl += trade.realizedProfit
            if rollingPnl > rollingPeak
                rollingPeak := rollingPnl
            
            float currentDip = rollingPeak - rollingPnl
            if currentDip > rollingMaxDD
                rollingMaxDD := currentDip

    // Finalize Calculations
    avgEfficiency = winCount > 0 ? (sumWinEfficiency / winCount) : 0.0
    avgLossRunup  = lossCount > 0 ? (sumLossRunup / lossCount) : 0.0
    winRate       = array.size(recentTrades) > 0 ? (winCount / array.size(recentTrades)) * 100 : 0.0
    
    // Global Account Drawdown (includes floating open PnL)
    currentOpenDD_pct = maxAccountEquity > 0 ? ((maxAccountEquity - strategy.equity) / maxAccountEquity) * 100 : 0.0

    // Render Dashboard
    cellBg = color.new(#1e222d, 10)
    
    table.cell(dash, 0, 0, "Rolling Edge (" + str.tostring(array.size(recentTrades)) + " Trades)", text_color=color.white, bgcolor=color.new(color.gray, 80), text_size=size.small)
    table.merge_cells(dash, 0, 0, 1, 0)
    
    table.cell(dash, 0, 1, "Win Rate:", text_color=color.gray, bgcolor=cellBg, text_size=size.small, text_halign=text.align_left)
    table.cell(dash, 1, 1, str.tostring(winRate, "#.#") + "%", text_color=color.white, bgcolor=cellBg, text_size=size.small, text_halign=text.align_right)
    
    table.cell(dash, 0, 2, "Winner Capture Ratio:", text_color=color.gray, bgcolor=cellBg, text_size=size.small, text_halign=text.align_left)
    table.cell(dash, 1, 2, str.tostring(avgEfficiency, "#.#") + "%", text_color=color.teal, bgcolor=cellBg, text_size=size.small, text_halign=text.align_right)
    
    table.cell(dash, 0, 3, "Loser Avg Peak MFE:", text_color=color.gray, bgcolor=cellBg, text_size=size.small, text_halign=text.align_left)
    table.cell(dash, 1, 3, "$" + str.tostring(avgLossRunup, "#.##"), text_color=color.maroon, bgcolor=cellBg, text_size=size.small, text_halign=text.align_right)
    
    // Drawdown Rows
    table.cell(dash, 0, 4, "Rolling Window Max DD:", text_color=color.gray, bgcolor=cellBg, text_size=size.small, text_halign=text.align_left)
    table.cell(dash, 1, 4, "$" + str.tostring(rollingMaxDD, "#.##"), text_color=color.red, bgcolor=cellBg, text_size=size.small, text_halign=text.align_right)
    
    table.cell(dash, 0, 5, "Current Account DD (%):", text_color=color.gray, bgcolor=cellBg, text_size=size.small, text_halign=text.align_left)
    table.cell(dash, 1, 5, str.tostring(currentOpenDD_pct, "#.##") + "%", text_color=color.red, bgcolor=cellBg, text_size=size.small, text_halign=text.align_right)
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Using MFE to Examine Your Targets and Exits

Post by PTScalper »

The Architectural Advantage of this Approach:

The Array Loop (Sequence Risk): Because array.push adds new trades to the end of the array, the for trade in recentTrades loop inherently processes the trades in chronological order. This allows us to rebuild a localized equity curve inside the loop (rollingPnl) and extract the true maximum dollar drawdown experienced specifically within your last 20 trades.

Global Tracking (Open Equity): The currentOpenDD_pct tracks tick-by-tick open equity independently of the closed-trade array. This reveals if you are currently holding onto a massively underwater position (MAE failure) even if your last 20 closed trades look mathematically sound.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 4805
Joined: Mon Jul 20, 2026 1:28 pm

Re: Using MFE to Examine Your Targets and Exits

Post by PTScalper »

This upgrade transforms the script into a closed-loop system: the analytics engine now directly feeds data back into the execution engine.

To achieve this, we have to make two architectural changes:

Convert Dollar MFE to Price Distance: Dollar MFE fluctuates with position sizing. We must normalize the MFE into raw points/pips so the execution block has an objective price distance to use as a target.

Dual-Exit Architecture: Pine Script handles scale-outs via multiple strategy.exit commands bound to the same entry ID. We allocate 50% to the dynamic limit target, and 100% to a trailing stop (which will just grab whatever is left over after the first 50% fills).
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply