Phase 1: Add MAE/MFE tracking and analytics schema
- Added 20+ analytics fields to Trade model (MAE/MFE, fill tracking, timing, market context, slippage) - Implemented real-time MAE/MFE tracking in Position Manager (updates every 5s) - Enhanced database schema with comprehensive trade analytics - Updated all API endpoints to initialize MAE/MFE fields - Modified updateTradeState() to persist MAE/MFE in configSnapshot Database changes: - maxFavorableExcursion/maxAdverseExcursion track best/worst profit % - maxFavorablePrice/maxAdversePrice track exact price levels - Fill tracking: tp1Filled, tp2Filled, softSlFilled, hardSlFilled - Timing metrics: timeToTp1, timeToTp2, timeToSl - Market context: atrAtEntry, adxAtEntry, volumeAtEntry, fundingRateAtEntry, basisAtEntry - Slippage tracking: expectedEntryPrice, entrySlippagePct, expectedExitPrice, exitSlippagePct Position Manager changes: - Track MAE/MFE on every price check (2s interval) - Throttled database updates (5s interval) via updateTradeMetrics() - Persist MAE/MFE in trade state snapshots for recovery Next: Phase 2 (market context capture) or Phase 3 (analytics API)
This commit is contained in:
@@ -42,6 +42,13 @@ export interface ActiveTrade {
|
||||
peakPnL: number
|
||||
peakPrice: number // Track highest price reached (for trailing)
|
||||
|
||||
// MAE/MFE tracking (Maximum Adverse/Favorable Excursion)
|
||||
maxFavorableExcursion: number // Best profit % reached
|
||||
maxAdverseExcursion: number // Worst drawdown % reached
|
||||
maxFavorablePrice: number // Best price hit
|
||||
maxAdversePrice: number // Worst price hit
|
||||
lastDbMetricsUpdate: number // Last time we updated MAE/MFE in DB (throttle to 5s)
|
||||
|
||||
// Monitoring
|
||||
priceCheckCount: number
|
||||
lastPrice: number
|
||||
@@ -110,6 +117,11 @@ 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,
|
||||
lastDbMetricsUpdate: Date.now(),
|
||||
priceCheckCount: 0,
|
||||
lastPrice: pmState?.lastPrice ?? dbTrade.entryPrice,
|
||||
lastUpdateTime: Date.now(),
|
||||
@@ -394,6 +406,23 @@ export class PositionManager {
|
||||
const accountPnL = profitPercent * trade.leverage
|
||||
trade.unrealizedPnL = (trade.currentSize * profitPercent) / 100
|
||||
|
||||
// Track MAE/MFE (Maximum Adverse/Favorable Excursion)
|
||||
if (profitPercent > trade.maxFavorableExcursion) {
|
||||
trade.maxFavorableExcursion = profitPercent
|
||||
trade.maxFavorablePrice = currentPrice
|
||||
}
|
||||
|
||||
if (profitPercent < trade.maxAdverseExcursion) {
|
||||
trade.maxAdverseExcursion = profitPercent
|
||||
trade.maxAdversePrice = currentPrice
|
||||
}
|
||||
|
||||
// Update MAE/MFE in database (throttled to every 5 seconds to avoid spam)
|
||||
if (Date.now() - trade.lastDbMetricsUpdate > 5000) {
|
||||
await this.updateTradeMetrics(trade)
|
||||
trade.lastDbMetricsUpdate = Date.now()
|
||||
}
|
||||
|
||||
// Track peak P&L
|
||||
if (trade.unrealizedPnL > trade.peakPnL) {
|
||||
trade.peakPnL = trade.unrealizedPnL
|
||||
@@ -708,6 +737,10 @@ export class PositionManager {
|
||||
unrealizedPnL: trade.unrealizedPnL,
|
||||
peakPnL: trade.peakPnL,
|
||||
lastPrice: trade.lastPrice,
|
||||
maxFavorableExcursion: trade.maxFavorableExcursion,
|
||||
maxAdverseExcursion: trade.maxAdverseExcursion,
|
||||
maxFavorablePrice: trade.maxFavorablePrice,
|
||||
maxAdversePrice: trade.maxAdversePrice,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to save trade state:', error)
|
||||
@@ -733,6 +766,29 @@ export class PositionManager {
|
||||
symbols,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update MAE/MFE metrics in database (throttled)
|
||||
*/
|
||||
private async updateTradeMetrics(trade: ActiveTrade): Promise<void> {
|
||||
try {
|
||||
const { getPrismaClient } = await import('../database/trades')
|
||||
const prisma = getPrismaClient()
|
||||
|
||||
await prisma.trade.update({
|
||||
where: { id: trade.id },
|
||||
data: {
|
||||
maxFavorableExcursion: trade.maxFavorableExcursion,
|
||||
maxAdverseExcursion: trade.maxAdverseExcursion,
|
||||
maxFavorablePrice: trade.maxFavorablePrice,
|
||||
maxAdversePrice: trade.maxAdversePrice,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
// Silent failure to avoid disrupting monitoring loop
|
||||
console.error('Failed to update trade metrics:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton instance
|
||||
|
||||
Reference in New Issue
Block a user