โ ะะฐะทะฐะด'use strict';
const express = require('express');
const prisma = require('../services/db');
const { checkApiKey } = require('../middleware/auth');
const logger = require('../utils/logger');
const router = express.Router();
// โโโ GET /api/backtest/stats โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Aggregate stats: WR%, avg P&L per strategy, per underlying
router.get('/api/backtest/stats', checkApiKey, async (req, res) => {
try {
const days = Math.min(parseInt(req.query.days) || 30, 90);
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
const allLogs = await prisma.signalLog.findMany({
where: { createdAt: { gte: since } },
orderBy: { createdAt: 'desc' },
});
const completed = allLogs.filter(s => s.outcome !== null);
const pending = allLogs.filter(s => s.outcome === null);
// โโโ Per-Strategy stats โโโโโโโโโโโโโโโโโโโโโโโโ
const strategyMap = {};
for (const sig of completed) {
if (!strategyMap[sig.strategy]) {
strategyMap[sig.strategy] = { total: 0, wins: 0, losses: 0, pnl1h: [], pnl4h: [], pnl24h: [] };
}
const s = strategyMap[sig.strategy];
s.total++;
if (sig.outcome === 'WIN') s.wins++;
else if (sig.outcome === 'LOSS') s.losses++;
if (sig.pnlPct1h != null) s.pnl1h.push(sig.pnlPct1h);
if (sig.pnlPct4h != null) s.pnl4h.push(sig.pnlPct4h);
if (sig.pnlPct24h != null) s.pnl24h.push(sig.pnlPct24h);
}
const strategies = Object.entries(strategyMap).map(([name, s]) => ({
strategy: name,
total: s.total,
wins: s.wins,
losses: s.losses,
winRate: s.total > 0 ? Math.round((s.wins / s.total) * 100) : 0,
avgPnl1h: avg(s.pnl1h),
avgPnl4h: avg(s.pnl4h),
avgPnl24h: avg(s.pnl24h),
})).sort((a, b) => b.winRate - a.winRate);
// โโโ Per-Underlying stats โโโโโโโโโโโโโโโโโโโโโโ
const underlyingMap = {};
for (const sig of completed) {
if (!underlyingMap[sig.underlying]) {
underlyingMap[sig.underlying] = { total: 0, wins: 0, pnl24h: [] };
}
const u = underlyingMap[sig.underlying];
u.total++;
if (sig.outcome === 'WIN') u.wins++;
if (sig.pnlPct24h != null) u.pnl24h.push(sig.pnlPct24h);
}
const underlyings = Object.entries(underlyingMap).map(([name, u]) => ({
underlying: name,
total: u.total,
wins: u.wins,
winRate: u.total > 0 ? Math.round((u.wins / u.total) * 100) : 0,
avgPnl24h: avg(u.pnl24h),
})).sort((a, b) => b.total - a.total);
// โโโ Overall โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
const totalCompleted = completed.length;
const totalWins = completed.filter(s => s.outcome === 'WIN').length;
res.json({
success: true,
data: {
period: `${days}d`,
totalSignals: allLogs.length,
completed: totalCompleted,
pending: pending.length,
overallWinRate: totalCompleted > 0 ? Math.round((totalWins / totalCompleted) * 100) : 0,
avgPnl24h: avg(completed.filter(s => s.pnlPct24h != null).map(s => s.pnlPct24h)),
strategies,
underlyings,
},
});
} catch (err) {
logger.error(`GET /api/backtest/stats error: ${err.message}`);
res.status(500).json({ success: false, error: err.message });
}
});
// โโโ GET /api/backtest/signals โโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Raw signal log with outcomes (paginated)
router.get('/api/backtest/signals', checkApiKey, async (req, res) => {
try {
const limit = Math.min(parseInt(req.query.limit) || 50, 200);
const offset = parseInt(req.query.offset) || 0;
const strategy = req.query.strategy;
const underlying = req.query.underlying;
const outcome = req.query.outcome; // WIN, LOSS, PENDING
const where = {};
if (strategy) where.strategy = strategy;
if (underlying) where.underlying = underlying;
if (outcome === 'PENDING') where.outcome = null;
else if (outcome) where.outcome = outcome;
const [signals, total] = await Promise.all([
prisma.signalLog.findMany({
where,
orderBy: { createdAt: 'desc' },
take: limit,
skip: offset,
}),
prisma.signalLog.count({ where }),
]);
res.json({
success: true,
data: {
signals: signals.map(s => ({
...s,
parameters: safeParse(s.parameters),
})),
total,
limit,
offset,
},
});
} catch (err) {
logger.error(`GET /api/backtest/signals error: ${err.message}`);
res.status(500).json({ success: false, error: err.message });
}
});
function avg(arr) {
if (!arr || arr.length === 0) return 0;
return parseFloat((arr.reduce((a, b) => a + b, 0) / arr.length).toFixed(2));
}
function safeParse(str) {
try { return JSON.parse(str); } catch { return {}; }
}
module.exports = router;