refactor(ui): remove legacy badge style debt
This commit is contained in:
parent
45e389fd2a
commit
5f38adac62
@ -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),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
};
|
);
|
||||||
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user