--- id: "US-TRD-015" title: "Exportar Historial de Trades a CSV" type: "User Story" status: "Done" priority: "Media" epic: "OQI-003" story_points: 3 created_date: "2025-12-05" updated_date: "2026-01-04" --- # US-TRD-015: Exportar Historial de Trades a CSV ## Metadata | Campo | Valor | |-------|-------| | **ID** | US-TRD-015 | | **Épica** | OQI-003 - Trading y Charts | | **Módulo** | trading | | **Prioridad** | P2 | | **Story Points** | 2 | | **Sprint** | Sprint 6 | | **Estado** | Pendiente | | **Asignado a** | Por asignar | --- ## Historia de Usuario **Como** trader practicante, **quiero** exportar mi historial de trades a formato CSV, **para** analizarlo en Excel u otras herramientas externas y llevar un registro personal. ## Descripción Detallada El usuario debe poder descargar su historial completo de trades (o filtrado) en formato CSV, incluyendo todos los detalles relevantes: fechas, símbolos, precios, P&L, duración, etc. Esto permite análisis externo y mantener registros personales. ## Mockups/Wireframes ``` ┌─────────────────────────────────────────────────────────────────┐ │ TRADE HISTORY │ ├─────────────────────────────────────────────────────────────────┤ │ Filters: │ │ Date Range: [Last 30 days ▼] Symbol: [All ▼] Side: [All ▼] │ │ Result: [All ▼] [🔍 Search] [📥 Export CSV] │ │ └─────────────┘ │ │ │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ EXPORT TO CSV │ │ │ ├────────────────────────────────────────────────────────────┤ │ │ │ │ │ │ │ Export Options: │ │ │ │ │ │ │ │ Date Range: │ │ │ │ ○ Current filter (Last 30 days - 45 trades) │ │ │ │ ○ Custom range │ │ │ │ From: [2025-11-01] To: [2025-12-05] │ │ │ │ ○ All time (127 trades) │ │ │ │ │ │ │ │ Include Columns: │ │ │ │ [✓] Trade ID │ │ │ │ [✓] Symbol │ │ │ │ [✓] Side (Long/Short) │ │ │ │ [✓] Entry Price │ │ │ │ [✓] Exit Price │ │ │ │ [✓] Quantity │ │ │ │ [✓] P&L (USD) │ │ │ │ [✓] P&L (%) │ │ │ │ [✓] Opened At │ │ │ │ [✓] Closed At │ │ │ │ [✓] Duration │ │ │ │ [✓] Close Reason │ │ │ │ [ ] Entry Order ID │ │ │ │ [ ] Exit Order ID │ │ │ │ │ │ │ │ Format: │ │ │ │ ○ CSV (Comma-separated) │ │ │ │ ○ CSV (Semicolon-separated - Excel Europe) │ │ │ │ ○ TSV (Tab-separated) │ │ │ │ │ │ │ │ File name: │ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ │ │ trades_2025-11-05_to_2025-12-05.csv │ │ │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ Estimated file size: ~15 KB (45 trades) │ │ │ │ │ │ │ │ [Cancel] [Download CSV] │ │ │ └────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ``` --- ## Criterios de Aceptación **Escenario 1: Exportar con filtros actuales** ```gherkin DADO que el usuario tiene filtro "Last 30 days" activo Y hay 45 trades en ese rango CUANDO hace click en "Export CSV" Y selecciona "Current filter" Y hace click en "Download CSV" ENTONCES se descarga archivo trades_2025-11-05_to_2025-12-05.csv Y contiene 45 trades + 1 línea de headers Y las columnas corresponden a las seleccionadas ``` **Escenario 2: Exportar rango personalizado** ```gherkin DADO que el usuario abre el diálogo de export CUANDO selecciona "Custom range" Y elige From: 2025-10-01, To: 2025-10-31 Y hay 23 trades en octubre Y hace click en "Download CSV" ENTONCES se descarga archivo con 23 trades de octubre Y el nombre refleja las fechas: trades_2025-10-01_to_2025-10-31.csv ``` **Escenario 3: Exportar todo el historial** ```gherkin DADO que el usuario tiene 127 trades en total CUANDO selecciona "All time" Y descarga ENTONCES se exportan los 127 trades Y el archivo se llama trades_all_time.csv ``` **Escenario 4: Seleccionar columnas específicas** ```gherkin DADO que el usuario abre opciones de export CUANDO desmarca "Entry Order ID" y "Exit Order ID" Y solo deja las columnas principales marcadas Y descarga ENTONCES el CSV contiene solo las columnas seleccionadas Y no incluye las columnas desmarcadas ``` **Escenario 5: Formato separador para Excel Europa** ```gherkin DADO que el usuario usa Excel con configuración europea CUANDO selecciona formato "CSV (Semicolon-separated)" Y descarga ENTONCES el archivo usa punto y coma como separador Y los decimales usan coma (ej: 123,45 en lugar de 123.45) ``` **Escenario 6: Validar contenido del CSV** ```gherkin DADO que se exporta un archivo CSV CUANDO se abre el archivo ENTONCES contiene: - Primera línea: Headers de columnas - Líneas siguientes: Datos de trades - Formato de fechas: YYYY-MM-DD HH:MM:SS - Formato de precios: Con 2 decimales - Formato de P&L: Con signo +/- y 2 decimales - Sin errores de encoding (UTF-8) ``` ## Criterios Adicionales - [ ] Máximo 1000 trades por exportación - [ ] Progreso de descarga para archivos grandes - [ ] Opción de incluir resumen estadístico al final - [ ] Exportar también a JSON (futuro) - [ ] Exportar también a Excel XLSX (futuro) --- ## Tareas Técnicas **Database:** - No requiere cambios en DB **Backend:** - [ ] BE-TRD-086: Crear endpoint GET /trading/paper/trades/export/csv - [ ] BE-TRD-087: Implementar ExportService.generateCSV() - [ ] BE-TRD-088: Implementar diferentes formatos (comma, semicolon, tab) - [ ] BE-TRD-089: Implementar conversión de decimales según formato - [ ] BE-TRD-090: Optimizar queries para exportaciones grandes - [ ] BE-TRD-091: Implementar streaming para archivos grandes **Frontend:** - [ ] FE-TRD-081: Crear componente ExportCSVDialog.tsx - [ ] FE-TRD-082: Crear componente ColumnSelector.tsx - [ ] FE-TRD-083: Crear componente FormatSelector.tsx - [ ] FE-TRD-084: Implementar hook useExportTrades - [ ] FE-TRD-085: Implementar descarga de archivo blob **Tests:** - [ ] TEST-TRD-040: Test unitario generación CSV - [ ] TEST-TRD-041: Test integración export endpoint - [ ] TEST-TRD-042: Test E2E descarga archivo --- ## Dependencias **Depende de:** - [ ] US-TRD-010: Ver historial - Estado: Pendiente **Bloquea:** - Ninguna --- ## Notas Técnicas **Endpoints involucrados:** | Método | Endpoint | Descripción | |--------|----------|-------------| | GET | /trading/paper/trades/export/csv | Exportar CSV | | GET | /trading/paper/trades/export/json | Exportar JSON (futuro) | **Componentes UI:** - `ExportCSVDialog`: Modal de opciones de export - `ColumnSelector`: Checklist de columnas - `FormatSelector`: Radio buttons de formato - `DateRangePicker`: Selector de rango **Query Parameters:** ```typescript { dateFrom: "2025-11-05", dateTo: "2025-12-05", symbol: "BTCUSDT", // opcional side: "long", // opcional result: "win", // opcional columns: ["id", "symbol", "side", "entry_price", "exit_price", "quantity", "pnl", "pnl_percentage", "opened_at", "closed_at", "duration", "close_reason"], format: "csv", // csv, csv-semicolon, tsv locale: "en-US" // en-US, es-ES, etc. } ``` **CSV Headers:** ```csv Trade ID,Symbol,Side,Entry Price,Exit Price,Quantity,P&L (USD),P&L (%),Opened At,Closed At,Duration,Close Reason ``` **CSV Example (Comma-separated, en-US):** ```csv Trade ID,Symbol,Side,Entry Price,Exit Price,Quantity,P&L (USD),P&L (%),Opened At,Closed At,Duration,Close Reason 550e8400-e29b-41d4-a716-446655440000,BTCUSDT,long,95000.00,97234.50,0.10,+223.45,+2.35,2025-12-05 08:00:00,2025-12-05 10:30:00,2h 30m,manual 660e8400-e29b-41d4-a716-446655440001,ETHUSDT,long,3800.00,3750.00,2.50,-125.00,-1.32,2025-12-04 14:15:00,2025-12-04 19:35:00,5h 20m,stop_loss 770e8400-e29b-41d4-a716-446655440002,SOLUSDT,short,145.00,142.73,10.00,+22.70,+1.56,2025-12-03 09:00:00,2025-12-04 12:45:00,1d 3h 45m,take_profit ``` **CSV Example (Semicolon-separated, es-ES):** ```csv Trade ID;Symbol;Side;Entry Price;Exit Price;Quantity;P&L (USD);P&L (%);Opened At;Closed At;Duration;Close Reason 550e8400-e29b-41d4-a716-446655440000;BTCUSDT;long;95000,00;97234,50;0,10;+223,45;+2,35;2025-12-05 08:00:00;2025-12-05 10:30:00;2h 30m;manual ``` **Backend Implementation:** ```typescript async function generateCSV(params) { const { dateFrom, dateTo, columns, format, locale } = params; // Obtener trades const trades = await getTradesForExport(params); // Configuración según formato const config = { delimiter: format === 'csv-semicolon' ? ';' : (format === 'tsv' ? '\t' : ','), decimalSeparator: locale.startsWith('es') ? ',' : '.', dateFormat: 'YYYY-MM-DD HH:mm:ss' }; // Headers const headers = columns.map(col => COLUMN_LABELS[col]); let csv = headers.join(config.delimiter) + '\n'; // Data rows for (const trade of trades) { const row = columns.map(col => { const value = formatValue(trade[col], col, config); return escapeCSV(value, config.delimiter); }); csv += row.join(config.delimiter) + '\n'; } return csv; } function formatValue(value, column, config) { if (value === null || value === undefined) return ''; switch (column) { case 'pnl': case 'pnl_percentage': const sign = value >= 0 ? '+' : ''; const formatted = value.toFixed(2); return sign + (config.decimalSeparator === ',' ? formatted.replace('.', ',') : formatted); case 'entry_price': case 'exit_price': return value.toFixed(2).replace('.', config.decimalSeparator); case 'opened_at': case 'closed_at': return moment(value).format(config.dateFormat); case 'duration': return formatDuration(value); default: return String(value); } } function escapeCSV(value, delimiter) { const str = String(value); if (str.includes(delimiter) || str.includes('"') || str.includes('\n')) { return '"' + str.replace(/"/g, '""') + '"'; } return str; } ``` **Frontend Download:** ```typescript async function downloadCSV(params) { // Request CSV from backend const response = await fetch('/trading/paper/trades/export/csv?' + new URLSearchParams(params)); const blob = await response.blob(); // Create download link const url = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = generateFilename(params); document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(url); } function generateFilename(params) { const { dateFrom, dateTo } = params; if (dateFrom && dateTo) { return `trades_${dateFrom}_to_${dateTo}.csv`; } return `trades_all_time.csv`; } ``` **Column Labels:** ```typescript const COLUMN_LABELS = { id: 'Trade ID', symbol: 'Symbol', side: 'Side', entry_price: 'Entry Price', exit_price: 'Exit Price', quantity: 'Quantity', pnl: 'P&L (USD)', pnl_percentage: 'P&L (%)', opened_at: 'Opened At', closed_at: 'Closed At', duration_seconds: 'Duration', close_reason: 'Close Reason', entry_order_id: 'Entry Order ID', exit_order_id: 'Exit Order ID' }; ``` --- ## 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