Using MFE to Examine Your Targets and Exits
Re: Using MFE to Examine Your Targets and Exits
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.
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.
Re: Using MFE to Examine Your Targets and Exits
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.
Re: Using MFE to Examine Your Targets and Exits
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.
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.
Re: Using MFE to Examine Your Targets and Exits
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.
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.
Re: Using MFE to Examine Your Targets and Exits
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.
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.
Re: Using MFE to Examine Your Targets and Exits
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.
Re: Using MFE to Examine Your Targets and Exits
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.
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.
Re: Using MFE to Examine Your Targets and Exits
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.
Re: Using MFE to Examine Your Targets and Exits
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.
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.
Re: Using MFE to Examine Your Targets and Exits
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).
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.