Saltar a contenido

Q07 · HMM causal de regímenes: tendencia / reversión / efectivo

Campo Valor
Familia Cambio de régimen: un modelo oculto de Markov decide cuánto peso dar a una regla de reversión, a una de tendencia o a quedarse fuera
Universo / panel Una serie (un activo); al menos 1500 barras diarias
Datos OHLC diario
Features returns, volatility (vol20), hmm_parameters, filtered_probabilities, trend_and_reversion_targets
Parámetros y defaults formation = 1260, refit_bars = 126, states = 3, max_iter = 200; trend_lookback = 63 (default añadido por DEV-03)
Rejilla formation ∈ {756, 1260}, refit_bars ∈ {63, 126}, trend_lookback ∈ {21, 63, 126} → 12 candidatos
Fijado en la receta covariance_type = diag, random_state = 1729, n_init = 1, tolerancia 1e-4
Estado Especificada, no implementada (specified_not_implemented, hito H4)
Motores implicados Ninguno ejecuta hoy un HMM. Relacionados: finaz_models_statistics (GARCH con arch, volatilidad condicional), finaz_training/validation (splits walk-forward sin fuga), la política P04 como objetivo de reversión

Estado

Q07 no está implementada y no tiene especificación detallada con fixture en docs/estrategias/ (solo la receta del catálogo). La API de backtesting la declara UNSUPPORTED con NOT_IMPLEMENTED_YET. El ejemplo numérico de esta ficha es didáctico, calculado a mano con parámetros inventados para ilustrar la recursión forward; no procede de ningún modelo ajustado ni de datos reales.

Fuentes: catalog/strategy_catalog.json, docs/API.md (DEV-03), docs/DESVIACIONES_INFORME.md (DEV-03), packages/finaz_models_statistics, docs/v2/MOTORES.md (AG-018, AG-021).

Qué vas a aprender

  • Qué es un modelo oculto de Markov (HMM) y cómo se interpreta un "régimen".
  • La diferencia crucial entre probabilidad filtrada (causal) y probabilidad suavizada (usa el futuro).
  • Cómo se ordenan los estados para que "bajo, medio, alto" signifique siempre lo mismo tras cada refit.
  • Cómo se mezclan dos reglas (reversión y tendencia) con las probabilidades del régimen.
  • Por qué predict_proba o Viterbi sobre todo el periodo no sirven en un backtest.

La idea en una frase

Estimar en cada barra la probabilidad de estar en un régimen tranquilo, normal o turbulento, y operar reversión cuando es tranquilo, tendencia cuando es normal y nada cuando es turbulento.

Intuición y evidencia académica

Intuición. Los mercados alternan periodos de calma (volatilidad baja, movimientos que se deshacen), periodos normales (tendencias moderadas) y periodos de estrés (volatilidad alta, saltos). La literatura de modelos de cambio de régimen en finanzas (modelos de Markov con cambio de estado aplicados a retornos y volatilidad) muestra que estos estados son identificables a posteriori. La dificultad está en identificarlos en tiempo real.

El catálogo lo advierte: "HMM por sí solo no genera una ventaja". La ventaja, si existe, está en la combinación con reglas que funcionan de forma distinta en cada régimen.

Cuándo falla.

  • Etiquetas que rotan: el ajuste numera los estados arbitrariamente; sin reordenar, "estado 0" puede ser tranquilo en un refit y turbulento en el siguiente.
  • Retraso: la probabilidad filtrada tarda unas barras en detectar un cambio de régimen; el daño del primer día de crisis no se evita.
  • Sobreajuste: tres estados con medias, varianzas y matriz de transición son muchos parámetros para un solo activo.

El modelo matemático

Observaciones (normalizadas con media y desviación de la formación):

x_t = [ r_t, |r_t|, vol20_t ]

Modelo: K = 3 estados ocultos, matriz de transición A (A[j,k] = P(estado k en t | estado j en t−1)), emisiones gaussianas con covarianza diagonal.

Recursión forward (filtro causal, en espacio logarítmico en la implementación):

alpha_t(k) ∝ p(x_t | k) · sum_j A[j,k] · alpha_{t−1}(j)
alpha_t    = alpha_t / sum_k alpha_t(k)                     probabilidades filtradas

alpha_t solo usa x_1 … x_t. La probabilidad suavizada P(k | x_1 … x_T) usa todo el periodo y está prohibida en la evaluación.

Ordenación de estados: por la varianza de r en la formación → baja, media, alta.

Peso objetivo:

w_t = p_baja · objetivo_reversión_P04 + p_media · sign(momentum_{trend_lookback}) + p_alta · 0

Pseudocódigo causal

para cada bloque de refit_bars barras:
    formación = [b − formation, b)  (+ historial previo para vol20)
    normalizar features con la formación; ajustar HMM (semilla 1729, diag, tol 1e-4)
    ordenar estados por varianza de r; guardar convergencia, semilla y orden
    inicializar alpha filtrando el pasado con el modelo nuevo
    para cada t del bloque:
        alpha_t = forward(alpha_{t−1}, x_t)                 # nunca backward
        w_t = p_baja · rev_t + p_media · trend_t
        q = trunc(w_t · frozen_equity / close[t]); fill en open[t+1]

Cómo lo haría la plataforma

Pieza Quién la cubriría Estado
Features r, vol20 Primitivas del banco (log_return, rolling_vol) Existen
Objetivo de reversión La política P04 (Bollinger) del banco Existe como estrategia popular
Objetivo de tendencia momentum{trend_lookback} Existe (Q01)
HMM y filtro forward Código propio o hmmlearn con filtro propio No existe; hmmlearn no está en ninguna imagen
Volatilidad condicional alternativa finaz_models_statistics.ajustar_predecir_garch(retornos, horizonte, niveles, p=1, q=1) Existe (G4, arch)
Splits sin fuga para validar finaz_training/validation (folds, splits cronológicos con embargo) Existe (AG-018)
Registro de semilla, convergencia y orden finaz_training/registry (artefacto + ModelRef) Existe (AG-019)
Ejecución Ledger de cartera (una sola pata) Existe
flowchart LR
    D["Serie diaria<br/>≥ 1500 barras"] --> F["x = [r, abs r, vol20]<br/>normalizado en formación"]
    F --> H["HMM 3 estados<br/>(no implementado)"]
    H --> O["Ordenar por varianza<br/>baja · media · alta"]
    O --> A["Filtro forward causal<br/>p_baja, p_media, p_alta"]
    P["Objetivo P04<br/>reversión"] --> M["Mezcla<br/>p_baja·rev + p_media·trend"]
    T["sign(momentum 63)"] --> M
    A --> M
    M --> L["Ledger<br/>fill open[t+1]"]
    G["finaz_models_statistics<br/>GARCH (alternativa de vol)"] -.-> F

Ejemplo numérico

Ejemplo didáctico

Parámetros inventados para ilustrar el cálculo. Una sola observación (r) en lugar de tres para que quepa a mano.

Modelo supuesto (tres estados ya ordenados):

             baja      media     alta
media r      0.0005    0.0000   −0.0010
desv. r      0.005     0.012     0.030

A = [[0.95, 0.04, 0.01],        (desde baja)
     [0.05, 0.90, 0.05],        (desde media)
     [0.02, 0.08, 0.90]]        (desde alta)

alpha_{t−1} = [0.60, 0.30, 0.10]

Barra 1: r = +0,004 (un día tranquilo).

predicción   A' · alpha = [0.587, 0.302, 0.111]
verosimilitud p(r|k)    = [62.45, 31.45, 13.12]     densidad normal en r
producto               = [36.66,  9.50,  1.46]
alpha_1 = normalizado  = [0.770, 0.200, 0.031]

Si la regla de reversión (P04) está neutral (0) y el momentum es positivo (+1):

w_1 = 0.770 · 0 + 0.200 · (+1) + 0.031 · 0 = +0.20

Barra 2: r = −0,025 (un golpe fuerte).

predicción   A' · alpha_1 = [0.742, 0.213, 0.045]
verosimilitud            = [≈ 0, 3.80, 9.66]         en baja, −0.025 está a 5 desviaciones
alpha_2                  = [0.0001, 0.649, 0.351]

Con reversión en compra (+1, el precio tocó la banda inferior) y momentum ya negativo (−1):

w_2 = 0.0001 · (+1) + 0.649 · (−1) + 0.351 · 0 = −0.65

Un solo día violento ha movido la probabilidad del régimen tranquilo de 0,77 a casi cero y ha llevado un 35 % de probabilidad al régimen turbulento, que pesa 0. La regla de reversión, que en calma habría comprado el golpe, queda anulada. Esa es la intuición de Q07.

Lo que no se debe hacer. Si se calculasen las probabilidades suavizadas con toda la serie, la barra 1 "sabría" que viene un golpe y ya bajaría p_baja: una fuga de información que haría el backtest irrealmente bueno.

Cómo lanzarla

Hoy no se puede lanzar. GET /v1/capabilities devuelve Q07 UNSUPPORTED en todos los carriles (NOT_IMPLEMENTED_YET) y POST /v1/backtest responde 501 STRATEGY_UNSUPPORTED_BY_ADAPTER. El catálogo publica su param_schema con los cinco parámetros, incluido trend_lookback = 63 (DEV-03).

Qué se puede ejecutar hoy de la familia estadística. GARCH(1,1) con arch está en la imagen quant como adaptador (finaz_models_statistics.adaptador_arch); el job S7 por defecto del rol ejecuta ARIMA/ETS:

echo '{"job_id": "demo-quant-stats", "rol": "quant", "operacion": "ridge_catboost_stats"}' \
    > /spool/jobs/demo-quant-stats.json
python -m finaz_runtime.jobs.runner --rol quant --once

Como biblioteca, dentro de la imagen quant (ilustrativo):

from finaz_models_statistics.adaptador_arch import ajustar_predecir_garch
dist = ajustar_predecir_garch(retornos, horizonte=1, niveles=(0.1, 0.5, 0.9), p=1, q=1)

Una volatilidad GARCH filtrada no es un HMM, pero cubre una parte de la misma pregunta ("¿estamos en un periodo turbulento?") con un motor que ya existe y pasa G4.

Riesgos, límites y qué no hace

  • No implementada y sin especificación con fixture: es la receta menos madura del catálogo después de Q09/Q10.
  • Muchos parámetros para una serie: 3 medias × 3 features, 3 × 3 varianzas, 6 transiciones libres.
  • Dependencia de la semilla y del orden: por eso la receta fija random_state = 1729, n_init = 1 y guarda el orden de etiquetas.
  • Retraso de detección inherente al filtro causal.
  • Historial de vol20: la ventana de 20 barras debe incluirse antes de la formación, o las primeras filas tendrán NaN.
  • No es una estrategia por sí sola: sin P04 y el momentum, el HMM no produce pesos.

Resumen

  • HMM gaussiano de 3 estados sobre [r, |r|, vol20], estados ordenados por varianza.
  • Probabilidades filtradas (forward), nunca suavizadas ni Viterbi sobre la evaluación.
  • Peso = mezcla de reversión (régimen bajo) y tendencia (régimen medio); régimen alto = fuera.
  • Especificada solo en el catálogo; no implementada; la plataforma aporta features, P04, GARCH y la disciplina de validación sin fuga.

Para practicar

  1. Repite la barra 2 del ejemplo con r = −0,010. ¿Cuánto baja p_baja?
  2. ¿Por qué ordenar los estados por varianza y no por media?
  3. Explica con tus palabras por qué Viterbi sobre toda la evaluación es una fuga aunque el modelo se haya ajustado solo con la formación.
  4. Propón cómo usar la banda de cuantiles de ajustar_predecir_garch como sustituto simple de p_alta.