diff --git a/app/api/trading/execute/route.ts b/app/api/trading/execute/route.ts index 23d0633..c8de56e 100644 --- a/app/api/trading/execute/route.ts +++ b/app/api/trading/execute/route.ts @@ -241,6 +241,11 @@ export async function POST(request: NextRequest): Promise 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 { + 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 diff --git a/prisma/migrations/20251029192059_add_mae_mfe_and_market_context/migration.sql b/prisma/migrations/20251029192059_add_mae_mfe_and_market_context/migration.sql new file mode 100644 index 0000000..31645b6 --- /dev/null +++ b/prisma/migrations/20251029192059_add_mae_mfe_and_market_context/migration.sql @@ -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; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index fb5fe04..ef46ec2 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -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?