import React from 'react' import { TrendingUp, TrendingDown, Minus, AlertTriangle, Gem } from 'lucide-react' import { getPredictionReliabilitySummary } from '../utils/predictionReliability' export interface MetalPredictionData { symbol: string name: string type: string signal: 'BUY' | 'SELL' | 'HOLD' signal_probability: number predicted_price: number predicted_change_pct: number sell_target: number stop_loss: number confidence: 'HIGH' | 'MEDIUM' | 'LOW' current_price: number training_accuracy: number prediction_horizon_days?: number oil_news_signal?: { summary?: string sentiment_score?: number event_score?: number confidence?: number } | null trend_conflict_guard?: { applied?: boolean reason?: string | null original_signal?: string final_signal?: string snapshot?: { bearish_count?: number bullish_count?: number return_5d?: number | null return_20d?: number | null rsi?: number | null } } | null oil_news_probability_adjustment?: number oil_news_change_adjustment_pct?: number disclaimer: string error?: string } interface Props { prediction: MetalPredictionData theme: 'gold' | 'silver' | 'oil' } const themeConfig = { gold: { border: 'border-yellow-500', bg: 'bg-yellow-500/10', text: 'text-yellow-400' }, silver: { border: 'border-slate-400', bg: 'bg-slate-400/10', text: 'text-slate-300' }, oil: { border: 'border-cyan-500', bg: 'bg-cyan-500/10', text: 'text-cyan-300' }, } const signalConfig = { BUY: { label: 'BUY', zh: '買入', bg: 'bg-green-600', Icon: TrendingUp }, SELL: { label: 'SELL', zh: '賣出', bg: 'bg-red-600', Icon: TrendingDown }, HOLD: { label: 'HOLD', zh: '觀望', bg: 'bg-yellow-600', Icon: Minus }, } export const MetalPredictionCard: React.FC = ({ prediction: p, theme }) => { const t = themeConfig[theme] const s = signalConfig[p.signal] || signalConfig.HOLD const up = p.predicted_change_pct >= 0 const prob = Math.round(p.signal_probability * 100) const reliability = getPredictionReliabilitySummary({ confidence: p.confidence, trainingAccuracy: p.training_accuracy, }) if (p.error) { return (

{p.name}

{p.error}

) } return (
{/* Header */}

{p.name}

ML 預測 · 未來 {p.prediction_horizon_days ?? 5} 交易日

{s.label} ({s.zh})
{/* Price grid */}
{[ { label: '現貨價', val: `$${p.current_price.toFixed(2)}`, cls: 'text-white' }, { label: '預測價(5日)', val: `$${p.predicted_price.toFixed(2)}`, cls: up ? 'text-green-400' : 'text-red-400', sub: `${up ? '+' : ''}${p.predicted_change_pct.toFixed(2)}%` }, { label: '目標價', val: `$${p.sell_target.toFixed(2)}`, cls: 'text-green-300' }, { label: '停損', val: `$${p.stop_loss.toFixed(2)}`, cls: 'text-red-300' }, ].map(({ label, val, cls, sub }) => (

{label}

{val}

{sub &&

{sub}

}
))}
{/* Probability bar */}
買入機率 {prob}%
= 60 ? 'bg-green-500' : prob <= 35 ? 'bg-red-500' : 'bg-yellow-500' }`} style={{ width: `${prob}%` }} />
{/* Reliability */}
整體可靠度
{reliability.levelText}
預測信心度
{reliability.confidenceText}
模型近期驗證準確率
{reliability.accuracyText}
{reliability.warning && (

{reliability.warning}

)} {p.trend_conflict_guard?.applied && (

強趨勢衝突 guard 已將 {p.trend_conflict_guard.original_signal} 調整為 {p.trend_conflict_guard.final_signal}。 空方條件 {p.trend_conflict_guard.snapshot?.bearish_count ?? 0} 項,5日報酬 {((p.trend_conflict_guard.snapshot?.return_5d ?? 0) * 100).toFixed(2)}%。

)} {theme === 'oil' && p.oil_news_signal && (
新聞 / 政治事件 overlay {((p.oil_news_probability_adjustment ?? 0) * 100).toFixed(2)}pp

{p.oil_news_signal.summary || '尚無新聞訊號'}

)} {/* Disclaimer */}

免責聲明: {p.disclaimer}

) }