From 12d874ff93909edefa112296d2859ea3abb42a6e Mon Sep 17 00:00:00 2001 From: mindesbunister Date: Sun, 2 Nov 2025 23:00:21 +0100 Subject: [PATCH] feat: implement MAE/MFE tracking for trade optimization Added Maximum Favorable/Adverse Excursion tracking: - Track maxFavorableExcursion: best profit % reached during trade - Track maxAdverseExcursion: worst loss % reached during trade - Track maxFavorablePrice and maxAdversePrice - Update every price check (2s interval) - Save to database on trade exit for optimization analysis Benefits: - Identify if TP levels are too conservative (MFE consistently higher) - Determine if SL is too tight (MAE < SL but trade recovers) - Optimize runner size based on how often MFE >> TP2 - Data-driven exit strategy tuning after collecting 10-20 trades Display in monitoring logs: Shows MFE/MAE % every 20 seconds --- lib/trading/position-manager.ts | 50 ++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/lib/trading/position-manager.ts b/lib/trading/position-manager.ts index 5a39346..a654724 100644 --- a/lib/trading/position-manager.ts +++ b/lib/trading/position-manager.ts @@ -42,6 +42,12 @@ export interface ActiveTrade { peakPnL: number peakPrice: number // Track highest price reached (for trailing) + // MAE/MFE tracking + maxFavorableExcursion: number // Best profit % reached + maxAdverseExcursion: number // Worst loss % reached + maxFavorablePrice: number // Price at best profit + maxAdversePrice: number // Price at worst loss + // Monitoring priceCheckCount: number lastPrice: number @@ -110,6 +116,10 @@ export class PositionManager { unrealizedPnL: pmState?.unrealizedPnL ?? 0, peakPnL: pmState?.peakPnL ?? 0, peakPrice: pmState?.peakPrice ?? dbTrade.entryPrice, + maxFavorableExcursion: pmState?.maxFavorableExcursion ?? 0, + maxAdverseExcursion: pmState?.maxAdverseExcursion ?? 0, + maxFavorablePrice: pmState?.maxFavorablePrice ?? dbTrade.entryPrice, + maxAdversePrice: pmState?.maxAdversePrice ?? dbTrade.entryPrice, priceCheckCount: 0, lastPrice: pmState?.lastPrice ?? dbTrade.entryPrice, lastUpdateTime: Date.now(), @@ -367,7 +377,13 @@ export class PositionManager { realizedPnL: 0, exitOrderTx: 'UNKNOWN_CLOSURE', holdTimeSeconds: Math.floor((Date.now() - trade.entryTime) / 1000), - maxDrawdown: 0, + maxDrawdown: Math.abs(Math.min(0, trade.maxAdverseExcursion)), + maxGain: Math.max(0, trade.maxFavorableExcursion), + maxFavorableExcursion: trade.maxFavorableExcursion, + maxAdverseExcursion: trade.maxAdverseExcursion, + maxFavorablePrice: trade.maxFavorablePrice, + maxAdversePrice: trade.maxAdversePrice, + }) maxGain: trade.peakPnL, }) console.log(`💾 Old trade marked as closed (lost tracking)`) @@ -434,8 +450,12 @@ export class PositionManager { realizedPnL, exitOrderTx: 'ON_CHAIN_ORDER', holdTimeSeconds, - maxDrawdown: 0, - maxGain: trade.peakPnL, + maxDrawdown: Math.abs(Math.min(0, trade.maxAdverseExcursion)), + maxGain: Math.max(0, trade.maxFavorableExcursion), + maxFavorableExcursion: trade.maxFavorableExcursion, + maxAdverseExcursion: trade.maxAdverseExcursion, + maxFavorablePrice: trade.maxFavorablePrice, + maxAdversePrice: trade.maxAdversePrice, }) console.log(`💾 External closure recorded: ${exitReason} at $${currentPrice} | P&L: $${realizedPnL.toFixed(2)}`) } catch (dbError) { @@ -481,11 +501,21 @@ export class PositionManager { const accountPnL = profitPercent * trade.leverage trade.unrealizedPnL = (trade.currentSize * profitPercent) / 100 - // Track peak P&L + // Track peak P&L (MFE - Maximum Favorable Excursion) if (trade.unrealizedPnL > trade.peakPnL) { trade.peakPnL = trade.unrealizedPnL } + // Track MAE/MFE (account percentage, not USD) + if (accountPnL > trade.maxFavorableExcursion) { + trade.maxFavorableExcursion = accountPnL + trade.maxFavorablePrice = currentPrice + } + if (accountPnL < trade.maxAdverseExcursion) { + trade.maxAdverseExcursion = accountPnL + trade.maxAdversePrice = currentPrice + } + // Track peak price for trailing stop if (trade.direction === 'long') { if (currentPrice > trade.peakPrice) { @@ -504,7 +534,9 @@ export class PositionManager { `Price: ${currentPrice.toFixed(4)} | ` + `P&L: ${profitPercent.toFixed(2)}% (${accountPnL.toFixed(1)}% acct) | ` + `Unrealized: $${trade.unrealizedPnL.toFixed(2)} | ` + - `Peak: $${trade.peakPnL.toFixed(2)}` + `Peak: $${trade.peakPnL.toFixed(2)} | ` + + `MFE: ${trade.maxFavorableExcursion.toFixed(2)}% | ` + + `MAE: ${trade.maxAdverseExcursion.toFixed(2)}%` ) } @@ -711,8 +743,12 @@ export class PositionManager { realizedPnL: trade.realizedPnL, exitOrderTx: result.transactionSignature || 'MANUAL_CLOSE', holdTimeSeconds, - maxDrawdown: 0, // TODO: Track this - maxGain: trade.peakPnL, + maxDrawdown: Math.abs(Math.min(0, trade.maxAdverseExcursion)), + maxGain: Math.max(0, trade.maxFavorableExcursion), + maxFavorableExcursion: trade.maxFavorableExcursion, + maxAdverseExcursion: trade.maxAdverseExcursion, + maxFavorablePrice: trade.maxFavorablePrice, + maxAdversePrice: trade.maxAdversePrice, }) console.log('💾 Trade saved to database') } catch (dbError) {