+ {/* Header */}
+
+
+
+
+
Indicators
+
{enabledCount} active
+
+
+
+
+ {/* Presets Button */}
+
+
setShowPresets(!showPresets)}
+ className="px-3 py-1.5 bg-gray-700 hover:bg-gray-600 text-gray-300 rounded text-xs font-medium transition-colors"
+ >
+ Presets
+
+
+ {showPresets && (
+
+
+
Load Preset
+
+ {savedPresets.map((preset, idx) => (
+ handleLoadPreset(preset)}
+ className="w-full text-left px-3 py-2 text-sm text-white hover:bg-gray-700 rounded transition-colors"
+ >
+ {preset.name}
+
+ ({preset.indicators.length} indicators)
+
+
+ ))}
+
+
+
+
Save Current
+
+ setPresetName(e.target.value)}
+ placeholder="Preset name..."
+ className="flex-1 px-2 py-1.5 bg-gray-900 border border-gray-700 rounded text-xs text-white focus:outline-none focus:border-blue-500"
+ />
+
+
+
+
+
+
+ )}
+
+
+ {/* Add Indicator Button */}
+
+
setShowAddMenu(!showAddMenu)}
+ className="flex items-center gap-1 px-3 py-1.5 bg-blue-600 hover:bg-blue-500 text-white rounded text-xs font-medium transition-colors"
+ >
+
+ Add
+
+
+ {showAddMenu && (
+
+ {Object.entries(INDICATOR_DEFINITIONS).map(([type, def]) => (
+
handleAddIndicator(type as IndicatorType)}
+ className="w-full flex items-center gap-3 px-4 py-2.5 text-white hover:bg-gray-700 transition-colors"
+ >
+
+ {def.icon}
+
+ {def.label}
+
+ ))}
+
+ )}
+
+
+
+
+ {/* Indicators List */}
+ {indicators.length === 0 ? (
+
+
+
No indicators configured
+
Click "Add" to add technical indicators
+
+ ) : (
+
+ {indicators.map((indicator) => {
+ const def = INDICATOR_DEFINITIONS[indicator.type];
+ const isExpanded = expandedId === indicator.id;
+
+ return (
+
+ {/* Header Row */}
+
setExpandedId(isExpanded ? null : indicator.id)}
+ className="flex items-center justify-between p-3 cursor-pointer hover:bg-gray-800/50"
+ >
+
+
+
+ {def.icon}
+
+
+
{def.label}
+
+ {Object.entries(indicator.params)
+ .map(([k, v]) => `${def.paramLabels[k]}: ${v}`)
+ .join(' • ')}
+
+
+
+
+
+ {
+ e.stopPropagation();
+ handleToggleEnabled(indicator.id);
+ }}
+ className="p-1.5 text-gray-400 hover:text-white transition-colors"
+ >
+ {indicator.enabled ? : }
+
+ {isExpanded ? (
+
+ ) : (
+
+ )}
+
+
+
+ {/* Expanded Config */}
+ {isExpanded && (
+
+ {/* Parameters */}
+
+ {Object.entries(indicator.params).map(([paramKey, paramValue]) => (
+
+
+ {def.paramLabels[paramKey]}
+
+ handleParamChange(indicator.id, paramKey, Number(e.target.value))}
+ className="w-full px-2 py-1.5 bg-gray-800 border border-gray-700 rounded text-white text-sm focus:outline-none focus:border-blue-500"
+ />
+
+ ))}
+
+
+ {/* Style Options */}
+
+
+
Color
+
+ {PRESET_COLORS.slice(0, 5).map((color) => (
+ handleColorChange(indicator.id, color)}
+ className={`w-6 h-6 rounded-full border-2 transition-all ${
+ indicator.color === color ? 'border-white scale-110' : 'border-transparent'
+ }`}
+ style={{ backgroundColor: color }}
+ />
+ ))}
+
+
+
+
Line Width
+
+ {[1, 2, 3].map((width) => (
+ handleLineWidthChange(indicator.id, width)}
+ className={`w-8 h-6 rounded text-xs ${
+ indicator.lineWidth === width
+ ? 'bg-blue-600 text-white'
+ : 'bg-gray-700 text-gray-300'
+ }`}
+ >
+ {width}px
+
+ ))}
+
+
+
+
+ {/* Actions */}
+
+
+ handleResetDefaults(indicator.id)}
+ className="flex items-center gap-1 px-2 py-1 text-xs text-gray-400 hover:text-white transition-colors"
+ >
+
+ Reset
+
+ handleDuplicateIndicator(indicator)}
+ className="flex items-center gap-1 px-2 py-1 text-xs text-gray-400 hover:text-white transition-colors"
+ >
+
+ Duplicate
+
+
+
onIndicatorRemove?.(indicator.id)}
+ className="flex items-center gap-1 px-2 py-1 text-xs text-red-400 hover:text-red-300 transition-colors"
+ >
+
+ Remove
+
+
+
+ )}
+
+ );
+ })}
+
+ )}
+
+ {/* Close menus on outside click */}
+ {(showAddMenu || showPresets) && (
+
{
+ setShowAddMenu(false);
+ setShowPresets(false);
+ }}
+ />
+ )}
+
+ );
+};
+
+export default IndicatorConfigPanel;
diff --git a/src/modules/trading/components/SymbolInfoPanel.tsx b/src/modules/trading/components/SymbolInfoPanel.tsx
new file mode 100644
index 0000000..d62d0ad
--- /dev/null
+++ b/src/modules/trading/components/SymbolInfoPanel.tsx
@@ -0,0 +1,433 @@
+/**
+ * SymbolInfoPanel Component
+ * Comprehensive symbol information and details sidebar
+ * OQI-003: Trading y Charts
+ */
+
+import React, { useState, useMemo } from 'react';
+import {
+ Info,
+ TrendingUp,
+ TrendingDown,
+ BarChart3,
+ Activity,
+ Clock,
+ DollarSign,
+ Percent,
+ Globe,
+ Star,
+ StarOff,
+ ExternalLink,
+ RefreshCw,
+ ChevronDown,
+ ChevronUp,
+ Zap,
+ AlertCircle,
+} from 'lucide-react';
+
+export interface SymbolStats {
+ price: number;
+ change24h: number;
+ changePercent24h: number;
+ high24h: number;
+ low24h: number;
+ open24h: number;
+ volume24h: number;
+ volumeQuote24h: number;
+ marketCap?: number;
+ circulatingSupply?: number;
+ totalSupply?: number;
+ allTimeHigh?: number;
+ allTimeLow?: number;
+ athDate?: string;
+ atlDate?: string;
+}
+
+export interface SymbolInfo {
+ symbol: string;
+ name: string;
+ baseAsset: string;
+ quoteAsset: string;
+ exchange: string;
+ type: 'spot' | 'futures' | 'forex' | 'crypto';
+ status: 'trading' | 'halt' | 'break';
+ minNotional?: number;
+ tickSize?: number;
+ stepSize?: number;
+ maxLeverage?: number;
+ tradingHours?: string;
+ description?: string;
+}
+
+interface RelatedSymbol {
+ symbol: string;
+ correlation: number;
+ change24h: number;
+}
+
+interface SymbolInfoPanelProps {
+ symbol: string;
+ info?: SymbolInfo;
+ stats?: SymbolStats;
+ relatedSymbols?: RelatedSymbol[];
+ isFavorite?: boolean;
+ onToggleFavorite?: (symbol: string) => void;
+ onSymbolSelect?: (symbol: string) => void;
+ onRefresh?: () => void;
+ isLoading?: boolean;
+ compact?: boolean;
+}
+
+const SymbolInfoPanel: React.FC
= ({
+ symbol,
+ info,
+ stats,
+ relatedSymbols = [],
+ isFavorite = false,
+ onToggleFavorite,
+ onSymbolSelect,
+ onRefresh,
+ isLoading = false,
+ compact = false,
+}) => {
+ const [expandedSection, setExpandedSection] = useState('stats');
+
+ const priceChangeColor = useMemo(() => {
+ if (!stats) return 'text-gray-400';
+ return stats.change24h >= 0 ? 'text-green-400' : 'text-red-400';
+ }, [stats]);
+
+ const formatNumber = (num: number, decimals = 2) => {
+ if (num >= 1e9) return `${(num / 1e9).toFixed(decimals)}B`;
+ if (num >= 1e6) return `${(num / 1e6).toFixed(decimals)}M`;
+ if (num >= 1e3) return `${(num / 1e3).toFixed(decimals)}K`;
+ return num.toFixed(decimals);
+ };
+
+ const formatPrice = (price: number) => {
+ if (price >= 1000) return price.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
+ if (price >= 1) return price.toFixed(4);
+ return price.toFixed(8);
+ };
+
+ const formatPercent = (percent: number) => {
+ const sign = percent >= 0 ? '+' : '';
+ return `${sign}${percent.toFixed(2)}%`;
+ };
+
+ const getStatusBadge = (status: string) => {
+ switch (status) {
+ case 'trading':
+ return Trading ;
+ case 'halt':
+ return Halted ;
+ case 'break':
+ return Break ;
+ default:
+ return null;
+ }
+ };
+
+ const toggleSection = (section: string) => {
+ setExpandedSection(expandedSection === section ? null : section);
+ };
+
+ return (
+
+ {/* Header */}
+
+
+
+
+
+
+
+
{symbol}
+ {info && getStatusBadge(info.status)}
+
+ {info && (
+
{info.name}
+ )}
+
+
+
+
+ onToggleFavorite?.(symbol)}
+ className={`p-2 rounded-lg transition-colors ${
+ isFavorite
+ ? 'text-yellow-400 bg-yellow-500/10'
+ : 'text-gray-500 hover:text-yellow-400 hover:bg-gray-700'
+ }`}
+ >
+ {isFavorite ? : }
+
+
+
+
+
+
+
+ {/* Price Display */}
+ {stats && (
+
+
+
+
${formatPrice(stats.price)}
+
+ {stats.change24h >= 0 ? (
+
+ ) : (
+
+ )}
+
+ {stats.change24h >= 0 ? '+' : ''}{formatPrice(stats.change24h)}
+
+
+ ({formatPercent(stats.changePercent24h)})
+
+
+
+
+
24h High: ${formatPrice(stats.high24h)}
+
24h Low: ${formatPrice(stats.low24h)}
+
+
+
+ {/* Mini Stats Row */}
+
+
+
Volume
+
${formatNumber(stats.volumeQuote24h)}
+
+ {stats.marketCap && (
+
+
Market Cap
+
${formatNumber(stats.marketCap)}
+
+ )}
+
+
Open
+
${formatPrice(stats.open24h)}
+
+
+
+ )}
+
+ {/* Expandable Sections */}
+
+ {/* Stats Section */}
+ {stats && (
+
+
toggleSection('stats')}
+ className="w-full flex items-center justify-between p-3 hover:bg-gray-800/50 transition-colors"
+ >
+
+
+ Statistics
+
+ {expandedSection === 'stats' ? (
+
+ ) : (
+
+ )}
+
+
+ {expandedSection === 'stats' && (
+
+
+ 24h Volume ({info?.baseAsset})
+ {formatNumber(stats.volume24h)}
+
+ {stats.allTimeHigh && (
+
+
All-Time High
+
+
${formatPrice(stats.allTimeHigh)}
+ {stats.athDate && (
+
{stats.athDate}
+ )}
+
+
+ )}
+ {stats.allTimeLow && (
+
+
All-Time Low
+
+
${formatPrice(stats.allTimeLow)}
+ {stats.atlDate && (
+
{stats.atlDate}
+ )}
+
+
+ )}
+ {stats.circulatingSupply && (
+
+ Circulating Supply
+ {formatNumber(stats.circulatingSupply, 0)}
+
+ )}
+
+ )}
+
+ )}
+
+ {/* Info Section */}
+ {info && (
+
+
toggleSection('info')}
+ className="w-full flex items-center justify-between p-3 hover:bg-gray-800/50 transition-colors"
+ >
+
+ {expandedSection === 'info' ? (
+
+ ) : (
+
+ )}
+
+
+ {expandedSection === 'info' && (
+
+
+ Exchange
+ {info.exchange}
+
+
+ Type
+ {info.type}
+
+
+ Base Asset
+ {info.baseAsset}
+
+
+ Quote Asset
+ {info.quoteAsset}
+
+ {info.tickSize && (
+
+ Tick Size
+ {info.tickSize}
+
+ )}
+ {info.minNotional && (
+
+ Min Order
+ ${info.minNotional}
+
+ )}
+ {info.maxLeverage && (
+
+ Max Leverage
+ {info.maxLeverage}x
+
+ )}
+ {info.tradingHours && (
+
+ Trading Hours
+ {info.tradingHours}
+
+ )}
+
+ )}
+
+ )}
+
+ {/* Related Symbols */}
+ {relatedSymbols.length > 0 && (
+
+
toggleSection('related')}
+ className="w-full flex items-center justify-between p-3 hover:bg-gray-800/50 transition-colors"
+ >
+
+
+ Related Symbols
+
+ {expandedSection === 'related' ? (
+
+ ) : (
+
+ )}
+
+
+ {expandedSection === 'related' && (
+
+ {relatedSymbols.map((related) => (
+
onSymbolSelect?.(related.symbol)}
+ className="w-full flex items-center justify-between p-2 hover:bg-gray-800 rounded transition-colors"
+ >
+
+ {related.symbol}
+
+ {related.correlation > 0 ? '+' : ''}{(related.correlation * 100).toFixed(0)}% corr
+
+
+ = 0 ? 'text-green-400' : 'text-red-400'}`}>
+ {formatPercent(related.change24h)}
+
+
+ ))}
+
+ )}
+
+ )}
+
+ {/* Description */}
+ {info?.description && (
+
+
toggleSection('desc')}
+ className="w-full flex items-center justify-between p-3 hover:bg-gray-800/50 transition-colors"
+ >
+
+ {expandedSection === 'desc' ? (
+
+ ) : (
+
+ )}
+
+
+ {expandedSection === 'desc' && (
+
+ )}
+
+ )}
+
+
+ {/* Quick Actions */}
+ {!compact && (
+
+
+
+
+ Buy
+
+
+
+ Sell
+
+
+
+ )}
+
+ );
+};
+
+export default SymbolInfoPanel;
diff --git a/src/modules/trading/components/TradeJournalPanel.tsx b/src/modules/trading/components/TradeJournalPanel.tsx
new file mode 100644
index 0000000..0829221
--- /dev/null
+++ b/src/modules/trading/components/TradeJournalPanel.tsx
@@ -0,0 +1,609 @@
+/**
+ * TradeJournalPanel Component
+ * Trade review, analysis, and journaling for paper trading
+ * OQI-003: Trading y Charts
+ */
+
+import React, { useState, useMemo } from 'react';
+import {
+ BookOpen,
+ TrendingUp,
+ TrendingDown,
+ Calendar,
+ Tag,
+ MessageSquare,
+ Star,
+ Filter,
+ Search,
+ Download,
+ BarChart3,
+ Target,
+ Clock,
+ DollarSign,
+ Percent,
+ ChevronDown,
+ ChevronUp,
+ Edit3,
+ Save,
+ X,
+ Plus,
+ Award,
+ AlertTriangle,
+ Zap,
+} from 'lucide-react';
+
+export interface TradeEntry {
+ id: string;
+ ticket: number;
+ symbol: string;
+ type: 'BUY' | 'SELL';
+ entryPrice: number;
+ exitPrice: number;
+ lots: number;
+ profit: number;
+ profitPercent: number;
+ entryTime: string;
+ exitTime: string;
+ duration: number; // minutes
+ stopLoss?: number;
+ takeProfit?: number;
+ // Journal fields
+ notes?: string;
+ rating?: 1 | 2 | 3 | 4 | 5;
+ tags?: string[];
+ strategy?: string;
+ setup?: string;
+ emotions?: string;
+ lessonsLearned?: string;
+ screenshots?: string[];
+}
+
+interface TradeStats {
+ totalTrades: number;
+ winningTrades: number;
+ losingTrades: number;
+ winRate: number;
+ totalProfit: number;
+ averageWin: number;
+ averageLoss: number;
+ profitFactor: number;
+ largestWin: number;
+ largestLoss: number;
+ averageRR: number;
+ currentStreak: number;
+ longestWinStreak: number;
+ longestLoseStreak: number;
+ averageDuration: number;
+}
+
+interface TradeJournalPanelProps {
+ trades?: TradeEntry[];
+ onTradeUpdate?: (trade: TradeEntry) => void;
+ onExportJournal?: (format: 'csv' | 'pdf') => void;
+ compact?: boolean;
+}
+
+const STRATEGY_TAGS = ['Trend Following', 'Reversal', 'Breakout', 'Scalping', 'Swing', 'Range'];
+const SETUP_TAGS = ['Support/Resistance', 'Moving Average', 'RSI Divergence', 'MACD Cross', 'Fibonacci', 'Pattern'];
+const EMOTION_TAGS = ['Confident', 'Fearful', 'Greedy', 'Patient', 'Impulsive', 'Disciplined'];
+
+const TradeJournalPanel: React.FC = ({
+ trades = [],
+ onTradeUpdate,
+ onExportJournal,
+ compact = false,
+}) => {
+ const [selectedTrade, setSelectedTrade] = useState(null);
+ const [isEditing, setIsEditing] = useState(false);
+ const [editedNotes, setEditedNotes] = useState('');
+ const [editedRating, setEditedRating] = useState(0);
+ const [editedTags, setEditedTags] = useState([]);
+ const [editedStrategy, setEditedStrategy] = useState('');
+ const [editedLessons, setEditedLessons] = useState('');
+ const [filterSymbol, setFilterSymbol] = useState('');
+ const [filterResult, setFilterResult] = useState<'all' | 'wins' | 'losses'>('all');
+ const [showStats, setShowStats] = useState(true);
+
+ // Calculate statistics
+ const stats: TradeStats = useMemo(() => {
+ if (trades.length === 0) {
+ return {
+ totalTrades: 0,
+ winningTrades: 0,
+ losingTrades: 0,
+ winRate: 0,
+ totalProfit: 0,
+ averageWin: 0,
+ averageLoss: 0,
+ profitFactor: 0,
+ largestWin: 0,
+ largestLoss: 0,
+ averageRR: 0,
+ currentStreak: 0,
+ longestWinStreak: 0,
+ longestLoseStreak: 0,
+ averageDuration: 0,
+ };
+ }
+
+ const wins = trades.filter(t => t.profit > 0);
+ const losses = trades.filter(t => t.profit < 0);
+ const totalWins = wins.reduce((sum, t) => sum + t.profit, 0);
+ const totalLosses = Math.abs(losses.reduce((sum, t) => sum + t.profit, 0));
+
+ // Calculate streaks
+ let currentStreak = 0;
+ let longestWinStreak = 0;
+ let longestLoseStreak = 0;
+ let tempWinStreak = 0;
+ let tempLoseStreak = 0;
+
+ const sortedTrades = [...trades].sort((a, b) =>
+ new Date(b.exitTime).getTime() - new Date(a.exitTime).getTime()
+ );
+
+ sortedTrades.forEach((trade, idx) => {
+ if (trade.profit > 0) {
+ tempWinStreak++;
+ tempLoseStreak = 0;
+ if (tempWinStreak > longestWinStreak) longestWinStreak = tempWinStreak;
+ if (idx === 0) currentStreak = tempWinStreak;
+ } else {
+ tempLoseStreak++;
+ tempWinStreak = 0;
+ if (tempLoseStreak > longestLoseStreak) longestLoseStreak = tempLoseStreak;
+ if (idx === 0) currentStreak = -tempLoseStreak;
+ }
+ });
+
+ return {
+ totalTrades: trades.length,
+ winningTrades: wins.length,
+ losingTrades: losses.length,
+ winRate: (wins.length / trades.length) * 100,
+ totalProfit: trades.reduce((sum, t) => sum + t.profit, 0),
+ averageWin: wins.length > 0 ? totalWins / wins.length : 0,
+ averageLoss: losses.length > 0 ? totalLosses / losses.length : 0,
+ profitFactor: totalLosses > 0 ? totalWins / totalLosses : totalWins,
+ largestWin: wins.length > 0 ? Math.max(...wins.map(t => t.profit)) : 0,
+ largestLoss: losses.length > 0 ? Math.min(...losses.map(t => t.profit)) : 0,
+ averageRR: losses.length > 0 && wins.length > 0
+ ? (totalWins / wins.length) / (totalLosses / losses.length) : 0,
+ currentStreak,
+ longestWinStreak,
+ longestLoseStreak,
+ averageDuration: trades.reduce((sum, t) => sum + t.duration, 0) / trades.length,
+ };
+ }, [trades]);
+
+ // Filter trades
+ const filteredTrades = useMemo(() => {
+ return trades.filter(trade => {
+ if (filterSymbol && !trade.symbol.toLowerCase().includes(filterSymbol.toLowerCase())) {
+ return false;
+ }
+ if (filterResult === 'wins' && trade.profit <= 0) return false;
+ if (filterResult === 'losses' && trade.profit >= 0) return false;
+ return true;
+ });
+ }, [trades, filterSymbol, filterResult]);
+
+ const handleEditTrade = (trade: TradeEntry) => {
+ setSelectedTrade(trade);
+ setEditedNotes(trade.notes || '');
+ setEditedRating(trade.rating || 0);
+ setEditedTags(trade.tags || []);
+ setEditedStrategy(trade.strategy || '');
+ setEditedLessons(trade.lessonsLearned || '');
+ setIsEditing(true);
+ };
+
+ const handleSaveTrade = () => {
+ if (!selectedTrade) return;
+
+ const updatedTrade: TradeEntry = {
+ ...selectedTrade,
+ notes: editedNotes,
+ rating: editedRating as 1 | 2 | 3 | 4 | 5,
+ tags: editedTags,
+ strategy: editedStrategy,
+ lessonsLearned: editedLessons,
+ };
+
+ onTradeUpdate?.(updatedTrade);
+ setIsEditing(false);
+ setSelectedTrade(updatedTrade);
+ };
+
+ const handleToggleTag = (tag: string) => {
+ if (editedTags.includes(tag)) {
+ setEditedTags(editedTags.filter(t => t !== tag));
+ } else {
+ setEditedTags([...editedTags, tag]);
+ }
+ };
+
+ const formatDuration = (minutes: number) => {
+ if (minutes < 60) return `${minutes}m`;
+ if (minutes < 1440) return `${Math.floor(minutes / 60)}h ${minutes % 60}m`;
+ return `${Math.floor(minutes / 1440)}d ${Math.floor((minutes % 1440) / 60)}h`;
+ };
+
+ const formatDate = (dateString: string) => {
+ return new Date(dateString).toLocaleDateString('en-US', {
+ month: 'short',
+ day: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ });
+ };
+
+ return (
+
+ {/* Header */}
+
+
+
+
+
Trade Journal
+
{trades.length} trades recorded
+
+
+
+
+ onExportJournal?.('csv')}
+ className="p-2 text-gray-400 hover:text-white hover:bg-gray-700 rounded-lg transition-colors"
+ title="Export CSV"
+ >
+
+
+
+
+
+ {/* Stats Summary */}
+ {showStats && (
+
+
+
= 0 ? 'text-green-400' : 'text-red-400'}`}>
+ ${stats.totalProfit.toFixed(2)}
+
+
Total P&L
+
+
+
{stats.winRate.toFixed(1)}%
+
Win Rate
+
+
+
{stats.profitFactor.toFixed(2)}
+
Profit Factor
+
+
+
= 0 ? 'text-green-400' : 'text-red-400'}`}>
+ {stats.currentStreak >= 0 ? `+${stats.currentStreak}` : stats.currentStreak}
+
+
Streak
+
+
+ )}
+
+ {/* Extended Stats */}
+ {!compact && showStats && (
+
+
+
+
+
Best Trade
+
${stats.largestWin.toFixed(2)}
+
+
+
+
+
+
Worst Trade
+
${stats.largestLoss.toFixed(2)}
+
+
+
+
+
+
Avg Duration
+
{formatDuration(stats.averageDuration)}
+
+
+
+ )}
+
+ {/* Filters */}
+
+
+
+ setFilterSymbol(e.target.value)}
+ placeholder="Filter by symbol..."
+ className="w-full pl-10 pr-4 py-2 bg-gray-900 border border-gray-700 rounded-lg text-white text-sm focus:outline-none focus:border-blue-500"
+ />
+
+
setFilterResult(e.target.value as 'all' | 'wins' | 'losses')}
+ className="px-3 py-2 bg-gray-900 border border-gray-700 rounded-lg text-white text-sm"
+ >
+ All
+ Wins
+ Losses
+
+
+
+ {/* Trades List */}
+ {filteredTrades.length === 0 ? (
+
+
+
No trades to journal
+
Complete some trades to start journaling
+
+ ) : (
+
+ {filteredTrades.map((trade) => (
+
setSelectedTrade(selectedTrade?.id === trade.id ? null : trade)}
+ className={`p-3 bg-gray-900/50 rounded-lg border cursor-pointer transition-all ${
+ selectedTrade?.id === trade.id
+ ? 'border-blue-500/50 bg-blue-500/5'
+ : 'border-gray-700 hover:border-gray-600'
+ }`}
+ >
+
+
+
+ {trade.type === 'BUY' ? (
+
+ ) : (
+
+ )}
+
+
+
+
{trade.symbol}
+
{trade.lots} lots
+ {trade.rating && (
+
+ {Array.from({ length: trade.rating }).map((_, i) => (
+
+ ))}
+
+ )}
+
+
+ {formatDate(trade.exitTime)} • {formatDuration(trade.duration)}
+
+
+
+
+
= 0 ? 'text-green-400' : 'text-red-400'}`}>
+ {trade.profit >= 0 ? '+' : ''}{trade.profit.toFixed(2)}
+
+
= 0 ? 'text-green-400' : 'text-red-400'}`}>
+ {trade.profitPercent >= 0 ? '+' : ''}{trade.profitPercent.toFixed(2)}%
+
+
+
+
+ {/* Tags */}
+ {trade.tags && trade.tags.length > 0 && (
+
+ {trade.tags.map((tag) => (
+
+ {tag}
+
+ ))}
+
+ )}
+
+ {/* Expanded Details */}
+ {selectedTrade?.id === trade.id && !isEditing && (
+
+
+
+ Entry:
+ {trade.entryPrice.toFixed(5)}
+
+
+ Exit:
+ {trade.exitPrice.toFixed(5)}
+
+ {trade.stopLoss && (
+
+ SL:
+ {trade.stopLoss.toFixed(5)}
+
+ )}
+ {trade.takeProfit && (
+
+ TP:
+ {trade.takeProfit.toFixed(5)}
+
+ )}
+
+
+ {trade.strategy && (
+
+ Strategy:
+ {trade.strategy}
+
+ )}
+
+ {trade.notes && (
+
+
Notes:
+
{trade.notes}
+
+ )}
+
+ {trade.lessonsLearned && (
+
+
Lessons:
+
{trade.lessonsLearned}
+
+ )}
+
+
{
+ e.stopPropagation();
+ handleEditTrade(trade);
+ }}
+ className="flex items-center gap-1 px-3 py-1.5 bg-blue-600 hover:bg-blue-500 text-white rounded text-xs mt-2"
+ >
+
+ Edit Journal Entry
+
+
+ )}
+
+ ))}
+
+ )}
+
+ {/* Edit Modal */}
+ {isEditing && selectedTrade && (
+
+
+
+
Edit Journal Entry
+ setIsEditing(false)}
+ className="p-2 text-gray-400 hover:text-white"
+ >
+
+
+
+
+
+ {/* Trade Info */}
+
+
+ {selectedTrade.type === 'BUY' ? (
+
+ ) : (
+
+ )}
+
+
+
{selectedTrade.symbol}
+
= 0 ? 'text-green-400' : 'text-red-400'}`}>
+ {selectedTrade.profit >= 0 ? '+' : ''}{selectedTrade.profit.toFixed(2)}
+
+
+
+
+ {/* Rating */}
+
+
Trade Rating
+
+ {[1, 2, 3, 4, 5].map((rating) => (
+ setEditedRating(rating)}
+ className="p-1"
+ >
+
+
+ ))}
+
+
+
+ {/* Strategy */}
+
+
Strategy
+
+ {STRATEGY_TAGS.map((tag) => (
+ setEditedStrategy(tag)}
+ className={`px-3 py-1 rounded text-xs ${
+ editedStrategy === tag
+ ? 'bg-blue-600 text-white'
+ : 'bg-gray-700 text-gray-300 hover:bg-gray-600'
+ }`}
+ >
+ {tag}
+
+ ))}
+
+
+
+ {/* Tags */}
+
+
Tags
+
+ {[...SETUP_TAGS, ...EMOTION_TAGS].map((tag) => (
+ handleToggleTag(tag)}
+ className={`px-3 py-1 rounded text-xs ${
+ editedTags.includes(tag)
+ ? 'bg-blue-600 text-white'
+ : 'bg-gray-700 text-gray-300 hover:bg-gray-600'
+ }`}
+ >
+ {tag}
+
+ ))}
+
+
+
+ {/* Notes */}
+
+ Notes
+
+
+ {/* Lessons Learned */}
+
+ Lessons Learned
+
+
+
+
+ setIsEditing(false)}
+ className="flex-1 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded-lg font-medium"
+ >
+ Cancel
+
+
+
+ Save Entry
+
+
+
+
+ )}
+
+ );
+};
+
+export default TradeJournalPanel;
diff --git a/src/modules/trading/components/index.ts b/src/modules/trading/components/index.ts
index 28d3117..785579e 100644
--- a/src/modules/trading/components/index.ts
+++ b/src/modules/trading/components/index.ts
@@ -9,6 +9,14 @@ export { default as CandlestickChart } from './CandlestickChart';
export { default as CandlestickChartWithML } from './CandlestickChartWithML';
export { default as TradingChart } from './TradingChart';
export { default as ChartToolbar } from './ChartToolbar';
+export { default as IndicatorConfigPanel } from './IndicatorConfigPanel';
+export type { IndicatorType, IndicatorConfig } from './IndicatorConfigPanel';
+export { default as ChartDrawingToolsPanel } from './ChartDrawingToolsPanel';
+export type { DrawingTool, Drawing } from './ChartDrawingToolsPanel';
+export { default as SymbolInfoPanel } from './SymbolInfoPanel';
+export type { SymbolStats, SymbolInfo } from './SymbolInfoPanel';
+export { default as TradeJournalPanel } from './TradeJournalPanel';
+export type { TradeEntry } from './TradeJournalPanel';
// Order & Position Components
export { default as OrderForm } from './OrderForm';