โ† ะะฐะทะฐะด
'use strict'; /** * Recalculate P&L and outcome for all completed NEUTRAL signals * (Straddle, Strangle, Weekend Trap, Weighted Straddle). * * Fixes the Math.abs() bug that made all neutral strategies show WIN. * Run once: node scripts/recalc-outcomes.js */ const { PrismaClient } = require('@prisma/client'); const prisma = new PrismaClient(); function clampPnl(pnl) { return parseFloat(Math.max(-100, Math.min(500, pnl)).toFixed(2)); } function safeParseJson(str) { try { return JSON.parse(str || '{}'); } catch { return {}; } } function calcFixedPnl(sig, spotNow) { if (!sig.spotAtSignal || sig.spotAtSignal === 0) return 0; const params = safeParseJson(sig.parameters); const premium = parseFloat(params.costUsd) || 0; const spotMove = spotNow - sig.spotAtSignal; // Infer direction let direction = sig.direction || 'NEUTRAL'; if (direction === 'NEUTRAL') { const st = (sig.strategy || '').toUpperCase(); if (st.includes('CALL') || st.includes('BULL')) direction = 'BULLISH'; else if (st.includes('PUT') || st.includes('BEAR')) direction = 'BEARISH'; } if (direction !== 'NEUTRAL') return null; // Only recalc NEUTRAL const callDelta = parseFloat(params.callDelta) || 0; const putDelta = parseFloat(params.putDelta) || 0; const callPrem = parseFloat(params.callPremium) || 0; const putPrem = parseFloat(params.putPremium) || 0; const ratioCall = parseFloat(params.ratioCall) || 1; const ratioPut = parseFloat(params.ratioPut) || 1; if (callDelta >= 0.03 && putDelta >= 0.03 && callPrem > 0 && putPrem > 0) { const callLegPnl = Math.max(callDelta * spotMove * ratioCall, -callPrem * ratioCall); const putLegPnl = Math.max(putDelta * (-spotMove) * ratioPut, -putPrem * ratioPut); const totalCost = callPrem * ratioCall + putPrem * ratioPut; return clampPnl(((callLegPnl + putLegPnl) / totalCost) * 100); } // No leg deltas (old signals) โ€” use fallback with ATM delta ~0.5 if (premium > 0) { const callLegPnl = Math.max(0.5 * spotMove, -premium / 2); const putLegPnl = Math.max(0.5 * (-spotMove), -premium / 2); return clampPnl(((callLegPnl + putLegPnl) / premium) * 100); } return 0; } async function main() { // Find all NEUTRAL-direction completed signals const neutralStrategies = ['Buy Straddle', 'Buy Strangle', 'Weekend Trap', 'Weighted Straddle']; const signals = await prisma.signalLog.findMany({ where: { strategy: { in: neutralStrategies }, spotAfter24h: { not: null }, }, }); console.log(`Found ${signals.length} completed NEUTRAL signals to recalculate`); let fixed = 0; let winsToLoss = 0; let winsToWin = 0; for (const sig of signals) { const updates = {}; // Recalc 1h if (sig.spotAfter1h) { updates.pnlPct1h = calcFixedPnl(sig, sig.spotAfter1h); if (updates.pnlPct1h === null) continue; } // Recalc 4h if (sig.spotAfter4h) { updates.pnlPct4h = calcFixedPnl(sig, sig.spotAfter4h); } // Recalc 24h + outcome if (sig.spotAfter24h) { updates.pnlPct24h = calcFixedPnl(sig, sig.spotAfter24h); updates.outcome = updates.pnlPct24h > 0 ? 'WIN' : updates.pnlPct24h < 0 ? 'LOSS' : 'NEUTRAL'; } const oldOutcome = sig.outcome; if (oldOutcome === 'WIN' && updates.outcome === 'LOSS') winsToLoss++; if (oldOutcome === 'WIN' && updates.outcome === 'WIN') winsToWin++; await prisma.signalLog.update({ where: { id: sig.id }, data: updates }); fixed++; } console.log(`\nRecalculated: ${fixed} signals`); console.log(`WIN โ†’ LOSS: ${winsToLoss}`); console.log(`WIN โ†’ WIN (stayed): ${winsToWin}`); console.log(`WIN โ†’ NEUTRAL: ${fixed - winsToLoss - winsToWin}`); // Show new stats const allCompleted = await prisma.signalLog.findMany({ where: { outcome: { not: null } }, }); const wins = allCompleted.filter(s => s.outcome === 'WIN').length; const losses = allCompleted.filter(s => s.outcome === 'LOSS').length; console.log(`\nNew overall: ${wins}W / ${losses}L โ€” WR ${((wins / (wins + losses)) * 100).toFixed(1)}%`); for (const strat of neutralStrategies) { const stratSignals = allCompleted.filter(s => s.strategy === strat); const sw = stratSignals.filter(s => s.outcome === 'WIN').length; const sl = stratSignals.filter(s => s.outcome === 'LOSS').length; const sn = stratSignals.filter(s => s.outcome === 'NEUTRAL').length; console.log(` ${strat}: ${sw}W / ${sl}L / ${sn}N โ€” WR ${stratSignals.length > 0 ? ((sw / Math.max(sw + sl, 1)) * 100).toFixed(0) : 0}%`); } await prisma.$disconnect(); } main().catch(err => { console.error(err); process.exit(1); });