Golden SkyGolden SkyGuía Técnica

Referencia técnica · Paso 8d · Backtest — régimen

Filtro de régimen

Clasificar cada barra en uno de 6 regímenes (tendencia × volatilidad) y dejar que el trader excluya, por combo y con hipótesis, los regímenes donde su estrategia no debería operar. El sistema informa; el trader decide.

§0Notación

ROCrate of change anualizado (~1 año)
SMALmedia móvil larga (~200 D1)
ATR pctpercentil rolling del ATR
−1régimen Unknown (warm-up)
WRwin rate
retorno medio por trade

§1Los 6 regímenes = tendencia × volatilidad

Dos ejes independientes. La macro tendencia exige consenso de dos indicadores; la volatilidad parte el percentil del ATR en tranquilo/volátil.

Eje 1 · MacroTrend (consenso ROC + SMA)

Up  ⟺  ROC > banda  Y  close > SMAL
Down  ⟺  ROC < −banda  Y  close < SMAL
Neutral  ⟺  los dos indicadores discrepan

Exigir consenso evita señales débiles: si ROC y SMA no coinciden, es Neutral, no una tendencia dudosa. Períodos adaptativos por timeframe (ROC ~1 año, SMA ~200 D1 equivalente).

Eje 2 · Volatilidad (percentil del ATR)

ATR_pct = rank percentil del ATR en ventana rolling causal
Volatile si ATR_pct > volatility_threshold  ·  si no, Quiet

El cruce

QuietVolatile
UpBull_Quiet0Bull_Volatile1
NeutralNeutral_Quiet2Neutral_Volatile3
DownBear_Quiet4Bear_Volatile5

Los códigos 0-5 y las etiquetas son inmutables: los comparten el pipeline, el live Python y los EAs MQL5 (contrato de 3 motores). Un cambio semántico debe replicarse en los tres.

§2Warm-up → Unknown, causalidad

Dos decisiones que protegen la integridad: el warm-up nunca se disfraza de Neutral, y en intraday el régimen se toma de D1 con desfase.

ROC o SMA aún NaN (warm-up) → régimen = −1 (Unknown)  // NUNCA Neutral
régimen válido ⟺ ATR_pct válido Y macro ≠ Unknown

Si el warm-up se etiquetara como Neutral genuino, inflaría ese régimen en las etapas posteriores y contaminaría el breakdown. Por eso el gate exige ambos ejes definidos.

intraday (M1..H12): régimen calculado sobre resample D1
alineado al TF de señal con .shift(1)  // contexto macro causal, sin look-ahead

Calcular el macro sobre D1 da un contexto de ~1 año calendario consistente, independiente del TF operado. El .shift(1) garantiza que la barra de hoy solo ve el régimen cerrado de ayer.

§3Aplicación en el motor

El filtro apaga las señales que caen en un régimen excluido, antes de que el motor genere el trade. No borra barras: enmascara entradas.

_apply_regime_filter señales_efectivas = señales
  & ¬ isin( regime, excluded_regimes )
  & ( block_unknown_regime ? regime ≠ −1 : todo )  // default True

Sin columna regime en los datos, el filtro no se aplica (pasa todas las señales). Cada trade guarda su regime de entrada, que alimenta el breakdown per-régimen del refinamiento.

§4Refinamiento per-combo, no uniforme

El pool tiene combos de naturaleza distinta (breakout, mean-reversion, momentum…). Un mismo excluded_regimes para todos no tiene sentido conceptual: cada uno necesita un tratamiento propio.

1. tag de naturaleza // breakout / mean_reversion / momentum / carry / mixed
2. hipótesis textual obligatoria // mín. 20 chars, no genérica
3. excluir 1-3 regímenes // subconjunto plausible según la hipótesis

Ejemplo: "Breakout long con ATR squeeze: excluir Bear_Quiet porque en bajista tranquilo los squeezes se resuelven a la baja, no al alza." El sistema RECHAZA hipótesis vacías, tags faltantes, o excluir 0/4+ regímenes.

El sistema informa, no decide

DecisiónEfecto en el ranking
AcceptEl combo CON filtro reemplaza al combo sin filtro (no coexisten → no duplica la superficie de multiple testing).
BurnEl combo sale del ranking definitivamente.
SkipQueda sin filtro, marcado como revisado.

Cada decisión se registra append-only (auditoría). El excluded_regimes aceptado es inmutable: los Steps 10-12 recalculan el régimen sobre sus ventanas pero aplican las mismas etiquetas — no permiten cambiarlas.

§5Diagnóstico determinante (default)

Al analizar un filtro se muestran cuatro cosas de costo bajo — sin re-backtest. Base de la decisión del trader.

Breakdown + Δ métricas + ratio de perdedores excluidos

breakdown per-régimen: trades, WR, PF, R̄, % del total // dónde funciona y dónde no
Δ métricas con vs sin filtro: Sharpe, PF, Sortino, N, MaxDD, R̄
losers_ratio = perdedores excluidosperdedores + ganadores excluidos  // breakeven no cuenta

El ratio de perdedores excluidos es el chequeo más honesto y directo: si sacaste trades pero eran mayormente ganadores (ratio < 0.5), la UI muestra warning naranja — algo va mal con la hipótesis.

Test de Δ Sharpe (bootstrap "estilo Jobson-Korkie")

El JK clásico asume dos series paired de igual longitud; acá filtered ⊂ baseline (subset, tamaños distintos). La implementación real es un bootstrap no-paramétrico, más honesto que forzar el supuesto — el nombre se conserva por familiaridad.

compute_jk_test · n=1000 por iteración: remuestrear baseline y filtered con reemplazo → ΔSharpe*
z = ΔSharpe observadostd( ΔSharpe* )  ·  p = 2 · min( P(ΔSharpe* ≤ 0), P(ΔSharpe* ≥ 0) )  // bilateral
CI 90% = percentiles 5% / 95% de ΔSharpe*

Sharpe interno simple (mean/std, sin anualizar) para comparabilidad. Si baseline o filtered tienen < 2 trades → None (no computable). Cost bajo: bootstrap sub-segundo sobre los retornos ya calculados.

§6Opt-in — placebo y FDR

Tests más caros o redundantes con lo que ya hacen WFV/OOS/transfer más adelante en el pipeline. No se corren por default: el trader los invoca si tiene duda sobre un combo puntual.

TestQué respondeCost
Placebo¿La mejora es real o artefacto del efecto-N (menos varianza al sacar N trades cualesquiera)? Excluye N trades al azar × 1000 y compara el Sharpe real vs. la distribución placebo.~1-2 s/combo
FDR-BHLectura estadística global del batch: de las hipótesis aceptadas, ¿cuántas sobreviven la corrección por multiplicidad? Denominador = familia completa (aceptadas + burned, no solo aceptadas).<1 s
Ninguno es un gate. Son info diagnóstica: el trader puede aceptar un combo que no supera el placebo si tiene razón contextual, quedando registrado con warning. La disciplina cae sobre el trader; el log auditable es el freno. El gate duro anti-espurio son WFV/OOS/transfer, en la validación posterior.

Viene de 8c (exit policies). Con esto cierra el backtest; sigue la validación (walk-forward, OOS, transfer).

Fuentes:
· domain/regime_detector/ — classify (macro consenso + ATR percentil), constants (6 regímenes 0-5, −1)
· domain/backtester/engine.py — _apply_regime_filter
· domain/regime_filter/ — breakdown, metrics_delta, losers_ratio, jobson_korkie (bootstrap), placebo, fdr
· webapp/docs/REGIME_FILTER_DESIGN.md — diseño per-combo, decisión trader, auditoría