← Back
'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); });

📜 Git History

e78fc62feat: auto-trading for Gamma Play + fix fake 100% WR bug10 weeks ago
Show last diff
Loading...