refactor(ui): remove legacy badge style debt

This commit is contained in:
Saravana Achu Mac 2026-05-06 18:26:12 -07:00
parent 45e389fd2a
commit 5f38adac62
4 changed files with 454 additions and 562 deletions

View File

@ -23,7 +23,7 @@ excluded_parts = {"test", "assets"}
patterns = [ patterns = [
("raw interactive controls outside approved primitives", "Raw Interactive Controls", re.compile(r"<(button|input|textarea|select)(\s|>)"), True), ("raw interactive controls outside approved primitives", "Raw Interactive Controls", re.compile(r"<(button|input|textarea|select)(\s|>)"), True),
("legacy global surface classes", "Legacy Global Surface Classes", re.compile(r"\b(surface-card|surface-muted|badge|input-shell)\b"), False), ("legacy global surface classes", "Legacy Global Surface Classes", re.compile(r"(?<![-\w])(surface-card|surface-muted|badge|input-shell)\b"), False),
("hardcoded color literals", "Hardcoded Color Literals", re.compile(r"(#[0-9a-f]{3,8}\b|rgba?\()", re.IGNORECASE), False), ("hardcoded color literals", "Hardcoded Color Literals", re.compile(r"(#[0-9a-f]{3,8}\b|rgba?\()", re.IGNORECASE), False),
("direct @bytelyst/ui imports outside product adapter", "Direct Common UI Imports Outside Adapter", re.compile(r"@bytelyst/ui"), True), ("direct @bytelyst/ui imports outside product adapter", "Direct Common UI Imports Outside Adapter", re.compile(r"@bytelyst/ui"), True),
] ]

View File

@ -591,24 +591,6 @@ body {
box-shadow: 0 0 10px var(--accent); box-shadow: 0 0 10px var(--accent);
} }
.badge {
padding: 4px 10px;
border-radius: 100px;
font-size: 0.8rem;
}
.badge.live {
background: #e67e22;
}
.badge.paper {
background: #3498db;
}
.badge.alerts {
background: #555;
}
.kill-switch { .kill-switch {
width: 100%; width: 100%;
padding: 16px; padding: 16px;

View File

@ -1,144 +1,127 @@
.symbol-card { .symbol-card {
background: linear-gradient(135deg, #1e1e2e 0%, #2a2a3e 100%); background: linear-gradient(135deg, #1e1e2e 0%, #2a2a3e 100%);
border-radius: 16px; border-radius: 16px;
padding: 24px; padding: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.1);
transition: transform 0.2s, box-shadow 0.2s; transition: transform 0.2s, box-shadow 0.2s;
} }
.symbol-card:hover { .symbol-card:hover {
transform: translateY(-4px); transform: translateY(-4px);
box-shadow: 0 12px 48px rgba(0, 0, 0, 0.4); box-shadow: 0 12px 48px rgba(0, 0, 0, 0.4);
} }
.symbol-header { .symbol-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 20px; margin-bottom: 20px;
} }
.title-area { .title-area {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 4px; gap: 4px;
} }
.session-info { .session-info {
display: flex; display: flex;
gap: 8px; gap: 8px;
align-items: center; align-items: center;
} }
.session-badge { .vol-label {
background: rgba(255, 255, 255, 0.1); font-size: 10px;
padding: 2px 8px; font-weight: 600;
border-radius: 4px; text-transform: uppercase;
font-size: 10px; }
font-weight: 700;
color: #888; .vol-label.high {
letter-spacing: 0.5px; color: #ff3366;
} }
.vol-label { .vol-label.med {
font-size: 10px; color: #ffaa00;
font-weight: 600; }
text-transform: uppercase;
} .vol-label.low {
color: #00ff88;
.vol-label.high { }
color: #ff3366;
} .symbol-header h2 {
color: #fff;
.vol-label.med { font-size: 24px;
color: #ffaa00; font-weight: 700;
} margin: 0;
letter-spacing: 0.5px;
.vol-label.low { }
color: #00ff88;
} .price-info {
text-align: right;
.symbol-header h2 { display: flex;
color: #fff; flex-direction: column;
font-size: 24px; gap: 2px;
font-weight: 700; }
margin: 0;
letter-spacing: 0.5px; .price {
} font-size: 26px;
font-weight: 700;
.price-info { color: #fff;
text-align: right; }
display: flex;
flex-direction: column; .change-grid {
gap: 2px; display: flex;
} flex-direction: column;
gap: 2px;
.price { }
font-size: 26px;
font-weight: 700; .change-item {
color: #fff; font-size: 11px;
} font-weight: 600;
}
.change-grid {
display: flex; .change-item.up {
flex-direction: column; color: #00ff88;
gap: 2px; }
}
.change-item.down {
.change-item { color: #ff3366;
font-size: 11px; }
font-weight: 600;
} .signal-status {
display: inline-flex;
.change-item.up { align-items: center;
color: #00ff88; gap: 8px;
} margin-bottom: 24px;
}
.change-item.down {
color: #ff3366; .signal-time {
} font-size: 11px;
opacity: 0.8;
.signal-badge { font-weight: 500;
display: inline-flex; }
align-items: center;
gap: 8px; .rules-section {
padding: 8px 16px; margin-bottom: 24px;
border-radius: 20px; }
font-weight: 700;
font-size: 14px; .rules-section h3,
color: #000; .indicators-section h3 {
margin-bottom: 24px; color: #888;
text-transform: uppercase; font-size: 12px;
letter-spacing: 1px; text-transform: uppercase;
} letter-spacing: 1.5px;
margin-bottom: 12px;
.signal-time { font-weight: 600;
font-size: 11px; }
opacity: 0.8;
font-weight: 500; .rules-grid {
} display: grid;
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
.rules-section { gap: 12px;
margin-bottom: 24px; }
}
.rules-section h3,
.indicators-section h3 {
color: #888;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 1.5px;
margin-bottom: 12px;
font-weight: 600;
}
.rules-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
gap: 12px;
}
.rule-container { .rule-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -194,212 +177,143 @@
font-size: 10px; font-size: 10px;
line-height: 1.35; line-height: 1.35;
} }
.rule-badge { .rule-status {
display: flex; justify-content: center;
align-items: center; width: 100%;
gap: 6px; text-transform: none;
padding: 8px 12px; }
border-radius: 8px;
font-size: 13px; .ai-details {
font-weight: 600; border-radius: 8px;
cursor: pointer; padding: 10px;
transition: all 0.2s; font-size: 11px;
border: 1px solid transparent; margin-top: -4px;
} border-left: 3px solid transparent;
background: rgba(255, 255, 255, 0.03);
.rule-badge.passed { }
background: rgba(0, 255, 136, 0.15);
color: #00ff88; .ai-details.passed {
border-color: rgba(0, 255, 136, 0.3); background: rgba(0, 255, 136, 0.05);
} border-color: #00ff88;
}
.rule-badge.failed {
background: rgba(255, 51, 102, 0.15); .ai-details.failed {
color: #ff3366; background: rgba(255, 51, 102, 0.05);
border-color: rgba(255, 51, 102, 0.3); border-color: #ff3366;
} }
.rule-badge.pending { .ai-confidence {
background: rgba(255, 255, 255, 0.05); font-weight: 700;
color: #888; margin-bottom: 4px;
border-color: rgba(255, 255, 255, 0.1); font-size: 12px;
opacity: 0.6; }
}
.ai-details.passed .ai-confidence {
.rule-badge.skipped { color: #00ff88;
background: rgba(255, 255, 255, 0.02); }
color: #555;
border-color: rgba(255, 255, 255, 0.05); .ai-details.failed .ai-confidence {
cursor: default; color: #ff3366;
} }
.rule-badge:hover { .ai-reasoning {
transform: scale(1.02); color: #aaa;
} line-height: 1.4;
font-style: italic;
.ai-details { }
border-radius: 8px;
padding: 10px; .indicators-grid {
font-size: 11px; display: grid;
margin-top: -4px; grid-template-columns: repeat(2, 1fr);
border-left: 3px solid transparent; gap: 12px;
background: rgba(255, 255, 255, 0.03); }
}
.indicator {
.ai-details.passed { display: flex;
background: rgba(0, 255, 136, 0.05); justify-content: space-between;
border-color: #00ff88; padding: 10px;
} background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
.ai-details.failed { border: 1px solid rgba(255, 255, 255, 0.08);
background: rgba(255, 51, 102, 0.05); }
border-color: #ff3366;
} .indicator-label {
color: #888;
.ai-confidence { font-size: 12px;
font-weight: 700; font-weight: 500;
margin-bottom: 4px; }
font-size: 12px;
} .indicator-value {
color: #fff;
.ai-details.passed .ai-confidence { font-size: 13px;
color: #00ff88; font-weight: 700;
} }
.ai-details.failed .ai-confidence { /* Active Position Panel */
color: #ff3366;
} .active-position-panel {
background: rgba(255, 255, 255, 0.05);
.ai-reasoning { border-radius: 8px;
color: #aaa; padding: 12px;
line-height: 1.4; margin: 15px 0;
font-style: italic; border-left: 4px solid #888;
} }
.rule-icon { .active-position-panel.buy {
font-size: 12px; border-left-color: #00ff88;
font-weight: 900; }
}
.active-position-panel.sell {
.rule-name { border-left-color: #ff3366;
font-size: 12px; }
}
.pos-header {
.indicators-grid { display: flex;
display: grid; justify-content: space-between;
grid-template-columns: repeat(2, 1fr); margin-bottom: 8px;
gap: 12px; font-size: 0.8rem;
} font-weight: bold;
}
.indicator {
display: flex; .pos-label {
justify-content: space-between; color: #aaa;
padding: 10px; }
background: rgba(255, 255, 255, 0.05);
border-radius: 8px; .buy .pos-label {
border: 1px solid rgba(255, 255, 255, 0.08); color: #00ff88;
} }
.indicator-label { .sell .pos-label {
color: #888; color: #ff3366;
font-size: 12px; }
font-weight: 500;
} .pos-grid {
display: grid;
.indicator-value { grid-template-columns: repeat(3, 1fr);
color: #fff; gap: 10px;
font-size: 13px; }
font-weight: 700;
} .pos-item {
display: flex;
/* Mode Badge */ flex-direction: column;
.mode-badge { }
padding: 2px 8px;
border-radius: 4px; .pos-item .label {
font-size: 0.75rem; font-size: 0.65rem;
font-weight: bold; color: #666;
text-transform: uppercase; text-transform: uppercase;
} }
.mode-badge.alerts { .pos-item .value {
background: #555; font-size: 0.85rem;
color: #fff; font-weight: 500;
} }
.mode-badge.paper { .pos-item.sl .value {
background: #3498db; color: #ff3366;
color: #fff; }
}
.pos-item.tp .value {
.mode-badge.live { color: #00ff88;
background: #e67e22;
color: #fff;
}
/* Active Position Panel */
.active-position-panel {
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
padding: 12px;
margin: 15px 0;
border-left: 4px solid #888;
}
.active-position-panel.buy {
border-left-color: #00ff88;
}
.active-position-panel.sell {
border-left-color: #ff3366;
}
.pos-header {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
font-size: 0.8rem;
font-weight: bold;
}
.pos-label {
color: #aaa;
}
.buy .pos-label {
color: #00ff88;
}
.sell .pos-label {
color: #ff3366;
}
.pos-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
.pos-item {
display: flex;
flex-direction: column;
}
.pos-item .label {
font-size: 0.65rem;
color: #666;
text-transform: uppercase;
}
.pos-item .value {
font-size: 0.85rem;
font-weight: 500;
}
.pos-item.sl .value {
color: #ff3366;
}
.pos-item.tp .value {
color: #00ff88;
} }

View File

@ -1,28 +1,29 @@
import './SymbolCard.css'; import './SymbolCard.css';
import { PriceChart } from './PriceChart'; import { PriceChart } from './PriceChart';
import { Badge, ProductStatusBadge } from './ui/Primitives';
interface SymbolCardProps {
symbol: string; interface SymbolCardProps {
data: { symbol: string;
price: number; data: {
change24h: number; price: number;
changeToday: number; change24h: number;
session: string; changeToday: number;
volatility: string; session: string;
signal: string; volatility: string;
signalTime?: number; signal: string;
tradingMode?: 'Paper' | 'Live' | 'Alerts'; signalTime?: number;
activePosition?: { tradingMode?: 'Paper' | 'Live' | 'Alerts';
side: 'BUY' | 'SELL'; activePosition?: {
entryPrice: number; side: 'BUY' | 'SELL';
size: number; entryPrice: number;
stopLoss: number; size: number;
takeProfit: number; stopLoss: number;
unrealizedPnl?: number; takeProfit: number;
unrealizedPnlPercent?: number; unrealizedPnl?: number;
marketValue?: number; unrealizedPnlPercent?: number;
} | null; marketValue?: number;
priceHistory: Array<{ timestamp: number; price: number }>; } | null;
priceHistory: Array<{ timestamp: number; price: number }>;
rules: { rules: {
[ruleName: string]: { [ruleName: string]: {
passed: boolean; passed: boolean;
@ -57,61 +58,58 @@ interface SymbolCardProps {
ema20_1h?: number; ema20_1h?: number;
ema20_15m?: number; ema20_15m?: number;
ema50_4h?: number; ema50_4h?: number;
ema200_4h?: number; ema200_4h?: number;
rsi_1h?: number; rsi_1h?: number;
rsi_15m?: number; rsi_15m?: number;
}; };
}; };
} }
const ruleDisplayNames: { [key: string]: string } = { const ruleDisplayNames: { [key: string]: string } = {
'TrendBiasRule': 'Trend', 'TrendBiasRule': 'Trend',
'SessionRule': 'Session', 'SessionRule': 'Session',
'ZoneRule': 'Zone', 'ZoneRule': 'Zone',
'MomentumRule': 'Momentum', 'MomentumRule': 'Momentum',
'EntryTriggerRule': 'Entry', 'EntryTriggerRule': 'Entry',
'AIAnalysisRule': 'AI', 'AIAnalysisRule': 'AI',
'RiskManagementRule': 'Risk' 'RiskManagementRule': 'Risk'
}; };
export const SymbolCard = ({ symbol, data }: SymbolCardProps) => { export const SymbolCard = ({ symbol, data }: SymbolCardProps) => {
const signalColor = data.signal === 'BUY' ? '#00ff88' : data.signal === 'SELL' ? '#ff3366' : '#888';
const profileSignalEntries = Object.entries(data.profileSignals || {}); const profileSignalEntries = Object.entries(data.profileSignals || {});
return ( return (
<div className="symbol-card"> <div className="symbol-card">
<div className="symbol-header"> <div className="symbol-header">
<div className="title-area"> <div className="title-area">
<h2>{symbol}</h2> <h2>{symbol}</h2>
<div className="session-info"> <div className="session-info">
<span className={`mode-badge ${data.tradingMode?.toLowerCase()}`}> <ProductStatusBadge status={data.tradingMode}>{data.tradingMode}</ProductStatusBadge>
{data.tradingMode} <Badge variant="neutral" size="sm">{data.session}</Badge>
</span> <span className={`vol-label ${data.volatility.toLowerCase()}`}>
<span className="session-badge">{data.session}</span> {data.volatility} Vol
<span className={`vol-label ${data.volatility.toLowerCase()}`}> </span>
{data.volatility} Vol </div>
</span> </div>
</div> <div className="price-info">
</div> <div className="price">${data.price.toLocaleString()}</div>
<div className="price-info"> <div className="change-grid">
<div className="price">${data.price.toLocaleString()}</div> <div className={`change-item ${data.changeToday >= 0 ? 'up' : 'down'}`}>
<div className="change-grid"> Today: {data.changeToday >= 0 ? '+' : ''}{data.changeToday.toFixed(2)}%
<div className={`change-item ${data.changeToday >= 0 ? 'up' : 'down'}`}> </div>
Today: {data.changeToday >= 0 ? '+' : ''}{data.changeToday.toFixed(2)}% <div className={`change-item ${data.change24h >= 0 ? 'up' : 'down'}`}>
</div> 24h: {data.change24h >= 0 ? '+' : ''}{data.change24h.toFixed(2)}%
<div className={`change-item ${data.change24h >= 0 ? 'up' : 'down'}`}> </div>
24h: {data.change24h >= 0 ? '+' : ''}{data.change24h.toFixed(2)}% </div>
</div> </div>
</div> </div>
</div>
</div> <div className="signal-status">
<ProductStatusBadge status={data.signal}>{data.signal || 'NONE'}</ProductStatusBadge>
<div className="signal-badge" style={{ backgroundColor: signalColor }}>
{data.signal || 'NONE'}
{data.signalTime && ( {data.signalTime && (
<span className="signal-time"> <span className="signal-time">
{Math.floor((Date.now() - data.signalTime) / 60000)}m ago {Math.floor((Date.now() - data.signalTime) / 60000)}m ago
</span> </span>
)} )}
</div> </div>
@ -120,13 +118,6 @@ export const SymbolCard = ({ symbol, data }: SymbolCardProps) => {
<h3>Profile Signals</h3> <h3>Profile Signals</h3>
<div className="rules-grid"> <div className="rules-grid">
{profileSignalEntries.map(([profileId, profileSignal]) => { {profileSignalEntries.map(([profileId, profileSignal]) => {
const badgeClass = profileSignal.signal === 'BUY'
? 'passed'
: profileSignal.signal === 'SELL'
? 'failed'
: profileSignal.signal === 'MIXED'
? 'pending'
: 'skipped';
const executionState = profileSignal.execution?.status; const executionState = profileSignal.execution?.status;
const executionClass = executionState === 'EXECUTED' const executionClass = executionState === 'EXECUTED'
? 'executed' ? 'executed'
@ -135,12 +126,16 @@ export const SymbolCard = ({ symbol, data }: SymbolCardProps) => {
: 'skipped'; : 'skipped';
return ( return (
<div key={profileId} className="rule-container"> <div key={profileId} className="rule-container">
<div className={`rule-badge ${badgeClass}`} title={profileSignal.reason || profileSignal.signal}> <Badge
className="rule-status"
variant={profileSignal.signal === 'BUY' ? 'success' : profileSignal.signal === 'SELL' ? 'danger' : profileSignal.signal === 'MIXED' ? 'warning' : 'neutral'}
title={profileSignal.reason || profileSignal.signal}
>
<span className="rule-name"> <span className="rule-name">
{(profileSignal.profileName || profileId).slice(0, 18)} {(profileSignal.profileName || profileId).slice(0, 18)}
</span> </span>
<span className="rule-icon">{profileSignal.signal}</span> <span className="rule-icon">{profileSignal.signal}</span>
</div> </Badge>
{profileSignal.execution && ( {profileSignal.execution && (
<div className={`profile-execution ${executionClass}`} title={profileSignal.execution.reason}> <div className={`profile-execution ${executionClass}`} title={profileSignal.execution.reason}>
<div className="profile-execution-head"> <div className="profile-execution-head">
@ -156,113 +151,114 @@ export const SymbolCard = ({ symbol, data }: SymbolCardProps) => {
</div> </div>
</div> </div>
)} )}
{data.activePosition && ( {data.activePosition && (
<div className={`active-position-panel ${data.activePosition.side.toLowerCase()}`}> <div className={`active-position-panel ${data.activePosition.side.toLowerCase()}`}>
<div className="pos-header"> <div className="pos-header">
<span className="pos-label">ACTIVE {data.activePosition.side}</span> <span className="pos-label">ACTIVE {data.activePosition.side}</span>
<span className="pos-size">{data.activePosition.size.toFixed(4)} Units</span> <span className="pos-size">{data.activePosition.size.toFixed(4)} Units</span>
</div> </div>
<div className="pos-grid"> <div className="pos-grid">
<div className="pos-item"> <div className="pos-item">
<span className="label">Entry</span> <span className="label">Entry</span>
<span className="value">${data.activePosition.entryPrice.toLocaleString()}</span> <span className="value">${data.activePosition.entryPrice.toLocaleString()}</span>
</div> </div>
<div className={`pos-item pnl ${data.activePosition.unrealizedPnl! >= 0 ? 'up' : 'down'}`}> <div className={`pos-item pnl ${data.activePosition.unrealizedPnl! >= 0 ? 'up' : 'down'}`}>
<span className="label">P/L ($)</span> <span className="label">P/L ($)</span>
<span className="value"> <span className="value">
{data.activePosition.unrealizedPnl! >= 0 ? '+' : ''} {data.activePosition.unrealizedPnl! >= 0 ? '+' : ''}
{data.activePosition.unrealizedPnl!.toFixed(2)} {data.activePosition.unrealizedPnl!.toFixed(2)}
</span> </span>
</div> </div>
<div className={`pos-item pnl-percent ${data.activePosition.unrealizedPnlPercent! >= 0 ? 'up' : 'down'}`}> <div className={`pos-item pnl-percent ${data.activePosition.unrealizedPnlPercent! >= 0 ? 'up' : 'down'}`}>
<span className="label">Return</span> <span className="label">Return</span>
<span className="value"> <span className="value">
{data.activePosition.unrealizedPnlPercent! >= 0 ? '+' : ''} {data.activePosition.unrealizedPnlPercent! >= 0 ? '+' : ''}
{data.activePosition.unrealizedPnlPercent!.toFixed(2)}% {data.activePosition.unrealizedPnlPercent!.toFixed(2)}%
</span> </span>
</div> </div>
<div className="pos-item"> <div className="pos-item">
<span className="label">Value</span> <span className="label">Value</span>
<span className="value">${data.activePosition.marketValue?.toLocaleString()}</span> <span className="value">${data.activePosition.marketValue?.toLocaleString()}</span>
</div> </div>
<div className="pos-item sl"> <div className="pos-item sl">
<span className="label">SL</span> <span className="label">SL</span>
<span className="value">${data.activePosition.stopLoss.toLocaleString()}</span> <span className="value">${data.activePosition.stopLoss.toLocaleString()}</span>
</div> </div>
<div className="pos-item tp"> <div className="pos-item tp">
<span className="label">TP</span> <span className="label">TP</span>
<span className="value">${data.activePosition.takeProfit.toLocaleString()}</span> <span className="value">${data.activePosition.takeProfit.toLocaleString()}</span>
</div> </div>
</div> </div>
</div> </div>
)} )}
<PriceChart data={data.priceHistory || []} currentPrice={data.price} /> <PriceChart data={data.priceHistory || []} currentPrice={data.price} />
<div className="rules-section"> <div className="rules-section">
<h3>Rules</h3> <h3>Rules</h3>
<div className="rules-grid"> <div className="rules-grid">
{Object.entries(data.rules).map(([ruleName, ruleData]) => { {Object.entries(data.rules).map(([ruleName, ruleData]) => {
const isAI = ruleName === 'AIAnalysisRule'; const isAI = ruleName === 'AIAnalysisRule';
const aiData = isAI ? ruleData.metadata : null; const aiData = isAI ? ruleData.metadata : null;
return ( return (
<div key={ruleName} className="rule-container"> <div key={ruleName} className="rule-container">
<div <Badge
className={`rule-badge ${ruleData.isSkipped ? 'skipped' : (ruleData.isPending ? 'pending' : (ruleData.passed ? 'passed' : 'failed'))}`} className="rule-status"
title={ruleData.reason} variant={ruleData.isSkipped ? 'neutral' : (ruleData.isPending ? 'warning' : (ruleData.passed ? 'success' : 'danger'))}
> title={ruleData.reason}
<span className="rule-icon"> >
{ruleData.isSkipped ? '' : (ruleData.isPending ? '⏳' : (ruleData.passed ? '✓' : '✗'))} <span className="rule-icon">
</span> {ruleData.isSkipped ? '' : (ruleData.isPending ? '⏳' : (ruleData.passed ? '✓' : '✗'))}
<span className="rule-name">{ruleDisplayNames[ruleName] || ruleName}</span> </span>
</div> <span className="rule-name">{ruleDisplayNames[ruleName] || ruleName}</span>
{isAI && aiData && ( </Badge>
<div className={`ai-details ${ruleData.passed ? 'passed' : 'failed'}`}> {isAI && aiData && (
{aiData.confidence !== undefined && ( <div className={`ai-details ${ruleData.passed ? 'passed' : 'failed'}`}>
<div className="ai-confidence">Confidence: {aiData.confidence}%</div> {aiData.confidence !== undefined && (
)} <div className="ai-confidence">Confidence: {aiData.confidence}%</div>
{aiData.reasoning && ( )}
<div className="ai-reasoning">{aiData.reasoning}</div> {aiData.reasoning && (
)} <div className="ai-reasoning">{aiData.reasoning}</div>
</div> )}
)} </div>
</div> )}
); </div>
})} );
</div> })}
</div> </div>
</div>
<div className="indicators-section">
<h3>Indicators</h3> <div className="indicators-section">
<div className="indicators-grid"> <h3>Indicators</h3>
{data.indicators.rsi_15m !== undefined && ( <div className="indicators-grid">
<div className="indicator"> {data.indicators.rsi_15m !== undefined && (
<span className="indicator-label">RSI 15m:</span> <div className="indicator">
<span className="indicator-value">{data.indicators.rsi_15m.toFixed(1)}</span> <span className="indicator-label">RSI 15m:</span>
</div> <span className="indicator-value">{data.indicators.rsi_15m.toFixed(1)}</span>
)} </div>
{data.indicators.rsi_1h !== undefined && ( )}
<div className="indicator"> {data.indicators.rsi_1h !== undefined && (
<span className="indicator-label">RSI 1h:</span> <div className="indicator">
<span className="indicator-value">{data.indicators.rsi_1h.toFixed(1)}</span> <span className="indicator-label">RSI 1h:</span>
</div> <span className="indicator-value">{data.indicators.rsi_1h.toFixed(1)}</span>
)} </div>
{data.indicators.ema20_15m !== undefined && ( )}
<div className="indicator"> {data.indicators.ema20_15m !== undefined && (
<span className="indicator-label">EMA20 15m:</span> <div className="indicator">
<span className="indicator-value">${data.indicators.ema20_15m.toLocaleString()}</span> <span className="indicator-label">EMA20 15m:</span>
</div> <span className="indicator-value">${data.indicators.ema20_15m.toLocaleString()}</span>
)} </div>
{data.indicators.ema20_1h !== undefined && ( )}
<div className="indicator"> {data.indicators.ema20_1h !== undefined && (
<span className="indicator-label">EMA20 1h:</span> <div className="indicator">
<span className="indicator-value">${data.indicators.ema20_1h.toLocaleString()}</span> <span className="indicator-label">EMA20 1h:</span>
</div> <span className="indicator-value">${data.indicators.ema20_1h.toLocaleString()}</span>
)} </div>
</div> )}
</div> </div>
</div> </div>
); </div>
}; );
};