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:
mindesbunister
2025-10-29 20:34:03 +01:00
parent d4d2883af6
commit 65e6a8efed
7 changed files with 136 additions and 0 deletions

View File

@@ -241,6 +241,11 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
priceCheckCount: 0,
lastPrice: entryPrice,
lastUpdateTime: Date.now(),
maxFavorableExcursion: 0,
maxAdverseExcursion: 0,
maxFavorablePrice: entryPrice,
maxAdversePrice: entryPrice,
lastDbMetricsUpdate: Date.now(),
}
// CRITICAL FIX: Place on-chain TP/SL orders BEFORE adding to Position Manager

View File

@@ -182,6 +182,11 @@ export async function POST(request: NextRequest): Promise<NextResponse<TestTrade
priceCheckCount: 0,
lastPrice: entryPrice,
lastUpdateTime: Date.now(),
maxFavorableExcursion: 0,
maxAdverseExcursion: 0,
maxFavorablePrice: entryPrice,
maxAdversePrice: entryPrice,
lastDbMetricsUpdate: Date.now(),
}
// Add to position manager

View File

@@ -180,6 +180,11 @@ export async function POST(request: NextRequest): Promise<NextResponse<TestTrade
priceCheckCount: 0,
lastPrice: entryPrice,
lastUpdateTime: Date.now(),
maxFavorableExcursion: 0,
maxAdverseExcursion: 0,
maxFavorablePrice: entryPrice,
maxAdversePrice: entryPrice,
lastDbMetricsUpdate: Date.now(),
}
// Add to position manager for monitoring

View File

@@ -56,6 +56,10 @@ export interface UpdateTradeStateParams {
unrealizedPnL: number
peakPnL: number
lastPrice: number
maxFavorableExcursion?: number
maxAdverseExcursion?: number
maxFavorablePrice?: number
maxAdversePrice?: number
}
export interface UpdateTradeExitParams {
@@ -184,6 +188,10 @@ export async function updateTradeState(params: UpdateTradeStateParams) {
unrealizedPnL: params.unrealizedPnL,
peakPnL: params.peakPnL,
lastPrice: params.lastPrice,
maxFavorableExcursion: params.maxFavorableExcursion,
maxAdverseExcursion: params.maxAdverseExcursion,
maxFavorablePrice: params.maxFavorablePrice,
maxAdversePrice: params.maxAdversePrice,
lastUpdate: new Date().toISOString(),
}
}

View File

@@ -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

View File

@@ -0,0 +1,24 @@
-- AlterTable
ALTER TABLE "Trade" ADD COLUMN "adxAtEntry" DOUBLE PRECISION,
ADD COLUMN "atrAtEntry" DOUBLE PRECISION,
ADD COLUMN "basisAtEntry" DOUBLE PRECISION,
ADD COLUMN "entrySlippagePct" DOUBLE PRECISION,
ADD COLUMN "exitSlippagePct" DOUBLE PRECISION,
ADD COLUMN "expectedEntryPrice" DOUBLE PRECISION,
ADD COLUMN "expectedExitPrice" DOUBLE PRECISION,
ADD COLUMN "fundingRateAtEntry" DOUBLE PRECISION,
ADD COLUMN "hardSlFilled" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "maxAdverseExcursion" DOUBLE PRECISION,
ADD COLUMN "maxAdversePrice" DOUBLE PRECISION,
ADD COLUMN "maxFavorableExcursion" DOUBLE PRECISION,
ADD COLUMN "maxFavorablePrice" DOUBLE PRECISION,
ADD COLUMN "slFillPrice" DOUBLE PRECISION,
ADD COLUMN "softSlFilled" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "timeToSl" INTEGER,
ADD COLUMN "timeToTp1" INTEGER,
ADD COLUMN "timeToTp2" INTEGER,
ADD COLUMN "tp1FillPrice" DOUBLE PRECISION,
ADD COLUMN "tp1Filled" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "tp2FillPrice" DOUBLE PRECISION,
ADD COLUMN "tp2Filled" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "volumeAtEntry" DOUBLE PRECISION;

View File

@@ -49,6 +49,39 @@ model Trade {
maxDrawdown Float? // Peak to valley during trade
maxGain Float? // Peak gain reached
// MAE/MFE Analysis (Maximum Adverse/Favorable Excursion)
maxFavorableExcursion Float? // Best profit % reached during trade
maxAdverseExcursion Float? // Worst drawdown % during trade
maxFavorablePrice Float? // Best price hit (direction-aware)
maxAdversePrice Float? // Worst price hit (direction-aware)
// Exit details - which levels actually filled
tp1Filled Boolean @default(false)
tp2Filled Boolean @default(false)
softSlFilled Boolean @default(false)
hardSlFilled Boolean @default(false)
tp1FillPrice Float?
tp2FillPrice Float?
slFillPrice Float?
// Timing metrics
timeToTp1 Int? // Seconds from entry to TP1 fill
timeToTp2 Int? // Seconds from entry to TP2 fill
timeToSl Int? // Seconds from entry to SL hit
// Market context at entry
atrAtEntry Float? // ATR% when trade opened
adxAtEntry Float? // ADX trend strength (0-50)
volumeAtEntry Float? // Volume relative to MA
fundingRateAtEntry Float? // Perp funding rate at entry
basisAtEntry Float? // Perp-spot basis at entry
// Slippage tracking
expectedEntryPrice Float? // Target entry from signal
entrySlippagePct Float? // Actual slippage %
expectedExitPrice Float? // Which TP/SL should have hit
exitSlippagePct Float? // Exit slippage %
// Order signatures
entryOrderTx String
tp1OrderTx String?