Add persistent learning data and PnL display

- Created persistent learning status API with trading statistics
- Added comprehensive PnL and win rate display to AI Learning panel
- Implemented trading stats tracking with win/loss ratios
- Added persistent data storage for historical trading performance
- Enhanced learning panel with real-time trading metrics
- Fixed learning data visibility when bot is not running
- Added sample trading data for demonstration
This commit is contained in:
mindesbunister
2025-07-27 13:57:52 +02:00
parent 7752463b9f
commit f623e46c26
4 changed files with 460 additions and 234 deletions

View File

@@ -1,116 +1,163 @@
// API route for persistent learning data that works regardless of automation status
import { NextResponse } from 'next/server';
import { PrismaClient } from '@prisma/client';
import fs from 'fs/promises';
import path from 'path';
const prisma = new PrismaClient();
const PERSISTENT_DATA_FILE = path.join(process.cwd(), 'data', 'learning-persistent.json');
// Default persistent data structure
const defaultPersistentData = {
totalTrades: 0,
winningTrades: 0,
losingTrades: 0,
totalPnL: 0,
winRate: 0,
avgWinAmount: 0,
avgLossAmount: 0,
bestTrade: 0,
worstTrade: 0,
learningDecisions: 0,
aiEnhancements: 0,
riskThresholds: {
emergency: 1,
risk: 2,
mediumRisk: 5
},
lastUpdated: new Date().toISOString(),
systemStatus: 'learning',
dataCollected: true
};
async function ensureDataDirectory() {
const dataDir = path.join(process.cwd(), 'data');
try {
await fs.access(dataDir);
} catch {
await fs.mkdir(dataDir, { recursive: true });
}
}
async function loadPersistentData() {
try {
await ensureDataDirectory();
const data = await fs.readFile(PERSISTENT_DATA_FILE, 'utf8');
return JSON.parse(data);
} catch (error) {
// File doesn't exist or is invalid, return default data
return defaultPersistentData;
}
}
async function savePersistentData(data) {
try {
await ensureDataDirectory();
await fs.writeFile(PERSISTENT_DATA_FILE, JSON.stringify(data, null, 2));
return true;
} catch (error) {
console.error('Error saving persistent data:', error);
return false;
}
}
export async function GET() {
try {
// Get persistent learning statistics from database using raw SQL
const [
totalDecisions,
recentDecisions,
totalTrades,
successfulTrades,
recentTrades
] = await Promise.all([
// Total AI decisions count
prisma.$queryRaw`SELECT COUNT(*) as count FROM ai_learning_data`,
// Recent decisions (last 24 hours)
prisma.$queryRaw`SELECT COUNT(*) as count FROM ai_learning_data WHERE createdAt >= datetime('now', '-24 hours')`,
// Total trades
prisma.$queryRaw`SELECT COUNT(*) as count FROM trades`,
// Successful trades (profit > 0)
prisma.$queryRaw`SELECT COUNT(*) as count FROM trades WHERE profit > 0`,
// Recent trades with PnL data
prisma.$queryRaw`
SELECT id, symbol, profit, side, confidence, marketSentiment, createdAt, closedAt, status
FROM trades
WHERE profit IS NOT NULL AND status = 'COMPLETED'
ORDER BY createdAt DESC
LIMIT 10
`
]);
// Extract counts (BigInt to Number)
const totalDecisionsCount = Number(totalDecisions[0]?.count || 0);
const recentDecisionsCount = Number(recentDecisions[0]?.count || 0);
const totalTradesCount = Number(totalTrades[0]?.count || 0);
const successfulTradesCount = Number(successfulTrades[0]?.count || 0);
// Calculate metrics
const successRate = totalTradesCount > 0 ? (successfulTradesCount / totalTradesCount) * 100 : 0;
const totalPnl = recentTrades.reduce((sum, trade) => sum + (Number(trade.profit) || 0), 0);
const avgPnl = recentTrades.length > 0 ? totalPnl / recentTrades.length : 0;
const persistentData = await loadPersistentData();
// Get wins and losses
const wins = recentTrades.filter(trade => Number(trade.profit) > 0).length;
const losses = recentTrades.filter(trade => Number(trade.profit) < 0).length;
const persistentData = {
success: true,
statistics: {
totalDecisions: totalDecisionsCount,
recentDecisions: recentDecisionsCount,
totalTrades: totalTradesCount,
successfulTrades: successfulTradesCount,
successRate: Math.round(successRate * 100) / 100,
totalPnl: Math.round(totalPnl * 100) / 100,
avgPnl: Math.round(avgPnl * 100) / 100,
wins,
losses,
winRate: wins + losses > 0 ? Math.round((wins / (wins + losses)) * 100 * 100) / 100 : 0
},
recentTrades: recentTrades.map(trade => ({
id: trade.id,
symbol: trade.symbol,
pnl: Number(trade.profit),
result: Number(trade.profit) > 0 ? 'WIN' : 'LOSS',
confidence: trade.confidence,
side: trade.side,
sentiment: trade.marketSentiment,
date: trade.createdAt,
closedAt: trade.closedAt
})),
systemHealth: {
dataAvailability: totalDecisionsCount > 0 ? 'Good' : 'Limited',
lastActivity: recentTrades.length > 0 ? recentTrades[0].createdAt : null,
databaseConnected: true,
activeDataSources: {
aiDecisions: totalDecisionsCount,
completedTrades: totalTradesCount,
recentActivity: recentDecisionsCount
// Get current automation status if available
let currentStatus = null;
try {
const { getAutomationInstance } = await import('../../../../lib/automation-singleton.js');
const automation = await getAutomationInstance();
if (automation) {
currentStatus = automation.getStatus();
// If automation has learning status, get it too
if (typeof automation.getLearningStatus === 'function') {
const learningStatus = await automation.getLearningStatus();
if (learningStatus && learningStatus.report) {
// Update some data from current learning status
persistentData.lastUpdated = new Date().toISOString();
persistentData.systemStatus = learningStatus.enabled ? 'active' : 'standby';
}
}
}
};
} catch (error) {
console.warn('Could not get current automation status:', error.message);
}
return NextResponse.json(persistentData);
return NextResponse.json({
success: true,
persistentData: {
...persistentData,
isLive: currentStatus?.isActive || false,
currentRunTime: currentStatus?.startTime || null,
enhancedSummary: {
totalDecisions: persistentData.learningDecisions,
successRate: persistentData.winRate,
systemConfidence: persistentData.winRate > 60 ? 0.8 : persistentData.winRate > 40 ? 0.6 : 0.3,
isActive: persistentData.systemStatus === 'active',
totalTrades: persistentData.totalTrades,
totalPnL: persistentData.totalPnL
},
tradingStats: {
totalTrades: persistentData.totalTrades,
winningTrades: persistentData.winningTrades,
losingTrades: persistentData.losingTrades,
winRate: persistentData.winRate,
totalPnL: persistentData.totalPnL,
avgWinAmount: persistentData.avgWinAmount,
avgLossAmount: persistentData.avgLossAmount,
bestTrade: persistentData.bestTrade,
worstTrade: persistentData.worstTrade
},
learningMetrics: {
totalDecisions: persistentData.learningDecisions,
aiEnhancements: persistentData.aiEnhancements,
riskThresholds: persistentData.riskThresholds,
dataQuality: persistentData.totalTrades > 10 ? 'Good' : persistentData.totalTrades > 5 ? 'Fair' : 'Limited'
}
}
});
} catch (error) {
console.error('Error fetching persistent learning data:', error);
console.error('Error in persistent status API:', error);
return NextResponse.json({
success: false,
error: error.message,
statistics: {
totalDecisions: 0,
totalTrades: 0,
successRate: 0,
totalPnl: 0,
wins: 0,
losses: 0,
winRate: 0
},
systemHealth: {
dataAvailability: 'Error',
databaseConnected: false
}
persistentData: defaultPersistentData
}, { status: 500 });
} finally {
await prisma.$disconnect();
}
}
export async function POST(request) {
try {
const updateData = await request.json();
const currentData = await loadPersistentData();
// Update the persistent data
const updatedData = {
...currentData,
...updateData,
lastUpdated: new Date().toISOString()
};
// Recalculate derived metrics
if (updatedData.totalTrades > 0) {
updatedData.winRate = (updatedData.winningTrades / updatedData.totalTrades) * 100;
}
const saved = await savePersistentData(updatedData);
return NextResponse.json({
success: saved,
message: saved ? 'Persistent data updated' : 'Failed to save data',
data: updatedData
});
} catch (error) {
console.error('Error updating persistent data:', error);
return NextResponse.json({
success: false,
error: error.message
}, { status: 500 });
}
}