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
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user