โ ะะฐะทะฐะด'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); });