456 lines
16 KiB
Markdown
456 lines
16 KiB
Markdown
# 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
|