# ET-TRD-009: Risk-Based Position Sizer **Versión:** 1.0.0 **Fecha:** 2026-01-25 **Epic:** OQI-003 - Trading y Charts **Componente:** Frontend Component **Estado:** ✅ Implementado (documentación retroactiva) **Prioridad:** P3 --- ## Metadata | Campo | Valor | |-------|-------| | **ID** | ET-TRD-009 | | **Tipo** | Especificación Técnica | | **Epic** | OQI-003 | | **US Relacionada** | US-TRD-009 (Calcular Position Size Basado en Riesgo) | | **Componente Frontend** | `RiskBasedPositionSizer.tsx` | | **Líneas de Código** | 391 | | **Complejidad** | Baja (cálculos matemáticos simples) | --- ## 1. Descripción General **Risk-Based Position Sizer** es una calculadora avanzada de gestión de riesgo que determina automáticamente el tamaño óptimo de una posición (lot size) basándose en: - Saldo de cuenta - Porcentaje de riesgo aceptable - Distancia al Stop Loss (en pips) - Valor del pip según el instrumento Utiliza la fórmula estándar de position sizing: ``` Lot Size = (Account Balance × Risk %) / (Stop Loss Pips × Pip Value) ``` ### Beneficios ✅ **Protección de Capital:** Limita pérdidas a un % predefinido del saldo ✅ **Consistencia:** Mismo nivel de riesgo en todas las operaciones ✅ **Psicología:** Elimina decisiones emocionales sobre tamaño de posición ✅ **Risk/Reward:** Calcula ratios automáticamente para evaluar trades ✅ **Escenarios Múltiples:** Compara 0.5%, 1%, 2%, 5% de riesgo simultáneamente --- ## 2. Arquitectura y Fórmulas ### 2.1 Diagrama de Flujo ``` ┌────────────────────────────────────────────────────────────┐ │ Risk-Based Position Sizer Flow │ ├────────────────────────────────────────────────────────────┤ │ │ │ User Inputs: │ │ ┌─────────────────────────────────────────────┐ │ │ │ • Account Balance: $10,000 │ │ │ │ • Risk Percent: 1% │ │ │ │ • Entry Price: 1.08500 │ │ │ │ • Stop Loss: 1.08300 │ │ │ │ • Take Profit: 1.08900 │ │ │ │ • Trade Type: BUY │ │ │ └─────────────────┬───────────────────────────┘ │ │ │ │ │ v │ │ ┌─────────────────────────────────────────────┐ │ │ │ Step 1: Calculate SL Distance (pips) │ │ │ │ slPips = |entry - sl| / pipValue │ │ │ │ slPips = |1.08500 - 1.08300| / 0.0001 │ │ │ │ slPips = 20 pips │ │ │ └─────────────────┬───────────────────────────┘ │ │ │ │ │ v │ │ ┌─────────────────────────────────────────────┐ │ │ │ Step 2: Calculate Risk Amount │ │ │ │ riskAmount = balance × riskPercent / 100 │ │ │ │ riskAmount = $10,000 × 1 / 100 │ │ │ │ riskAmount = $100 │ │ │ └─────────────────┬───────────────────────────┘ │ │ │ │ │ v │ │ ┌─────────────────────────────────────────────┐ │ │ │ Step 3: Calculate Lot Size │ │ │ │ lots = riskAmount / (slPips × pipValueUsd) │ │ │ │ lots = $100 / (20 × $10) │ │ │ │ lots = 0.50 │ │ │ └─────────────────┬───────────────────────────┘ │ │ │ │ │ v │ │ ┌─────────────────────────────────────────────┐ │ │ │ Step 4: Calculate Potential Profit │ │ │ │ tpPips = |tp - entry| / pipValue │ │ │ │ tpPips = |1.08900 - 1.08500| / 0.0001 │ │ │ │ tpPips = 40 pips │ │ │ │ profit = lots × tpPips × pipValueUsd │ │ │ │ profit = 0.50 × 40 × $10 = $200 │ │ │ └─────────────────┬───────────────────────────┘ │ │ │ │ │ v │ │ ┌─────────────────────────────────────────────┐ │ │ │ Step 5: Calculate R:R Ratio │ │ │ │ rr = profit / potentialLoss │ │ │ │ rr = $200 / $100 = 2.0 │ │ │ │ Display: 1:2.0 │ │ │ └─────────────────┬───────────────────────────┘ │ │ │ │ │ v │ │ ┌─────────────────────────────────────────────┐ │ │ │ Output: │ │ │ │ • Lot Size: 0.50 │ │ │ │ • Risk: $100.00 │ │ │ │ • Max Loss: $100.00 │ │ │ │ • Potential Profit: $200.00 │ │ │ │ • R:R Ratio: 1:2.0 │ │ │ └─────────────────────────────────────────────┘ │ │ │ └────────────────────────────────────────────────────────────┘ ``` ### 2.2 Fórmulas Matemáticas #### Formula 1: Pip Distance Calculation ```typescript const pipValue = symbol.includes('JPY') ? 0.01 : 0.0001; // SL distance in pips const slPips = Math.abs(entryPrice - stopLoss) / pipValue; // TP distance in pips const tpPips = takeProfit ? Math.abs(takeProfit - entryPrice) / pipValue : 0; ``` **Ejemplos:** - EURUSD (entry: 1.08500, SL: 1.08300) → 20 pips - USDJPY (entry: 149.50, SL: 149.00) → 50 pips - GBPUSD (entry: 1.25800, SL: 1.25600) → 20 pips #### Formula 2: Risk Amount ```typescript const riskAmount = (accountBalance * riskPercent) / 100; ``` **Ejemplos:** - Balance: $10,000 @ 1% → $100 risk - Balance: $50,000 @ 2% → $1,000 risk - Balance: $5,000 @ 0.5% → $25 risk #### Formula 3: Lot Size (Position Size) ```typescript const pipValueUsd = 10; // $10 per pip per standard lot for majors const lots = riskAmount / (slPips * pipValueUsd); const roundedLots = Math.floor(lots * 100) / 100; // Round down to 0.01 ``` **Ejemplos:** - Risk: $100 / (20 pips × $10) → 0.50 lots - Risk: $1,000 / (50 pips × $10) → 2.00 lots - Risk: $25 / (10 pips × $10) → 0.25 lots **Important:** Always round DOWN to prevent exceeding risk tolerance #### Formula 4: Potential Loss (Verification) ```typescript const potentialLoss = roundedLots * slPips * pipValueUsd; ``` This should equal `riskAmount` (or slightly less due to rounding) #### Formula 5: Potential Profit ```typescript const potentialProfit = roundedLots * tpPips * pipValueUsd; ``` #### Formula 6: Risk/Reward Ratio ```typescript const riskRewardRatio = potentialProfit / potentialLoss; ``` **Interpretation:** - R:R = 2.0 → For every $1 risked, gain $2 (1:2 ratio) ✅ Good - R:R = 1.0 → Equal risk/reward (1:1 ratio) ⚠️ Borderline - R:R = 0.5 → Risk $2 to gain $1 (1:0.5 ratio) ❌ Bad trade --- ## 3. Component Interface ### 3.1 Props ```typescript interface RiskBasedPositionSizerProps { accountBalance?: number; // Default: 10000 defaultSymbol?: string; // Default: 'EURUSD' defaultRiskPercent?: number; // Default: 1 onCalculate?: (result: CalculationResult) => void; // Callback on calc onApplyToOrder?: (lots: number) => void; // Apply to order form compact?: boolean; // Default: false } interface CalculationResult { lots: number; // Calculated position size riskAmount: number; // Dollar amount at risk potentialLoss: number; // Max loss (≈ riskAmount) potentialProfit: number; // Potential gain (if TP hit) riskRewardRatio: number; // R:R ratio pipValue: number; // Dollar value per pip for this lot size slPips: number; // Distance to SL in pips tpPips: number; // Distance to TP in pips } ``` ### 3.2 State Variables ```typescript const [balance, setBalance] = useState('10000'); const [riskPercent, setRiskPercent] = useState('1'); const [symbol, setSymbol] = useState('EURUSD'); const [entryPrice, setEntryPrice] = useState(''); const [stopLoss, setStopLoss] = useState(''); const [takeProfit, setTakeProfit] = useState(''); const [tradeType, setTradeType] = useState<'BUY' | 'SELL'>('BUY'); const [copied, setCopied] = useState(false); ``` --- ## 4. Features Implementadas ### 4.1 Input Controls | Campo | Tipo | Validación | Descripción | |-------|------|------------|-------------| | **Account Balance** | Number | > 0 | Saldo de cuenta en USD | | **Risk Percent** | Number | 0.1 - 10% | Porcentaje de riesgo por trade | | **Trade Direction** | BUY/SELL | Required | Tipo de operación | | **Entry Price** | Number | > 0 | Precio de entrada | | **Stop Loss** | Number | Validate logic | SL debe estar "contra" la dirección | | **Take Profit** | Number | Optional | TP (opcional) | **Validación de Stop Loss:** - BUY trades: SL < Entry Price - SELL trades: SL > Entry Price - Si inválido → Muestra warning rojo ### 4.2 Quick Risk Presets Botones rápidos para seleccionar niveles de riesgo comunes: ```typescript {[0.5, 1, 2, 3].map((pct) => ( ))} ``` - 0.5% → Conservative (profesionales) - 1% → Standard (recomendado) - 2% → Aggressive - 3% → Very aggressive (no recomendado) ### 4.3 Calculation Result Panel Muestra en tiempo real: ``` ┌─────────────────────────────────────────┐ │ Recommended Position Size │ │ │ │ 0.50 lots 📋 [Copy] │ │ │ │ ────────────────────────────────────── │ │ Risk Amount Max Loss │ │ $100.00 $100.00 │ │ │ │ Potential Profit R:R Ratio │ │ $200.00 1:2.00 │ │ ────────────────────────────────────── │ │ ℹ SL: 20.0 pips • Pip value: $5.00 │ │ │ │ [Apply to Order] │ └─────────────────────────────────────────┘ ``` ### 4.4 Risk Scenarios Matrix Muestra 4 escenarios simultáneos (0.5%, 1%, 2%, 5%): ``` ┌─────┬─────┬─────┬─────┐ │ 0.5%│ 1% │ 2% │ 5% │ │0.25 │0.50 │1.00 │2.50 │ │ $50 │$100 │$200 │$500 │ └─────┴─────┴─────┴─────┘ ``` Click en cualquier escenario → cambia risk% automáticamente ### 4.5 Copy to Clipboard Botón "Copy" copia lot size al portapapeles: ```typescript const handleCopyLots = async () => { await navigator.clipboard.writeText(calculation.lots.toFixed(2)); setCopied(true); setTimeout(() => setCopied(false), 2000); }; ``` Muestra checkmark verde por 2 segundos ### 4.6 Apply to Order Callback para integrar con formularios de orden: ```typescript const handleApply = () => { if (calculation && onApplyToOrder) { onApplyToOrder(calculation.lots); } }; ``` --- ## 5. Cálculos Avanzados ### 5.1 Pip Value Detection Detecta automáticamente el valor del pip según el par: ```typescript const pipValue = symbol.includes('JPY') ? 0.01 : 0.0001; ``` - **Major pairs (EURUSD, GBPUSD):** 0.0001 (4 decimales) - **JPY pairs (USDJPY, EURJPY):** 0.01 (2 decimales) ### 5.2 Pip Value USD (Approximation) Asume $10 USD per pip per lot para pares mayores: ```typescript const pipValueUsd = 10; // Approximate for majors ``` **Real pip values (1 standard lot):** - EURUSD: $10/pip - GBPUSD: $10/pip - USDJPY: ~$9.09/pip (variable) - AUDUSD: $10/pip **Mejora futura:** API para obtener pip values reales dinámicamente ### 5.3 Lot Size Rounding Redondea HACIA ABAJO para no exceder riesgo: ```typescript const roundedLots = Math.floor(lots * 100) / 100; ``` **Ejemplos:** - 0.5678 → 0.56 lots - 1.2345 → 1.23 lots - 0.0199 → 0.01 lots (minimum tradeable) **Micro lots (0.01) son el tamaño mínimo** --- ## 6. Validaciones ### 6.1 Input Validation ```typescript const calculation = useMemo(() => { const balanceNum = parseFloat(balance) || 0; const riskPercentNum = parseFloat(riskPercent) || 0; const entry = parseFloat(entryPrice) || 0; const sl = parseFloat(stopLoss) || 0; const tp = parseFloat(takeProfit) || 0; // Required fields if (!balanceNum || !riskPercentNum || !entry || !sl) { return null; } // ... calculations }, [balance, riskPercent, entryPrice, stopLoss, takeProfit, symbol]); ``` ### 6.2 Stop Loss Logic Validation ```typescript const isValidSL = () => { const entry = parseFloat(entryPrice) || 0; const sl = parseFloat(stopLoss) || 0; if (!entry || !sl) return true; if (tradeType === 'BUY') { return sl < entry; // SL must be below entry for BUY } else { return sl > entry; // SL must be above entry for SELL } }; ``` **UI Feedback:** - Invalid SL → Red border + warning message - Blocks calculation until fixed --- ## 7. UI Components ### 7.1 Account Balance Input ```tsx
setBalance(e.target.value)} className="w-full pl-10 pr-4 py-2.5 bg-gray-900 border border-gray-700 rounded-lg" />
``` ### 7.2 Risk Percent Slider + Buttons ```tsx
{[0.5, 1, 2, 3].map((pct) => ( ))}
``` ### 7.3 BUY/SELL Toggle ```tsx
``` ### 7.4 Price Inputs (Entry, SL, TP) ```tsx
``` ### 7.5 Result Card ```tsx
Recommended Position Size
{calculation.lots.toFixed(2)} lots
Risk Amount
${calculation.riskAmount.toFixed(2)}
Max Loss
${calculation.potentialLoss.toFixed(2)}
Potential Profit
${calculation.potentialProfit.toFixed(2)}
R:R Ratio
1:{calculation.riskRewardRatio.toFixed(2)}
``` --- ## 8. Uso e Integración ### 8.1 Standalone Mode ```tsx import RiskBasedPositionSizer from '@/modules/trading/components/RiskBasedPositionSizer'; function TradingPage() { return (
{ console.log('Calculated lot size:', result.lots); }} />
); } ``` ### 8.2 Integration with Order Form ```tsx import RiskBasedPositionSizer from '@/modules/trading/components/RiskBasedPositionSizer'; import OrderForm from '@/modules/trading/components/OrderForm'; function TradingDashboard() { const [calculatedLots, setCalculatedLots] = useState(null); return (
{/* Position Sizer */} { setCalculatedLots(lots); // Automatically fill order form }} /> {/* Order Form */}
); } ``` ### 8.3 Compact Mode (Sidebar) ```tsx ``` --- ## 9. Mejoras Futuras ### 9.1 Dynamic Pip Values (API Integration) Actualmente usa $10 aproximado. Mejorar con API real: ```typescript // Fetch real-time pip values const pipValueUsd = await tradingService.getPipValue(symbol, accountCurrency); ``` ### 9.2 Multi-Currency Support Soportar cuentas en EUR, GBP, AUD: ```typescript interface Props { accountCurrency?: 'USD' | 'EUR' | 'GBP'; } const convertedRisk = riskAmount * conversionRate; ``` ### 9.3 Advanced Risk Models - **Kelly Criterion:** Optimal position sizing basado en win rate - **Fixed Fractional:** Basado en equity drawdown - **Volatility-Based:** Ajustar según ATR/volatilidad ### 9.4 Presets Persistence Guardar configuraciones favoritas: ```typescript const savePreset = async (name: string) => { await apiClient.post('/api/risk-presets', { name, risk_percent: riskPercent, default_symbol: symbol }); }; ``` ### 9.5 Risk Analytics Dashboard de gestión de riesgo: - Total risk exposure (todas las posiciones abiertas) - Daily risk limit tracker - Risk heatmap por símbolo - Historical risk performance --- ## 10. Testing Scenarios ### Manual Test Cases 1. **Basic Calculation:** - Balance: $10,000 - Risk: 1% - Entry: 1.08500 - SL: 1.08300 (20 pips) - **Expected:** 0.50 lots, $100 risk 2. **High Risk:** - Balance: $50,000 - Risk: 5% - Entry: 1.25800 - SL: 1.25300 (50 pips) - **Expected:** 5.00 lots, $2,500 risk 3. **Invalid SL (BUY):** - Entry: 1.08500 - SL: 1.08700 (above entry) - **Expected:** Red warning, no calculation 4. **Invalid SL (SELL):** - Entry: 1.08500 - SL: 1.08300 (below entry) - **Expected:** Red warning, no calculation 5. **No Take Profit:** - Entry: 1.08500 - SL: 1.08300 - TP: (empty) - **Expected:** Calculation works, R:R = 0 6. **JPY Pair:** - Symbol: USDJPY - Entry: 149.50 - SL: 149.00 (50 pips) - **Expected:** Correct pip value (0.01) --- ## 11. Referencias - **Componente:** `apps/frontend/src/modules/trading/components/RiskBasedPositionSizer.tsx` (391 líneas) - **Integración:** `AdvancedOrderEntry.tsx`, `QuickOrderPanel.tsx` - **US:** `US-TRD-009-risk-based-position-sizer.md` - **Formula Reference:** [Babypips Position Size Calculator](https://www.babypips.com/tools/position-size-calculator) --- **Última actualización:** 2026-01-25 **Responsable:** Frontend Lead