/** * Ensemble Signal Card Component * Displays the combined ML signal from multiple strategies */ import React from 'react'; import { ArrowTrendingUpIcon, ArrowTrendingDownIcon, MinusIcon, ScaleIcon, BeakerIcon, ClockIcon, } from '@heroicons/react/24/solid'; interface StrategySignal { action: string; score: number; weight: number; } interface EnsembleSignal { symbol: string; timeframe: string; action: 'BUY' | 'SELL' | 'HOLD'; strength: 'strong' | 'moderate' | 'weak'; confidence: number; net_score: number; strategy_signals: { amd: StrategySignal; ict: StrategySignal; range: StrategySignal; tpsl: StrategySignal; }; entry?: number; stop_loss?: number; take_profit?: number; risk_reward?: number; reasoning: string[]; timestamp: string; } interface EnsembleSignalCardProps { signal: EnsembleSignal; onExecuteTrade?: (direction: 'buy' | 'sell', signal: EnsembleSignal) => void; className?: string; } export const EnsembleSignalCard: React.FC = ({ signal, onExecuteTrade, className = '', }) => { const getActionIcon = () => { switch (signal.action) { case 'BUY': return ; case 'SELL': return ; default: return ; } }; const getActionColor = () => { switch (signal.action) { case 'BUY': return 'bg-green-500/20 text-green-400 border-green-500/30'; case 'SELL': return 'bg-red-500/20 text-red-400 border-red-500/30'; default: return 'bg-gray-500/20 text-gray-400 border-gray-500/30'; } }; const getStrengthLabel = () => { switch (signal.strength) { case 'strong': return { text: 'Strong Signal', color: 'text-green-400' }; case 'moderate': return { text: 'Moderate Signal', color: 'text-yellow-400' }; default: return { text: 'Weak Signal', color: 'text-gray-400' }; } }; const strengthInfo = getStrengthLabel(); const formatScore = (score: number) => { return score >= 0 ? `+${score.toFixed(2)}` : score.toFixed(2); }; const getScoreBarColor = (action: string) => { if (action === 'BUY') return 'bg-green-500'; if (action === 'SELL') return 'bg-red-500'; return 'bg-gray-500'; }; const strategies = [ { key: 'amd', name: 'AMD', ...signal.strategy_signals.amd }, { key: 'ict', name: 'ICT/SMC', ...signal.strategy_signals.ict }, { key: 'range', name: 'Range', ...signal.strategy_signals.range }, { key: 'tpsl', name: 'TP/SL', ...signal.strategy_signals.tpsl }, ]; return (
{/* Header */}

Ensemble Signal

{signal.symbol} {signal.timeframe}
{/* Action Badge */}
{getActionIcon()}

{signal.action}

{strengthInfo.text}

{/* Net Score Meter */}
Net Score 0 ? 'text-green-400' : signal.net_score < 0 ? 'text-red-400' : 'text-gray-400' }`}> {formatScore(signal.net_score)}
= 0 ? 'left-1/2' : 'right-1/2' } ${signal.net_score >= 0 ? 'bg-green-500' : 'bg-red-500'} rounded-full transition-all`} style={{ width: `${Math.min(Math.abs(signal.net_score) * 50, 50)}%`, }} />
-1.0 (Strong Sell) +1.0 (Strong Buy)
{/* Confidence */}
Confidence {Math.round(signal.confidence * 100)}%
= 0.7 ? 'bg-green-500' : signal.confidence >= 0.5 ? 'bg-yellow-500' : 'bg-red-500' }`} style={{ width: `${signal.confidence * 100}%` }} />
{/* Strategy Breakdown */}

Strategy Contributions

{strategies.map((strategy) => (
{strategy.name}
{strategy.action}
{Math.round(strategy.weight * 100)}%
))}
{/* Trade Levels */} {signal.entry && (

Entry

{signal.entry.toFixed(5)}

{signal.stop_loss && (

Stop Loss

{signal.stop_loss.toFixed(5)}

)} {signal.take_profit && (

Take Profit

{signal.take_profit.toFixed(5)}

)}
)} {/* Risk/Reward */} {signal.risk_reward && (
Risk:Reward 1:{signal.risk_reward.toFixed(1)}
)} {/* Reasoning */} {signal.reasoning.length > 0 && (

Analysis Reasoning

    {signal.reasoning.slice(0, 4).map((reason, idx) => (
  • {reason}
  • ))}
)} {/* Timestamp */}
{new Date(signal.timestamp).toLocaleString()}
{/* Execute Button */} {onExecuteTrade && signal.action !== 'HOLD' && signal.confidence >= 0.5 && ( )}
); }; export default EnsembleSignalCard;