Referencia técnica · Paso 8c · Backtest — salidas
Cómo se cierra un trade — fija, GARCH o trailing — y por qué la salida vive en el refinamiento, nunca en el descubrimiento. Meter la salida en el target sería co-optimizar señal y regla de cierre: irreproducible.
Regla dura del pipeline: el Target que consumen lift y combinaciones se calcula SIEMPRE con TP/SL fijos del data_config. Las exit policies alternativas se evalúan solo sobre el top-K ya seleccionado.
Violar esto se llama co-optimización de features y stopping rules — una de las fuentes principales de resultados irreproducibles en descubrimiento cuantitativo.
| Riesgo | Qué pasa |
|---|---|
| Data snooping ×K | Cada variante define un Target distinto → otro ranking → otro set de "ganadores". El FDR habría que hacerlo sobre 10⁹ × K hipótesis. Sin corregir por K, se eligen reglas que solo matchean el ruido de esa exit policy. |
| Path-dependence / circularidad | Si el target depende de un modelo (GARCH, trailing) ajustado a los mismos datos, el edge aparente no viene de la regla — viene de que el target se ajustó al ruido del período. Colapsa silencioso si cambia el régimen de volatilidad. |
| Costo ×K | El discovery recalcula targets K veces. En el timeframe M5 (velas de 5 min) + K=4 + GARCH rolling, un run de ~30 min pasa a ~8 h. Ese costo no compra edge, compra sesgo. |
Única excepción legítima: cuando la salida es estructural al edge (hipótesis documentada ANTES, UNA variante, sensibilidad post-selección). Ya migrada como los targets Target_*_Direction (edge direccional puro, agnóstico de magnitud) — un solo target_column por run.
Toda policy se enchufa al motor sin tocarlo. Dos rutas de ejecución, estado del trade en fracción del entry, sin memoria entre trades.
resolve_vectorized resuelve el trade entero con NumPyevaluate se llama bar-by-bar hasta cerrar o timeout
Las policies son frozen y funciones puras: no mutan arrays ni acumulan estado — cada trade es una llamada independiente. Para validación por folds, windowed(start, end) slicea los arrays puntuales (σ, ATR, extremos) a la ventana, sin truncar el look-behind.
La política por defecto: TP/SL fijos, primer toque gana. Dos detalles de realismo que la separan del legacy: gap conservador y desempate intrabar.
[entry_idx, entry_idx + H]El legacy usaba siempre exit = strike, subestimando el slippage en equities con gaps overnight. El modelo conservador nunca infla ganancias con gaps favorables.
Sin datos intrabar, la dirección de la vela es el proxy de la trayectoria O→H→L→C:
| Dirección | Vela | Trayectoria | Primer toque |
|---|---|---|---|
| long | alcista | O→L→H→C | Low → SL |
| long | bajista | O→H→L→C | High → TP |
| short | alcista | O→L→H→C | Low → TP |
| short | bajista | O→H→L→C | High → SL |
Heurística validada por paridad con el create_target del legacy. Cambiarla rompe la reproducibilidad histórica.
La regla no cambia; lo que se adapta es la magnitud del target a la volatilidad del momento de entrar. Fijos durante el trade — no es un trailing.
En una barra volátil (σ=0.008): TP=1.6%, SL=0.8%. En una tranquila (σ=0.003): TP=0.6%, SL=0.3%. Se calculan UNA vez en la barra de entrada y no se recalculan. Fallback: si σt es NaN o ≤ 0 (warm-up del GARCH), cae al TP/SL fijo del data_config — nunca falla en runtime. Reason tags: tp_garch / sl_garch, o tp/sl si usó fallback.
El SL persigue al precio favorable: solo se acerca, nunca se aleja. Las tres son iterativas (necesitan mfe_pct por barra) y comparten use_fixed_tp (default True → sale por lo primero que toque).
| Variante | SL vigente | Cuándo |
|---|---|---|
| Percent | peak · (1 − trailing_pct) | Volatilidad estable. Variante A: activo desde la barra 1. Variante B: SL fijo hasta mfe ≥ activation_pct, luego trailing. |
| ATR (Chandelier) | peak − mult · ATRt | Volatilidad variable (crypto, forex exóticos). Se autoajusta sin re-calibrar por activo. No dispara mientras ATR sea NaN (warm-up). |
| Bar Extreme | min-low de N barras previas | Niveles estructurales (swings, soportes). La ventana excluye la barra actual — evita path-dependence intrabar. |
El peak deriva del mfe_pct post-update (incluye el high/low de la propia barra) y el disparo compara contra el low (long) / high (short) — paridad con Fixed. Empate TP + trailing SL: misma heurística de vela. Los extremos de Bar Extreme se pre-computan causalmente sobre la serie completa (fold-safe).
La elección del trailing NO se optimiza sobre OOS. Se prueba en el IS post-discovery, se congela, y se valida en WFV/OOS/transfer sin re-tunear.
trailing_pct × activation_pct × initial_sl_pct y quedarse con la mejor es data snooping sobre parámetros de exit — contamina la validación downstream aunque no sea sobre el target. Misma regla que en discovery: hipótesis primero, parámetros según la hipótesis, no búsqueda.Viene de 8a (motor) y 8b (métricas). Sigue en 8d: el filtro de régimen.
Fuentes:
· domain/backtester/exit_policies/ — base, fixed, garch, trailing_percent, trailing_atr, trailing_bar_extreme
· domain/backtester/exit_resolution.py — find_exit (gap conservador, empate intrabar)
· webapp/docs/EXIT_POLICY_ARCHITECTURE.md — scope discovery/refinamiento, los 3 riesgos, guía de parámetros