Backtest de cartera (2023-01-03 → 2026-09-11)
Estrategia: sma_cross · Selector: fuerza de tendencia (cierre / SMA200 − 1)
Parámetros: {'fast': 20, 'slow': 50, 'trend': 200, 'atr_period': 14, 'stop_atr_mult': 2.0, 'take_profit_atr_mult': 3.0, 'max_hold_days': 40, 'calendar_days': False, 'volume_filter_ratio': 0.8, 'entry_window': 5, 'exit_mode': 'take_profit'}
Universo (10): SPY QQQ AAPL MSFT NVDA AMZN GOOGL META JPM XOM · Benchmark: SPY
Métricas por modo de salida
| Símbolo | Periodo | Retorno | CAGR | Max DD | Sharpe | Trades | Win % | Expectancy | Exposición | Buy & Hold |
|---|---|---|---|---|---|---|---|---|---|---|
| PORTFOLIO-REAL2000-OOS (take_profit) | 2023-01-03 → 2026-09-11 | +6.9% | +1.8% | -1.4% | 1.18 | 39 | 59% | +2.21% | 39% | +100.6% |
La columna Buy & Hold es SPY en el mismo periodo.
Detalle take_profit
Operaciones cerradas: 39 · Máx. posiciones simultáneas: 4/5 · Señales descartadas por falta de huecos: 0 · por régimen: 0 · por hueco de apertura: 5 · Reglas de riesgo: 0 kills diarios, 0 días vetados por drawdown, 0 señales descartadas, 0 vetadas por anti-PDT (1 day trades) · Precios nominales: sí (0 posiciones atravesaron un split)
Riesgo comparable: SPY a la misma volatilidad (mezcla con efectivo, vol móvil 60 d): +7.0 % · IC 90 % del retorno anualizado (bootstrap por bloques): [+0.8 %, +3.1 %]
| Símbolo | Trades | Win % | P&L | Ret. medio |
|---|---|---|---|---|
| AAPL | 3 | 67% | +4.39 | +0.44% |
| AMZN | 6 | 50% | +8.67 | +0.81% |
| GOOGL | 6 | 83% | +38.18 | +4.82% |
| JPM | 5 | 60% | +10.71 | +1.36% |
| NVDA | 10 | 80% | +94.39 | +6.19% |
| XOM | 9 | 22% | -17.97 | -1.94% |
Por año y régimen take_profit
| Grupo | Ops | Win rate | P&L medio | P&L total | Ret. medio |
|---|---|---|---|---|---|
| 2023 | 15 | 53% | +2.20 | +32.94 | +1.24% |
| régimen bear | 1 | 0% | -5.46 | -5.46 | -4.80% |
| régimen bull | 38 | 61% | +3.78 | +143.82 | +2.40% |
| 2024 | 13 | 54% | +2.74 | +35.59 | +1.99% |
| 2025 | 5 | 80% | +8.68 | +43.38 | +5.11% |
| 2026 | 6 | 67% | +4.41 | +26.45 | +2.72% |
Últimas operaciones take_profit
| Entrada | Salida | Precio in | Precio out | Qty | P&L | Ret. | Motivo salida |
|---|---|---|---|---|---|---|---|
| 2023-11-15 | 2023-11-16 | 147.17 | 139.74 | 1 | -7.44 | -5.05% | bracket stop-loss |
| 2023-11-30 | 2023-12-04 | 135.23 | 130.12 | 1 | -5.11 | -3.78% | bracket stop-loss |
| 2023-11-28 | 2023-12-05 | 153.51 | 158.01 | 1 | +4.50 | +2.93% | bracket take-profit |
| 2023-12-05 | 2023-12-07 | 129.06 | 136.97 | 1 | +7.91 | +6.13% | bracket take-profit |
| 2023-11-15 | 2023-12-11 | 188.08 | 193.00 | 1 | +4.91 | +2.61% | take-profit: close 195.69 >= 195.65; SMA20=190.26 SMA50=181.48 SMA200=176.33 ATR=2.52 close=195.69 |
| 2023-11-17 | 2023-12-18 | 142.80 | 152.49 | 1 | +9.69 | +6.79% | bracket take-profit |
| 2024-04-03 | 2024-04-26 | 153.57 | 164.05 | 1 | +10.48 | +6.83% | bracket take-profit |
| 2024-05-17 | 2024-05-20 | 203.98 | 198.10 | 1 | -5.88 | -2.88% | bracket stop-loss |
| 2024-05-14 | 2024-06-06 | 187.87 | 195.62 | 1 | +7.75 | +4.13% | take-profit: close 195.87 >= 195.70; SMA20=189.65 SMA50=178.58 SMA200=181.54 ATR=2.61 close=195.87 |
| 2024-06-24 | 2024-06-27 | 189.56 | 198.97 | 1 | +9.41 | +4.96% | bracket take-profit |
| 2024-05-21 | 2024-07-02 | 197.08 | 207.10 | 1 | +10.02 | +5.08% | bracket take-profit |
| 2024-06-28 | 2024-07-17 | 197.70 | 189.50 | 1 | -8.20 | -4.15% | bracket stop-loss |
| 2024-07-25 | 2024-09-10 | 115.15 | 110.75 | 1 | -4.39 | -3.82% | bracket stop-loss |
| 2024-09-09 | 2024-09-20 | 104.98 | 116.96 | 1 | +11.98 | +11.41% | SMA20 cruzó bajo SMA50; SMA20=116.39 SMA50=116.42 SMA200=92.11 ATR=5.68 close=117.90 |
| 2024-09-20 | 2024-10-07 | 190.46 | 180.52 | 1 | -9.94 | -5.22% | bracket stop-loss |
| 2024-10-04 | 2024-10-14 | 124.99 | 138.92 | 1 | +13.93 | +11.14% | bracket take-profit |
| 2024-10-10 | 2024-10-29 | 161.05 | 170.06 | 1 | +9.01 | +5.60% | bracket take-profit |
| 2024-10-09 | 2024-11-01 | 120.96 | 115.29 | 1 | -5.67 | -4.69% | bracket stop-loss |
| 2024-12-04 | 2024-12-05 | 117.59 | 114.69 | 1 | -2.90 | -2.47% | SMA20 cruzó bajo SMA50; SMA20=119.59 SMA50=119.71 SMA200=115.98 ATR=1.88 close=114.29 |
| 2025-05-22 | 2025-06-10 | 201.54 | 216.69 | 1 | +15.15 | +7.52% | take-profit: close 216.94 >= 215.10; SMA20=206.78 SMA50=192.97 SMA200=202.40 ATR=4.52 close=216.94 |
| 2025-05-15 | 2025-06-20 | 134.37 | 145.33 | 1 | +10.96 | +8.16% | take-profit: close 145.49 >= 145.12; SMA20=139.66 SMA50=124.51 SMA200=127.91 ATR=3.59 close=145.49 |
| 2025-06-23 | 2025-06-23 | 116.76 | 112.20 | 1 | -4.56 | -3.90% | bracket stop-loss |
| 2025-09-11 | 2025-09-26 | 111.47 | 117.17 | 1 | +5.71 | +5.12% | bracket take-profit |
| 2025-10-07 | 2025-10-28 | 186.29 | 202.41 | 1 | +16.12 | +8.65% | bracket take-profit |
| 2026-01-16 | 2026-01-20 | 189.08 | 179.33 | 1 | -9.75 | -5.15% | bracket stop-loss |
| 2026-01-21 | 2026-01-30 | 179.15 | 194.22 | 1 | +15.07 | +8.41% | bracket take-profit |
| 2026-03-09 | 2026-03-11 | 176.90 | 185.83 | 1 | +8.92 | +5.04% | SMA20 cruzó bajo SMA50; SMA20=185.74 SMA50=185.78 SMA200=176.64 ATR=6.13 close=184.76 |
| 2026-04-24 | 2026-04-27 | 200.13 | 213.66 | 1 | +13.54 | +6.76% | bracket take-profit |
| 2026-04-29 | 2026-04-30 | 212.81 | 201.24 | 1 | -11.56 | -5.43% | bracket stop-loss |
| 2026-08-03 | 2026-08-18 | 153.53 | 163.76 | 1 | +10.23 | +6.66% | bracket take-profit |
Lectura honesta
- Sesgo de supervivencia. El universo son símbolos que hoy sabemos que sobrevivieron y subieron; sobre datos de 2019 en adelante eso infla el resultado. Compara siempre contra el benchmark y trata estas cifras como techo, no como expectativa.
- Este backtest de cartera es el que se compara con el paper: mismo capital compartido, mismo límite de posiciones, mismo sizing y mismo selector.
- Paridad con el bot: hueco de apertura > MAX_GAP_PCT descarta la entrada y stop/take-profit se desplazan con el precio de apertura, como en
engine/cycle.py. Lo que el backtest NO reproduce: la cancelación de entradas sin fill (aquí toda entrada se llena en la apertura), los fills parciales y la latencia real del cron. La regla anti-PDT SÍ se simula con--risk-rules(solo veta entradas, como en vivo). - Reglas de pérdida diaria y drawdown solo con
--risk-rules(el workflow automático las activa): con el histórico gratuito (desde 2020-07-27) nunca se disparan; se prueban con un crash sintético entests/test_kill_switch_system.py. - El objetivo de take-profit de la regla al cierre se recalcula cada día con el ATR actual (entrada + 3×ATR), mientras la pata bracket queda fija en la entrada: cuando la volatilidad baja, la regla al cierre vence antes. El bot hace exactamente lo mismo (docs/STRATEGY.md).
- El número de operaciones no es un umbral universal de validez: mira el intervalo de confianza del retorno anualizado (bootstrap por bloques, abajo) y recuerda que esta ventana participó en la selección de variantes.
Cómo se calcula
- Sharpe: retornos diarios de la equity de la cartera, tipo libre de riesgo 0, media ÷ desviación × √252. Sin remuneración del efectivo (la cuenta paper no paga intereses).
- Costes: comisión 0.00 % por lado + 0.00 USD por orden; slippage 0.050 % por lado sobre apertura, stop y objetivo.
- Drawdown máximo: caída desde el máximo previo de la equity diaria al cierre.
- Exposición: % de días con alguna posición abierta. Es tiempo invertido, no capital ni riesgo: no se usa como denominador de nada.
- Benchmark a la misma volatilidad: mezcla benchmark + efectivo cuyo peso sigue la volatilidad móvil de 60 días de la estrategia (solo datos pasados). Descriptivo.
- IC 90 % del retorno anualizado: bootstrap por bloques de 10 días sobre los retornos diarios (1 000 remuestreos). Conserva la dependencia dentro del bloque; NO corrige que la ventana fuera de muestra participó en la selección del modo de salida.
- Precios: indicadores sobre barras ajustadas por splits (IEX); dinero, tamaños enteros, stop y objetivo con precios nominales del día: sí.
- Anti-PDT (con reglas de riesgo): veta entradas nuevas tras 3 operaciones abiertas y cerradas el mismo día en 5 días hábiles si la equity está bajo 25,000 USD; nunca bloquea una salida. Como en vivo.
- Evidencia técnica vs económica: este reporte mide si el sistema hace lo que dice; no demuestra ventaja económica. Esa se plantea aparte, con la observación en paper y estos intervalos, nunca con el retorno acumulado solo.
Reproducir
- Commit del código:
957a040 - Comando:
bot backtest-portfolio --start 2019-01-01 --split 2023-01-01 --cash 2000 --exit-mode take_profit --risk-rules --tag REAL2000 --no-png - Hash de los datos (cachés CSV usadas):
8bfaba63e4ea - Datos conservados en:
backtest-data/2026-09-12 - Ajuste y feed:
split (indicadores) + raw (dinero), feed IEX - alpaca-py:
0.44.0 - Generado:
2026-09-12 04:37 UTC - Mismo commit + mismos archivos de caché + mismo comando → mismo resultado (
tests/test_backtest_reproducible.py). Sin el archivo de datos el hash no sirve.