TradingAgents/frontend/src/components/ReturnExplainModal.tsx

222 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { X, CheckCircle, XCircle, Calculator } from 'lucide-react';
import { createPortal } from 'react-dom';
import type { ReturnBreakdown } from '../types';
interface ReturnExplainModalProps {
isOpen: boolean;
onClose: () => void;
breakdown: ReturnBreakdown | null;
date: string;
}
export default function ReturnExplainModal({ isOpen, onClose, breakdown, date }: ReturnExplainModalProps) {
if (!isOpen || !breakdown) return null;
const formattedDate = new Date(date).toLocaleDateString('en-IN', {
weekday: 'short',
month: 'short',
day: 'numeric',
year: 'numeric',
});
return createPortal(
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
{/* Backdrop */}
<div
className="absolute inset-0 bg-black/50 backdrop-blur-sm"
onClick={onClose}
/>
{/* Modal */}
<div className="relative bg-white dark:bg-slate-800 rounded-xl shadow-xl max-w-[95vw] sm:max-w-lg w-full max-h-[90vh] overflow-y-auto">
{/* Header */}
<div className="sticky top-0 flex items-center justify-between p-4 border-b border-gray-100 dark:border-slate-700 bg-white dark:bg-slate-800">
<div className="flex items-center gap-2">
<Calculator className="w-5 h-5 text-nifty-600 dark:text-nifty-400" />
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
Return Calculation
</h2>
</div>
<button
onClick={onClose}
className="p-1.5 rounded-lg hover:bg-gray-100 dark:hover:bg-slate-700 transition-colors"
>
<X className="w-5 h-5 text-gray-500 dark:text-gray-400" />
</button>
</div>
{/* Content */}
<div className="p-4 space-y-5">
{/* Date & Result */}
<div className="p-4 rounded-lg bg-nifty-50 dark:bg-nifty-900/20 border border-nifty-100 dark:border-nifty-800">
<div className="text-sm text-gray-600 dark:text-gray-400 mb-1">{formattedDate}</div>
<div className={`text-3xl font-bold ${breakdown.weightedReturn >= 0 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'}`}>
{breakdown.weightedReturn >= 0 ? '+' : ''}{breakdown.weightedReturn.toFixed(1)}%
</div>
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1">
Weighted Average Return
</p>
</div>
{/* Method Explanation */}
<div>
<h3 className="font-semibold text-gray-900 dark:text-gray-100 mb-2">Calculation Method</h3>
<div className="p-3 rounded-lg bg-gray-50 dark:bg-slate-700/50 text-sm space-y-2">
<p className="text-gray-700 dark:text-gray-300">
<strong>1. Correct Predictions</strong> Contribute <span className="text-green-600 dark:text-green-400">positively</span>
</p>
<ul className="text-xs text-gray-600 dark:text-gray-400 ml-4 space-y-1">
<li> BUY that went up add the gain</li>
<li> SELL that went down add the avoided loss</li>
<li> HOLD that stayed flat small positive</li>
</ul>
<p className="text-gray-700 dark:text-gray-300 mt-2">
<strong>2. Incorrect Predictions</strong> Contribute <span className="text-red-600 dark:text-red-400">negatively</span>
</p>
<ul className="text-xs text-gray-600 dark:text-gray-400 ml-4 space-y-1">
<li> BUY that went down subtract the loss</li>
<li> SELL that went up subtract missed gain</li>
<li> HOLD that moved subtract missed opportunity</li>
</ul>
<p className="text-gray-700 dark:text-gray-300 mt-2">
<strong>3. Weighted Average</strong>
</p>
<div className="p-2 bg-white dark:bg-slate-800 rounded border border-gray-200 dark:border-slate-600 font-mono text-xs">
(Correct Avg × Correct Weight) + (Incorrect Avg × Incorrect Weight)
</div>
</div>
</div>
{/* Correct Predictions Breakdown */}
<div>
<div className="flex items-center gap-2 mb-2">
<CheckCircle className="w-4 h-4 text-green-600 dark:text-green-400" />
<h3 className="font-semibold text-green-800 dark:text-green-300">Correct Predictions</h3>
<span className="text-xs text-gray-500 dark:text-gray-400">
({breakdown.correctPredictions.count} stocks)
</span>
</div>
<div className="p-3 rounded-lg bg-green-50 dark:bg-green-900/20 border border-green-100 dark:border-green-800">
<div className="grid grid-cols-2 gap-3 mb-3">
<div>
<div className="text-xs text-gray-500 dark:text-gray-400">Average Return</div>
<div className="text-lg font-bold text-green-600 dark:text-green-400">
+{breakdown.correctPredictions.avgReturn.toFixed(1)}%
</div>
</div>
<div>
<div className="text-xs text-gray-500 dark:text-gray-400">Weight</div>
<div className="text-lg font-bold text-green-600 dark:text-green-400">
{breakdown.correctPredictions.count}/{breakdown.correctPredictions.count + breakdown.incorrectPredictions.count}
</div>
</div>
</div>
{breakdown.correctPredictions.stocks.length > 0 && (
<div className="border-t border-green-200 dark:border-green-700 pt-2">
<div className="text-xs text-gray-500 dark:text-gray-400 mb-1">Top performers:</div>
<div className="space-y-1">
{breakdown.correctPredictions.stocks.map((stock: { symbol: string; decision: string; return1d: number }) => (
<div key={stock.symbol} className="flex items-center justify-between text-xs">
<span className="font-medium text-gray-700 dark:text-gray-300">
{stock.symbol}
<span className={`ml-1 ${
stock.decision === 'BUY' ? 'text-green-600 dark:text-green-400' :
stock.decision === 'SELL' ? 'text-red-600 dark:text-red-400' :
'text-amber-600 dark:text-amber-400'
}`}>
({stock.decision})
</span>
</span>
<span className="text-green-600 dark:text-green-400">+{stock.return1d.toFixed(1)}%</span>
</div>
))}
</div>
</div>
)}
</div>
</div>
{/* Incorrect Predictions Breakdown */}
<div>
<div className="flex items-center gap-2 mb-2">
<XCircle className="w-4 h-4 text-red-600 dark:text-red-400" />
<h3 className="font-semibold text-red-800 dark:text-red-300">Incorrect Predictions</h3>
<span className="text-xs text-gray-500 dark:text-gray-400">
({breakdown.incorrectPredictions.count} stocks)
</span>
</div>
<div className="p-3 rounded-lg bg-red-50 dark:bg-red-900/20 border border-red-100 dark:border-red-800">
<div className="grid grid-cols-2 gap-3 mb-3">
<div>
<div className="text-xs text-gray-500 dark:text-gray-400">Average Return</div>
<div className="text-lg font-bold text-red-600 dark:text-red-400">
{breakdown.incorrectPredictions.avgReturn.toFixed(1)}%
</div>
</div>
<div>
<div className="text-xs text-gray-500 dark:text-gray-400">Weight</div>
<div className="text-lg font-bold text-red-600 dark:text-red-400">
{breakdown.incorrectPredictions.count}/{breakdown.correctPredictions.count + breakdown.incorrectPredictions.count}
</div>
</div>
</div>
{breakdown.incorrectPredictions.stocks.length > 0 && (
<div className="border-t border-red-200 dark:border-red-700 pt-2">
<div className="text-xs text-gray-500 dark:text-gray-400 mb-1">Worst performers:</div>
<div className="space-y-1">
{breakdown.incorrectPredictions.stocks.map((stock: { symbol: string; decision: string; return1d: number }) => (
<div key={stock.symbol} className="flex items-center justify-between text-xs">
<span className="font-medium text-gray-700 dark:text-gray-300">
{stock.symbol}
<span className={`ml-1 ${
stock.decision === 'BUY' ? 'text-green-600 dark:text-green-400' :
stock.decision === 'SELL' ? 'text-red-600 dark:text-red-400' :
'text-amber-600 dark:text-amber-400'
}`}>
({stock.decision})
</span>
</span>
<span className="text-red-600 dark:text-red-400">{stock.return1d.toFixed(1)}%</span>
</div>
))}
</div>
</div>
)}
</div>
</div>
{/* Final Calculation */}
<div>
<h3 className="font-semibold text-gray-900 dark:text-gray-100 mb-2">Final Calculation</h3>
<div className="p-3 rounded-lg bg-gray-50 dark:bg-slate-700/50">
<div className="font-mono text-xs text-gray-600 dark:text-gray-400 break-all">
{breakdown.formula}
</div>
</div>
</div>
{/* Disclaimer */}
<div className="p-3 rounded-lg bg-gray-100 dark:bg-slate-700/30 border border-gray-200 dark:border-slate-600">
<p className="text-xs text-gray-500 dark:text-gray-400">
<strong>Note:</strong> This weighted return represents the theoretical gain/loss
if you followed all predictions for the day. Actual results may vary based on
execution timing, transaction costs, and market conditions.
</p>
</div>
</div>
{/* Footer */}
<div className="sticky bottom-0 p-4 border-t border-gray-100 dark:border-slate-700 bg-white dark:bg-slate-800">
<button
onClick={onClose}
className="w-full btn-primary"
>
Got it
</button>
</div>
</div>
</div>,
document.body
);
}