learning_ai_invt_trdg/web/src/components/SymbolCard.tsx

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