Fix Prisma Decimal type handling in version comparison API

- Changed numeric fields from typed as number to 'any' in raw query results
- Properly convert Prisma Decimal/BigInt types to JavaScript numbers
- Fixes TypeError: e.totalPnL.toFixed is not a function
- All numeric values (totalPnL, avgPnL, avgADX, etc.) now converted with Number()

Issue: Prisma returns Decimal objects from aggregation queries which don't have
toFixed() method. Frontend expects plain numbers for .toFixed(2) formatting.
This commit is contained in:
mindesbunister
2025-11-07 13:11:04 +01:00
parent 711ff9aaf4
commit 6983f37a59

View File

@@ -36,18 +36,19 @@ export async function GET() {
version: string | null version: string | null
trades: bigint trades: bigint
wins: bigint wins: bigint
total_pnl: number total_pnl: any
avg_pnl: number avg_pnl: any
avg_quality_score: number | null avg_quality_score: any
avg_mfe: number | null avg_mfe: any
avg_mae: number | null avg_mae: any
}>>` }>>`
SELECT SELECT
COALESCE("signalQualityVersion", 'v1') as version, COALESCE("signalQualityVersion", 'v1') as version,
COUNT(*) as trades, COUNT(*) as trades,
SUM(CASE WHEN "realizedPnL" > 0 THEN 1 ELSE 0 END) as wins, SUM(CASE WHEN "realizedPnL" > 0 THEN 1 ELSE 0 END) as wins,
ROUND(SUM("realizedPnL")::numeric, 2) as total_pnl, SUM("realizedPnL") as total_pnl,
ROUND(AVG("realizedPnL")::numeric, 2) as avg_pnl, AVG("realizedPnL") as avg_pnl,
AVG("realizedPnL") as avg_pnl,
ROUND(AVG("signalQualityScore")::numeric, 1) as avg_quality_score, ROUND(AVG("signalQualityScore")::numeric, 1) as avg_quality_score,
ROUND(AVG("maxFavorableExcursion")::numeric, 2) as avg_mfe, ROUND(AVG("maxFavorableExcursion")::numeric, 2) as avg_mfe,
ROUND(AVG("maxAdverseExcursion")::numeric, 2) as avg_mae ROUND(AVG("maxAdverseExcursion")::numeric, 2) as avg_mae
@@ -63,10 +64,10 @@ export async function GET() {
const extremePositionStats = await prisma.$queryRaw<Array<{ const extremePositionStats = await prisma.$queryRaw<Array<{
version: string | null version: string | null
count: bigint count: bigint
avg_adx: number | null avg_adx: any
weak_adx_count: bigint weak_adx_count: bigint
wins: bigint wins: bigint
avg_pnl: number avg_pnl: any
}>>` }>>`
SELECT SELECT
COALESCE("signalQualityVersion", 'v1') as version, COALESCE("signalQualityVersion", 'v1') as version,
@@ -74,7 +75,7 @@ export async function GET() {
ROUND(AVG("adxAtEntry")::numeric, 1) as avg_adx, ROUND(AVG("adxAtEntry")::numeric, 1) as avg_adx,
COUNT(*) FILTER (WHERE "adxAtEntry" < 18) as weak_adx_count, COUNT(*) FILTER (WHERE "adxAtEntry" < 18) as weak_adx_count,
SUM(CASE WHEN "realizedPnL" > 0 THEN 1 ELSE 0 END) as wins, SUM(CASE WHEN "realizedPnL" > 0 THEN 1 ELSE 0 END) as wins,
ROUND(AVG("realizedPnL")::numeric, 2) as avg_pnl AVG("realizedPnL") as avg_pnl
FROM "Trade" FROM "Trade"
WHERE "exitReason" IS NOT NULL WHERE "exitReason" IS NOT NULL
AND "exitReason" NOT LIKE '%CLEANUP%' AND "exitReason" NOT LIKE '%CLEANUP%'
@@ -100,17 +101,17 @@ export async function GET() {
version: stat.version || 'v1', version: stat.version || 'v1',
tradeCount: trades, tradeCount: trades,
winRate: trades > 0 ? Math.round((wins / trades) * 100 * 10) / 10 : 0, winRate: trades > 0 ? Math.round((wins / trades) * 100 * 10) / 10 : 0,
totalPnL: stat.total_pnl, totalPnL: Number(stat.total_pnl) || 0,
avgPnL: stat.avg_pnl, avgPnL: Number(stat.avg_pnl) || 0,
avgQualityScore: stat.avg_quality_score, avgQualityScore: stat.avg_quality_score ? Number(stat.avg_quality_score) : null,
avgMFE: stat.avg_mfe, avgMFE: stat.avg_mfe ? Number(stat.avg_mfe) : null,
avgMAE: stat.avg_mae, avgMAE: stat.avg_mae ? Number(stat.avg_mae) : null,
extremePositions: { extremePositions: {
count: extremeCount, count: extremeCount,
avgADX: extremeStats?.avg_adx || null, avgADX: extremeStats?.avg_adx ? Number(extremeStats.avg_adx) : null,
weakADXCount: extremeStats ? Number(extremeStats.weak_adx_count) : 0, weakADXCount: extremeStats ? Number(extremeStats.weak_adx_count) : 0,
winRate: extremeCount > 0 ? Math.round((extremeWins / extremeCount) * 100 * 10) / 10 : 0, winRate: extremeCount > 0 ? Math.round((extremeWins / extremeCount) * 100 * 10) / 10 : 0,
avgPnL: extremeStats?.avg_pnl || 0, avgPnL: extremeStats?.avg_pnl ? Number(extremeStats.avg_pnl) : 0,
} }
} }
}) })