--- id: "US-TRD-018" title: "Comparar Multiples Simbolos en el Chart" type: "User Story" status: "Done" priority: "Media" epic: "OQI-003" story_points: 5 created_date: "2025-12-05" updated_date: "2026-01-04" --- # US-TRD-018: Comparar Múltiples Símbolos en el Chart ## Metadata | Campo | Valor | |-------|-------| | **ID** | US-TRD-018 | | **Épica** | OQI-003 - Trading y Charts | | **Módulo** | trading | | **Prioridad** | P2 | | **Story Points** | 5 | | **Sprint** | Sprint 7 | | **Estado** | Pendiente | | **Asignado a** | Por asignar | --- ## Historia de Usuario **Como** trader avanzado, **quiero** comparar múltiples símbolos en el mismo chart, **para** analizar correlaciones, divergencias y movimientos relativos entre diferentes activos. ## Descripción Detallada El usuario debe poder superponer múltiples símbolos en el mismo chart para comparación visual. Los precios se normalizan a un índice base (100) para permitir comparación de activos con diferentes escalas de precio. Cada símbolo tiene un color distintivo y se puede mostrar/ocultar individualmente. ## Mockups/Wireframes ``` ┌─────────────────────────────────────────────────────────────────┐ │ COMPARE MODE [Exit] │ ├─────────────────────────────────────────────────────────────────┤ │ [+ Add Symbol to Compare] │ │ │ │ Active Comparisons: │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ ● BTCUSDT $97,234.50 +2.34% [────] [x] │ │ │ │ ● ETHUSDT $3,845.20 -0.45% [────] [x] │ │ │ │ ● SOLUSDT $142.73 +1.56% [────] [x] │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ │ View Mode: [Normalized ▼] │ │ Options: Normalized (Index), Absolute Price, % Change │ │ │ │ Base Date: [2025-12-01] (Index = 100 at this date) │ │ │ ├─────────────────────────────────────────────────────────────────┤ │ NORMALIZED VIEW (Index Base = 100) │ │ │ │ 120 ┤ ──── BTC (Blue) │ │ 115 ┤ ──────── │ │ 110 ┤ ──────── │ │ 105 ┤ ════════ ════ ETH (Orange) │ │ 100 ┤──────── │ │ 95 ┤ ···························· ···· SOL (Green) │ │ 90 ┤ │ │ │ │ Legend: │ │ ──── BTCUSDT (+15.2% from base) │ │ ════ ETHUSDT (+8.7% from base) │ │ ···· SOLUSDT (-3.4% from base) │ │ │ │ Correlation Matrix: │ │ ┌─────────────────────────────────────┐ │ │ │ BTC ETH SOL │ │ │ │ BTC 1.00 0.87 0.65 │ │ │ │ ETH 0.87 1.00 0.72 │ │ │ │ SOL 0.65 0.72 1.00 │ │ │ └─────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ABSOLUTE PRICE VIEW: ┌─────────────────────────────────────────┐ │ Dual Y-Axis Mode │ ├─────────────────────────────────────────┤ │ $100K ┤ ──── BTC │ │ $95K ┤ ──────── │ │ $90K ┤ ────────── │ │ │ │ │ $4K ┤ ════════ ETH │ │ $3.8K ┤ ════════ │ │ $3.6K ┤ │ │ │ │ │ $150 ┤ ···························· │ │ $140 ┤ ····· SOL │ │ $130 ┤ ····· │ └─────────────────────────────────────────┘ ``` --- ## Criterios de Aceptación **Escenario 1: Activar modo comparación** ```gherkin DADO que el usuario está viendo chart de BTCUSDT CUANDO hace click en botón "Compare" ENTONCES el chart entra en modo comparación Y BTCUSDT se convierte en el símbolo base Y se muestra botón "+ Add Symbol to Compare" Y se muestra la lista de símbolos activos ``` **Escenario 2: Agregar símbolo a comparación** ```gherkin DADO que el usuario está en modo comparación con BTCUSDT CUANDO hace click en "+ Add Symbol to Compare" Y busca "ETH" Y selecciona "ETHUSDT" ENTONCES ETHUSDT se agrega al chart Y se muestra con línea de color diferente (naranja) Y aparece en la lista de símbolos activos Y ambos símbolos están visibles simultáneamente ``` **Escenario 3: Ver modo normalizado (índice base 100)** ```gherkin DADO que hay 3 símbolos en comparación Y el modo es "Normalized" Y la fecha base es 2025-12-01 ENTONCES todos los símbolos comienzan en índice 100 en la fecha base Y los valores subsecuentes muestran cambio relativo al base Ejemplo: - BTC: $90K → $100K = Índice 100 → 111.1 (+11.1%) - ETH: $3.6K → $3.9K = Índice 100 → 108.3 (+8.3%) Y se pueden comparar visualmente los rendimientos ``` **Escenario 4: Ver modo precio absoluto** ```gherkin DADO que el usuario selecciona "Absolute Price" ENTONCES cada símbolo muestra su precio real Y se usan ejes Y múltiples (uno por símbolo) Y los ejes Y están a la derecha con colores matching Y el chart permite ver movimientos absolutos ``` **Escenario 5: Ver modo % de cambio** ```gherkin DADO que el usuario selecciona "% Change" Y la fecha base es 2025-12-01 ENTONCES todos los símbolos muestran % de cambio desde el base Y el eje Y muestra -10%, 0%, +10%, +20%, etc. Y las líneas cruzan en 0% en la fecha base ``` **Escenario 6: Ocultar/mostrar símbolo** ```gherkin DADO que hay 3 símbolos en comparación CUANDO el usuario hace click en el color de ETHUSDT ENTONCES la línea de ETHUSDT se oculta Y sigue en la lista pero con opacidad reducida Y puede volver a hacer click para mostrarla ``` **Escenario 7: Eliminar símbolo de comparación** ```gherkin DADO que SOLUSDT está en comparación CUANDO el usuario hace click en [x] junto a SOLUSDT ENTONCES SOLUSDT se elimina del chart Y desaparece de la lista Y su línea desaparece del gráfico ``` **Escenario 8: Ver matriz de correlación** ```gherkin DADO que hay múltiples símbolos en comparación CUANDO se calcula la correlación ENTONCES se muestra matriz con coeficientes de correlación Y valores van de -1.0 (correlación negativa) a +1.0 (positiva) Y diagonal siempre es 1.00 (símbolo consigo mismo) Ejemplo: BTC vs ETH = 0.87 (alta correlación positiva) ``` **Escenario 9: Cambiar fecha base** ```gherkin DADO que el usuario está en modo normalizado CUANDO cambia la fecha base a 2025-11-15 ENTONCES todos los índices se recalculan Y el índice 100 ahora está en 2025-11-15 Y los cambios relativos se ajustan ``` **Escenario 10: Límite de símbolos** ```gherkin DADO que el usuario tiene 5 símbolos en comparación (límite) CUANDO intenta agregar un sexto ENTONCES se muestra mensaje "Maximum 5 symbols for comparison" Y debe eliminar uno para agregar otro ``` ## Criterios Adicionales - [ ] Colores distintivos automáticos para cada símbolo - [ ] Tooltip sincronizado mostrando todos los valores - [ ] Exportar comparación como imagen - [ ] Guardar configuración de comparación - [ ] Templates de comparación (ej: "Major Crypto", "DeFi Tokens") --- ## Tareas Técnicas **Database:** - [ ] DB-TRD-026: Crear tabla trading.comparison_presets - Campos: id, user_id, name, symbols, view_mode, base_date **Backend:** - [ ] BE-TRD-096: Crear endpoint GET /trading/candles/multi - [ ] BE-TRD-097: Implementar normalización de precios - [ ] BE-TRD-098: Implementar cálculo de correlación - [ ] BE-TRD-099: Optimizar queries para múltiples símbolos - [ ] BE-TRD-100: Crear endpoint para guardar comparación **Frontend:** - [ ] FE-TRD-102: Crear componente CompareMode.tsx - [ ] FE-TRD-103: Crear componente SymbolSelector.tsx - [ ] FE-TRD-104: Crear componente SymbolsList.tsx - [ ] FE-TRD-105: Crear componente CorrelationMatrix.tsx - [ ] FE-TRD-106: Implementar normalización en frontend - [ ] FE-TRD-107: Implementar múltiples series en Lightweight Charts - [ ] FE-TRD-108: Implementar tooltip sincronizado - [ ] FE-TRD-109: Implementar hook useCompare **Tests:** - [ ] TEST-TRD-049: Test unitario normalización - [ ] TEST-TRD-050: Test unitario correlación - [ ] TEST-TRD-051: Test integración múltiples símbolos - [ ] TEST-TRD-052: Test E2E modo comparación completo --- ## Dependencias **Depende de:** - [ ] US-TRD-001: Ver chart - Estado: Pendiente **Bloquea:** - Ninguna --- ## Notas Técnicas **Endpoints involucrados:** | Método | Endpoint | Descripción | |--------|----------|-------------| | GET | /trading/candles/multi | Obtener velas de múltiples símbolos | | POST | /trading/compare/correlation | Calcular correlación | | POST | /trading/compare/presets | Guardar preset de comparación | **Componentes UI:** - `CompareMode`: Modo de comparación principal - `SymbolSelector`: Selector de símbolos - `SymbolsList`: Lista de símbolos activos - `CorrelationMatrix`: Matriz de correlaciones - `ViewModeSelector`: Selector de modo de vista **Query Parameters (Multi Candles):** ```typescript { symbols: ["BTCUSDT", "ETHUSDT", "SOLUSDT"], interval: "1h", from: "2025-11-01", to: "2025-12-05" } ``` **Response (Multi Candles):** ```typescript { data: { "BTCUSDT": [ { time: 1699027200, open: 90000, high: 91000, low: 89500, close: 90500, volume: 1234 }, // ... más velas ], "ETHUSDT": [ { time: 1699027200, open: 3600, high: 3650, low: 3580, close: 3620, volume: 5678 }, // ... más velas ], "SOLUSDT": [ { time: 1699027200, open: 140, high: 145, low: 138, close: 142, volume: 9012 }, // ... más velas ] } } ``` **Normalization Logic:** ```typescript function normalizeToIndex(candles: Candle[], baseDate: Date) { const baseCandle = candles.find(c => c.time === baseDate.getTime() / 1000); if (!baseCandle) return candles; const basePrice = baseCandle.close; return candles.map(candle => ({ ...candle, indexValue: (candle.close / basePrice) * 100, percentChange: ((candle.close - basePrice) / basePrice) * 100 })); } ``` **Correlation Calculation:** ```typescript function calculateCorrelation(series1: number[], series2: number[]): number { const n = Math.min(series1.length, series2.length); // Calculate means const mean1 = series1.reduce((a, b) => a + b, 0) / n; const mean2 = series2.reduce((a, b) => a + b, 0) / n; // Calculate correlation coefficient let numerator = 0; let sum1Sq = 0; let sum2Sq = 0; for (let i = 0; i < n; i++) { const diff1 = series1[i] - mean1; const diff2 = series2[i] - mean2; numerator += diff1 * diff2; sum1Sq += diff1 * diff1; sum2Sq += diff2 * diff2; } const denominator = Math.sqrt(sum1Sq * sum2Sq); return denominator === 0 ? 0 : numerator / denominator; } function calculateCorrelationMatrix(symbols: string[], data: CandleData) { const matrix: { [key: string]: { [key: string]: number } } = {}; symbols.forEach(symbol1 => { matrix[symbol1] = {}; symbols.forEach(symbol2 => { if (symbol1 === symbol2) { matrix[symbol1][symbol2] = 1.0; } else { const series1 = data[symbol1].map(c => c.close); const series2 = data[symbol2].map(c => c.close); matrix[symbol1][symbol2] = calculateCorrelation(series1, series2); } }); }); return matrix; } ``` **Chart Implementation (Lightweight Charts):** ```typescript // Add multiple series const colors = ['#2962FF', '#FF6D00', '#26A69A', '#9C27B0', '#F44336']; symbols.forEach((symbol, index) => { const series = chart.addLineSeries({ color: colors[index], lineWidth: 2, title: symbol, priceScaleId: viewMode === 'absolute' ? `scale-${index}` : 'right', }); const normalizedData = normalizeToIndex(data[symbol], baseDate); series.setData(normalizedData.map(candle => ({ time: candle.time, value: viewMode === 'normalized' ? candle.indexValue : candle.close }))); symbolSeries[symbol] = series; }); // Configure price scales for absolute mode if (viewMode === 'absolute') { symbols.forEach((symbol, index) => { chart.priceScale(`scale-${index}`).applyOptions({ scaleMargins: { top: 0.1 + (index * 0.3), bottom: 0.7 - (index * 0.3), }, borderColor: colors[index], }); }); } ``` **Synchronized Crosshair:** ```typescript function setupSyncedCrosshair(chart, symbols, data) { chart.subscribeCrosshairMove((param) => { if (!param.time) { tooltip.style.display = 'none'; return; } const tooltipContent = symbols.map(symbol => { const price = param.seriesPrices.get(symbolSeries[symbol]); return `${symbol}: ${price?.toFixed(2) || 'N/A'}`; }).join('\n'); tooltip.textContent = tooltipContent; tooltip.style.display = 'block'; }); } ``` **Color Palette:** ```typescript const SYMBOL_COLORS = [ '#2962FF', // Blue '#FF6D00', // Orange '#26A69A', // Teal '#9C27B0', // Purple '#F44336' // Red ]; ``` --- ## Definition of Ready (DoR) - [x] Historia claramente escrita - [x] Criterios de aceptación definidos - [x] Story points estimados - [x] Dependencias identificadas - [x] Sin bloqueadores - [ ] Diseño/mockup disponible - [ ] API spec disponible ## Definition of Done (DoD) - [ ] Código implementado según criterios - [ ] Tests unitarios escritos y pasando - [ ] Tests de integración pasando - [ ] Code review aprobado - [ ] Documentación actualizada - [ ] QA aprobado - [ ] Desplegado en ambiente de pruebas --- ## Historial de Cambios | Fecha | Cambio | Autor | |-------|--------|-------| | 2025-12-05 | Creación | Requirements-Analyst | --- **Creada por:** Requirements-Analyst **Fecha:** 2025-12-05 **Última actualización:** 2025-12-05