Saltar a contenido

P16 · Keltner: ruptura tendencial

Campo Valor
Familia Volatilidad-ruptura con sesgo de tendencia · entra al romper el canal y sale a plano; nunca invierte
Features requeridas ema (entrada close) y atr (entradas high, low, close; depende de tr), ambas ya registradas; las bandas se calculan en la política
Parámetros y defaults period = 20 (EMA), atr_period = 10 (ATR), k = 2.0 (ancho, de política)
Rejilla del catálogo period ∈ {10, 20, 40}, atr_period ∈ {10, 14, 20}, k ∈ {1.0, 1.5, 2.0, 2.5} → 36 candidatos válidos, 3 EMA + 3 ATR
variant_keys period, atr_period, k
Restricciones period >= 2, atr_period >= 1, k > 0 (si no, PolicyParamError); sin restricción cruzada entre periodos
first_decision_index max(period − 1, atr_period − 1) = 19 con defaults (39 con period = 40)
Activos y timeframes NVDA y KO en los 7 TF (1min1d); AMZN, INTC, MSFT, TSLA en 1d; BME y FX en 1d con los calendarios de WP-01b
Paridad en la matriz PASS en NT_FEATURES, NT_INTENT_REPLAY y NT_ONLINE(vectorta) (12/12 cada una). PARITY_FAIL nativa en NT_ONLINE(native) (12/12), capa features: la EMA nativa de Nautilus usa semilla "primer valor" (nt_seed_first_value) y la del banco la media corrida enmascarada (vta_running_mean_masked); primera divergencia en la barra 19 de KO 1d (ema 45,3790 frente a 45,3564). Es una variante declarada, no un bug

Fuentes: docs/estrategias/P16.md (especificación), catalog/strategy_catalog.json, docs/DATOS_POR_ESTRATEGIA.md, runs/parity/summary.json, runs/parity/KO_1d_P16_5bps_NT_ONLINE_native.md.

Qué vas a aprender

  • Qué es un canal de Keltner (EMA ± k·ATR) y por qué aquí se persigue la ruptura en lugar de revertirla como en P04.
  • La convención de cruce entre dos series (a[t] > b[t] y a[t−1] <= b[t−1]) y en qué se diferencia de un cruce contra un nivel fijo.
  • Una máquina de estados con salida a plano, prioridad de la salida y prohibición de invertir.
  • Por qué k no toca la caché: 36 candidatos, 6 curvas.
  • Qué significa la regla D-12 para la primera barra de decisión cuando la regla mira t−1.

La idea en una frase

Cuando el cierre rompe hacia arriba un canal de volatilidad alrededor de su media, entrar largo (hacia abajo, corto); cuando el cierre vuelve al otro lado de la media, salir a plano.

Intuición de mercado

El canal de Keltner envuelve una EMA con una banda proporcional al ATR, la volatilidad típica reciente. Mientras el precio se mueve dentro del canal, el movimiento es "ruido normal". Un cierre fuera del canal es un desplazamiento anormalmente grande respecto a la volatilidad reciente, y P16 apuesta a que ese impulso continúa. Sale cuando el precio vuelve a cruzar la media: el impulso se ha agotado.

Analogía. Un río con diques. Mientras el agua sube y baja dentro del cauce, no pasa nada. Si desborda el dique, algo ha cambiado aguas arriba y la crecida suele seguir un rato; das la alarma cuando el agua vuelve a su nivel medio, no antes.

Por qué podría funcionar. Normalizar por el ATR hace la regla comparable entre activos y regímenes de volatilidad; las rupturas de volatilidad son un punto de partida clásico de las tendencias.

Cuándo falla.

  • Rupturas falsas: el cierre sale del canal y vuelve al día siguiente. Con k bajo pasa a menudo.
  • Gaps de apertura intradía: la primera barra de la sesión rompe el canal por el gap nocturno. La EMA y el ATR no se reinician por sesión, así que en 1min buena parte de las entradas pueden ser gaps.
  • Dividendos con precio ajustado: el escalón del ajuste mete un TR artificial que ensancha el canal justo en la barra ex-dividendo. Por eso se recomienda raw_split_adjusted.

Sesgos típicos al evaluarla.

  • Elegir k sobre el mismo periodo: con 4 valores de k por pareja de periodos es fácil sobreajustar.
  • Atribuir a la receta lo que son gaps: mide la fracción de entradas en la primera barra de la sesión.
  • Usar un Keltner "estándar": muchas plataformas centran el canal en el precio típico (H+L+C)/3 y usan un solo periodo; es otra curva.

Las reglas exactas

La regla canónica del catálogo: "Canal EMA(C,n)±k·ATR(a). Entrar largo al cruce del cierre por la banda superior; corto al cruce por la inferior. Salir cuando el cierre atraviese la EMA contra la posición; sin inversión en la decisión de salida."

Bandas, calculadas en la política: upper[t] = ema[t] + k·atr[t], lower[t] = ema[t] − k·atr[t].

Estado previo Situación en el cierre de t Objetivo Motivo (reason)
t < first_decision_index 0 WARMUP
plano 0 close[t] > upper[t] y close[t−1] <= upper[t−1] +1 ENTRY
plano 0 close[t] < lower[t] y close[t−1] >= lower[t−1] −1 ENTRY
plano 0 ninguna de las dos 0 HOLD
largo +1 close[t] < ema[t] (estricto) 0 EXIT
largo +1 si no +1 HOLD
corto −1 close[t] > ema[t] (estricto) 0 EXIT
corto −1 si no −1 HOLD
cualquiera barra no operable se congela el publicado NOT_TRADABLE
  • Entrada: por cruce de la banda, con la convención de dos series; un cierre que ya estaba fuera no vuelve a entrar (no hay piramidación).
  • Salida: por nivel contra la EMA, no contra la banda. Se lee como nivel para que un largo no quede atrapado si el cruce ocurrió en una barra no operable.
  • Reversión: prohibida. Desde largo solo se va a plano; un fill de 2 unidades en P16 es un bug.
  • Prioridad: estando en posición, se evalúa primero la salida y, si sale, esa barra no reabre.
  • Empates: close == upper no entra; close == ema no sale; close[t−1] == upper[t−1] sí permite el cruce.
# decisión en el cierre de t, fill en el open de t+1
target_prev = 0
para cada barra t:
    si t < first_decision_index:              target = 0              # WARMUP
    upper = ema[t] + k*atr[t] ; lower = ema[t] - k*atr[t]
    si target_prev == +1:
        target = 0 si close[t] < ema[t] si no +1                     # salida primero
    si no, si target_prev == -1:
        target = 0 si close[t] > ema[t] si no -1
    si no:                                                            # desde plano
        si close[t] > upper y close[t-1] <= upper[t-1]:  target = +1
        si no, si close[t] < lower y close[t-1] >= lower[t-1]: target = -1
        si no:                                            target = 0
    si target != target_prev:
        orden(target - target_prev) -> fill a open[t+1]              # siempre ±1
    target_prev = target

No es el crossed_up de P04

El crossed_up del código de políticas compara contra un nivel constante con previous < level y current >= level. Aquí el "nivel" es una serie que cambia cada barra y la convención es la opuesta en los empates: > estricto en t y <= en t−1. Un cierre que toca exactamente la banda no entra con esta regla y sí con crossed_up.

Los indicadores que usa

Feature Fórmula first_valid
ema{period} α = 2/(n+1); semilla SMA(close[0..n−1]); luego ema[t] = α·close[t] + (1−α)·ema[t−1] period − 1 = 19
tr max(H−L, abs(H−C[t−1]), abs(L−C[t−1])); TR[0] = H0 − L0 0
atr{atr_period} RMA de Wilder del TR (α = 1/n, semilla SMA) atr_period − 1 = 9
upper, lower ema ± k·atr (en la política, no en una feature) max de las dos

first_decision_index = max(period − 1, atr_period − 1) sin sumar 1, por la regla D-12: en la barra 19 la regla consulta upper[18], que es NaN, la comparación da False y la decisión legítima es "no entrar". La barra 19 se marca HOLD con decision_index_valid = True, no WARMUP, y ningún objetivo cambia por ello.

No hace falta ninguna primitiva nueva: P16 reutiliza ema, atr y tr. Se descarta a propósito una primitiva keltner_ema (nombre reservado y no registrado) porque metería k en la clave de caché. Fórmulas y comparación entre motores en Indicadores paso a paso y en Indicadores y features.

Ejemplo barra a barra

Fixture §6 de docs/estrategias/P16.md: 16 barras, parámetros fuera de rejilla a propósito (con los de la rejilla no hay ninguna ruptura en 16 barras): period = 3 (α = 0,5), atr_period = 2, k = 0.5. cash_inicial = 1.000,00, tick 0,01, sin costes. first_valid(ema) = 2, first_valid(atr) = 1first_decision_index = 2.

Se muestran 14 de las 16 barras: se omiten la 0 (warmup, O=10,00 H=10,20 L=9,95 C=10,10) y la 11 (corto mantenido: C=9,70, ema 9,9340, upper 10,1296, lower 9,7384, equity 1.000,50).

t O H L C ema(3) atr(2) upper lower target orden fill pos. PnL acum. sin costes equity con 5 bps*
1 10,10 10,25 10,00 10,05 NaN 0,2500 NaN NaN 0 WARMUP 0 0,00 1.000,00
2 10,05 10,30 10,00 10,20 10,1167 0,2750 10,2542 9,9792 0 HOLD 0 0,00 1.000,00
3 10,20 10,65 10,15 10,60 10,3583 0,3875 10,5521 10,1646 +1 ENTRY BUY 1 0 0,00 1.000,00
4 10,60 11,00 10,55 10,95 10,6542 0,4187 10,8635 10,4448 +1 10,60 +1 0,35 1.000,34
5 10,95 11,20 10,85 11,10 10,8771 0,3844 11,0693 10,6849 +1 +1 0,50 1.000,49
6 11,10 11,15 10,80 10,90 10,8885 0,3672 11,0721 10,7049 +1 +1 0,30 1.000,29
7 10,90 11,00 10,55 10,60 10,7443 0,4086 10,9486 10,5400 0 EXIT SELL 1 +1 0,00 999,99
8 10,60 10,70 10,35 10,40 10,5721 0,3793 10,7618 10,3825 0 10,60 0 0,00 999,98
9 10,40 10,50 10,15 10,20 10,3861 0,3646 10,5684 10,2037 −1 ENTRY SELL 1 0 0,00 999,98
10 10,20 10,30 9,90 9,95 10,1680 0,3823 10,3592 9,9769 −1 10,20 −1 0,25 1.000,22
12 9,70 9,95 9,60 9,85 9,8920 0,3706 10,0773 9,7067 −1 −1 0,35 1.000,32
13 9,85 10,15 9,80 10,10 9,9960 0,3603 10,1761 9,8159 0 EXIT BUY 1 −1 0,10 1.000,07
14 10,10 10,35 10,05 10,30 10,1480 0,3301 10,3131 9,9829 0 10,10 0 0,10 1.000,06
15 10,30 10,55 10,25 10,50 10,3240 0,3151 10,4815 10,1665 +1 ENTRY BUY 1 sin fill 0 0,10 1.000,06

Todo es literal del fixture (§6.1, §6.2, §6.3 y la serie de equity de §6.5) salvo la última columna. *"Equity con 5 bps" es cálculo de este manual: escenario hypothetical_5bps (fee_rate = 0,0005, sin spread ni deslizamiento), comisión por fill = |delta| · precio · 0,0005 redondeada al céntimo half-even y restada de la caja en la barra del fill: 10,60 · 0,0005 = 0,0053 → 0,01 (fills 1 y 2), 10,20 · 0,0005 = 0,0051 → 0,01, 10,10 · 0,0005 = 0,00505 → 0,01. Cuatro fills, 0,04 en total. El PnL acumulado es equity − 1.000,00 del fixture.

Lo que hay que ver en la tabla:

  1. Barra 2: primera barra de decisión (D-12). Aunque close = 10,20 y upper = 10,2542, el cruce necesita upper[1], que es NaN: objetivo 0. (La lista de casos de §6.3 de la especificación todavía llama "warmup" a la barra 2; su propia tabla y §6.4 la marcan HOLD, que es lo que manda D-12.)
  2. Barra 3: 10,60 > 10,5521 y 10,20 <= 10,2542: las dos mitades del cruce. Fill al open de 4 (10,60).
  3. Barras 4–5: el cierre sigue por encima de la banda, pero no hay nuevo cruce (close[t−1] <= upper[t−1] es falso). No se piramida.
  4. Barra 6: el cierre cae bajo la banda pero sigue sobre la EMA: se conserva. La salida es contra la EMA.
  5. Barra 7: 10,60 < 10,7443 → plano. Un solo fill de 1 unidad. El trade largo termina en 0,00 exactos (compra y venta a 10,60).
  6. Barra 9: 10,20 < 10,2037 (por 0,0037) y 10,40 >= 10,3825: entrada corta.
  7. Barras 13–15: salida del corto, una barra plana y reentrada larga. El objetivo pasa de −1 a +1 a través de 0. La última señal queda sin fill y la posición final es 0: no hay nada abierto que valorar. Equity final 1.000,10.

Las cuentas de las barras clave

Semillas:      TR[0..3] = 0,25 ; 0,25 ; 0,30 ; 0,50
               atr[1] = (0,25 + 0,25)/2 = 0,2500 ; atr[2] = 0,25 + (0,30 - 0,25)/2 = 0,2750
               atr[3] = 0,2750 + (0,50 - 0,2750)/2 = 0,3875
               ema[2] = (10,10 + 10,05 + 10,20)/3 = 10,116667
               ema[3] = 10,116667 + 0,5·(10,60 - 10,116667) = 10,358333
Entrada (t=3): upper[2] = 10,116667 + 0,5·0,2750 = 10,254167
               upper[3] = 10,358333 + 0,5·0,3875 = 10,552083
               10,60 > 10,552083  y  10,20 <= 10,254167          -> +1 ENTRY
Salida (t=7):  ema[7] = 10,8885 + 0,5·(10,60 - 10,8885) = 10,74425
               10,60 < 10,74425                                  -> 0 EXIT (1 unidad)
Corto (t=9):   ema[9] = 10,5721 + 0,5·(10,20 - 10,5721) = 10,38605 ; atr[9] = 0,36465
               lower[9] = 10,38605 - 0,182325 = 10,203725 ; lower[8] = 10,38245
               10,20 < 10,203725  y  10,40 >= 10,38245           -> -1 ENTRY
Caja:          1.000,00 - 10,60 = 989,40 ; + 10,60 = 1.000,00 ; + 10,20 = 1.010,20 ; - 10,10 = 1.000,10

Este fixture no sirve para probar el fill en la apertura siguiente

Sus barras están encadenadas (open[t+1] == close[t]), así que un fill a close[t] y uno a open[t+1] dan el mismo precio. La especificación usa otro fixture con gap para ese test.

Diagrama

flowchart LR
    W(["WARMUP: t < max(period, atr_period) - 1"]) --> FLAT["Plano 0"]
    FLAT -->|"C cruza upper hacia arriba · BUY 1"| L["Largo +1"]
    FLAT -->|"C cruza lower hacia abajo · SELL 1"| S["Corto -1"]
    L -->|"C < EMA · SELL 1"| FLAT
    S -->|"C > EMA · BUY 1"| FLAT
    L -->|"C >= EMA"| L
    S -->|"C <= EMA"| S
    FLAT -->|"sin cruce"| FLAT

No hay flecha directa entre largo y corto: es la traducción literal de "sin inversión en la decisión de salida".

Cómo lanzarla

Backtest con ambos motores sobre NVDA 5min en 2024, con los defaults (forma "inline" de la API de backtesting; el resto del payload como examples/api/01_backtest_p01_both.request.json):

BASE=http://localhost:8000
SID=$(curl -sS -X POST "$BASE/v1/backtest" -H 'Content-Type: application/json' -d '{
  "request_id": "manual-p16-0001",
  "correlation_id": null,
  "schema_version": "v1",
  "payload": {
    "strategy": {"template_id": "P16",
                 "params": {"period": 20, "atr_period": 10, "k": 2.0},
                 "name": "Keltner 20/10 k2 base", "register": true},
    "data": {"asset_id": "NVDA", "timeframe": "5min",
             "window": {"start": "2024-01-01T00:00:00Z", "end": "2025-01-01T00:00:00Z",
                        "mode": "reset_flat"}},
    "execution": {"lane": "SIM-S", "contract_version": "1", "initial_cash": 100000,
                  "target_lots": [-1, 0, 1], "cost_scenario": "hypothetical_5bps"},
    "engine": "both",
    "cache": {"cache_mode": "WARM_FEATURES", "write_through": true},
    "output": {"output_mode": "metrics", "include_timings": true,
               "include_parity": true, "tail_rows": 5},
    "engine_options": {"vectorta": {"kernel": "scalar", "use_batch": true},
                       "nautilus": {"adapter": "NT_FEATURES"}}
  }}' | jq -r '.payload.signature.strategy_id')
echo "$SID"
import uuid, requests
BASE = "http://localhost:8000"
env = lambda payload: {"request_id": str(uuid.uuid4()), "correlation_id": None,
                       "schema_version": "v1", "payload": payload}
payload = {
    "strategy": {"template_id": "P16",
                 "params": {"period": 20, "atr_period": 10, "k": 2.0},
                 "register": True},
    "data": {"asset_id": "NVDA", "timeframe": "5min",
             "window": {"start": "2024-01-01T00:00:00Z",
                        "end": "2025-01-01T00:00:00Z", "mode": "reset_flat"}},
    "execution": {"lane": "SIM-S", "contract_version": "1", "initial_cash": 100000,
                  "target_lots": [-1, 0, 1], "cost_scenario": "hypothetical_5bps"},
    "engine": "both",
    "output": {"output_mode": "metrics", "include_parity": True},
    "engine_options": {"vectorta": {"kernel": "scalar", "use_batch": True},
                       "nautilus": {"adapter": "NT_FEATURES"}},
}
r = requests.post(f"{BASE}/v1/backtest", json=env(payload)).json()["payload"]
SID = r["signature"]["strategy_id"]
print(SID, r["parity"]["verdict"])
for res in r["results"]:
    print(res["adapter"], res["run_metrics"]["final_equity"], res["run_metrics"]["n_fills"])

Si pides NT_ONLINE con indicator_provider: "native", espera PARITY_FAIL en la capa features en la barra period − 1: es la variante declarada de la semilla EMA.

Derivar una variante con un canal más ancho:

curl -sS -X POST "$BASE/v1/strategies/$SID/derive" -H 'Content-Type: application/json' -d '{
  "request_id": "manual-p16-derive", "correlation_id": null, "schema_version": "v1",
  "payload": {"param_overrides": {"k": 2.5}, "name": "Keltner 20/10 k2.5",
              "slug": "keltner-20-10-k2-5", "tags": ["sensibilidad"]}}' | jq '.payload.strategy_id, .payload.cache_forecast'

k es parámetro de política: según §5.3 de la especificación, ema(20) y atr(10) se reutilizan enteras y solo se rehacen las bandas (dos operaciones vectoriales), la política y el ledger. El cache_forecast debe anunciar ema y atr como aciertos esperados. Si cambiaras period, fallaría solo la EMA (el ATR se reutiliza); si cambiaras atr_period, fallaría solo el ATR (la EMA y el tr se reutilizan).

Sweep de sus variant_keys (rejilla completa del catálogo, con la forma de 03_sweep_p01_4x4.request.json):

curl -sS -X POST "$BASE/v1/sweep" -H 'Content-Type: application/json' -d '{
  "request_id": "manual-p16-sweep", "correlation_id": null, "schema_version": "v1",
  "payload": {"base_strategy_id": "'"$SID"'",
    "grid": {"period": [10, 20, 40], "atr_period": [10, 14, 20], "k": [1.0, 1.5, 2.0, 2.5]},
    "candidate_policy": {"drop_invalid": true, "drop_duplicates": true,
                         "register_strategies": true,
                         "name_template": "{template_name} {period}/{atr_period} k{k}"},
    "data": {"asset_id": "NVDA", "timeframe": "5min",
             "window": {"start": "2024-01-01T00:00:00Z", "end": "2025-01-01T00:00:00Z", "mode": "reset_flat"}},
    "execution": {"lane": "SIM-S", "contract_version": "1", "initial_cash": 100000,
                  "cost_scenario": "hypothetical_5bps"},
    "engine": "vectorta", "cache": {"cache_mode": "OPT_FULL"},
    "output": {"output_mode": "metrics"},
    "engine_options": {"vectorta": {"kernel": "scalar", "use_batch": true}}}}' | jq '.payload.job_id, .payload.factorization_plan'

Según §5.4 de la especificación: 36 candidatos, 36 válidos, 6 curvas (ema 10, 20, 40 y atr 10, 14, 20) más una sola tr. El resultado se consulta en GET /v1/jobs/{job_id}.

Cuánto cuesta la rejilla

Concepto Cuenta
Candidatos cartesianos 3 × 3 × 4 = 36
Inválidos 0 (ningún k <= 0 y sin restricción cruzada: period = 10, atr_period = 20 es un candidato legítimo)
Curvas de feature 3 EMA + 3 ATR = 6, más 1 tr = 7 cálculos
Reutilización cada EMA la usan 12 candidatos y cada ATR otros 12; factor 36/6 = 6×
Entre estrategias ema(20) es la misma clave que la ema_slow de P01; atr(14) la comparten P06, P15 y P19

Es, con P15, la mejor factorización del grupo, y el contraste con P14 (12 candidatos, 12 curvas) es el mejor ejemplo del manual de por qué importa separar parámetros de feature y de política.

Qué mirar en los resultados

  • parity.verdict en NT_FEATURES: PASS en la matriz.
  • Número de fills con qty == 2: debe ser exactamente 0. Es una métrica de corrección.
  • Número de trades completos (0 → ±1 → 0) y duración media: es lo que k gobierna.
  • Fracción de barras planas tras el warmup: debe ser alta. Si es 0, la salida está mal leída.
  • Ancho medio del canal 2·k·atr / ema, para comparar k entre activos.
  • Fracción de entradas en la primera barra de la sesión (intradía): si es muy alta, estás midiendo gaps, no rupturas.
  • Distancia entre el cierre de entrada y la banda rota: cuánto "se pasa" la ruptura antes de que el fill ocurra en el open siguiente.
  • Divergencia conocida: con NT_ONLINE(native) la matriz da PARITY_FAIL en features desde la barra 19 de KO 1d (ema 45,378999900000004 frente a 45,356433020734514; diferencia máxima 0,0226). El mismo informe muestra que el atr nativo (semilla nt_seed_first_value_sma_inner) también difiere desde antes, pero la primera divergencia registrada es la de la EMA. Una diferencia pequeña en la banda puede mover el índice exacto de un cruce, así que no se esconde bajo una tolerancia.

Causalidad comprobada

Las dos curvas son recursiones causales; las bandas son aritmética de la misma barra. Las fugas posibles están en la política: comparar close[t] con upper[t+1] (un np.roll en la dirección equivocada) o decidir con la banda o el precio de la barra de fill. Ambas adelantan las entradas una barra y mejoran el resultado de forma sistemática.

El test de futuros alterados de la especificación multiplica por 1,5 open, high, low y close desde la barra 300 de una serie de 400 y exige igualdad bit a bit de ema, atr, bandas, objetivos y fills hasta la barra 250. Su complemento perturba solo close[300]: target[299] no puede cambiar, target[300] sí, y target[301] también, porque close[300] es la mitad t−1 del cruce de la barra 301. Una implementación que no muestre el efecto en 301 tiene el cruce mal construido.

Para reanudar en modo continue bastan tres escalares: ema[T], atr[T] y close[T] (este se usa dos veces: para TR[T+1] y para la mitad t−1 del cruce), más el objetivo vigente y el ledger. upper[T] y lower[T] se derivan de ellos con k.

Errores típicos

Error Consecuencia Cómo evitarlo
k <= 0 PolicyParamError: con k = 0 las bandas colapsan en la EMA y cada trade dura una barra Usa k > 0
period = 1 PolicyParamError: la EMA sería el propio cierre y la salida no se cumpliría nunca period >= 2
Invertir de +1 a −1 en una barra Un fill de 2 unidades, prohibido por el catálogo La salida lleva a 0; la entrada contraria se evalúa en barras siguientes
Salir contra la banda en vez de contra la EMA Trades mucho más cortos: otra estrategia Salida close < ema (largo) / close > ema (corto)
Entrar por nivel (close > upper) sin exigir cruce Reentradas en cada barra fuera del canal Exige también close[t−1] <= upper[t−1]
Usar crossed_up con la banda como nivel Diferencias en los empates Convención de dos series: > en t, <= en t−1
Meter k en la FeatureSpec 36 curvas en vez de 6; el test de caché falla Las bandas se calculan en la política
Usar vector_ta.keltner o el KeltnerChannel de Nautilus Otra curva (precio típico, periodo único) La fórmula canónica es EMA(close, n) ± k·ATR(a) con dos periodos

Variantes y extensiones

  • Reversión en el canal: P04 (Bollinger) usa el mismo tipo de canal para apostar a la vuelta a la media.
  • Canal de Keltner con SMA: keltner_sma (SMA ± k·SMA(TR)) es la primitiva de P08; no se confunde con este canal.
  • Solo largos: target_lots = [0, 1], ignorando la ruptura de la banda inferior.
  • Stop real en la banda (SIM-R): entrar intrabar al tocar la banda es otra estrategia.
  • Filtro de tendencia: exigir ADX alto (P15) para aceptar la ruptura.
  • Parientes de ruptura: P11 Donchian (máximos y mínimos del canal) y P05 (compresión de ancho).

Resumen

  • P16 entra cuando el cierre cruza fuera del canal EMA ± k·ATR y sale a plano cuando el cierre vuelve al otro lado de la EMA.
  • Nunca invierte: el ciclo es 0 → ±1 → 0; todos los fills son de 1 unidad.
  • Primera decisión en max(period − 1, atr_period − 1) (19 con defaults), sin sumar 1 por el t−1 del cruce (D-12).
  • k es de política: 36 candidatos, 6 curvas.
  • Paridad PASS en los carriles comparables; el carril nativo de Nautilus diverge por la semilla de la EMA, una variante declarada.

Para practicar

  1. Con el fixture, calcula a mano la barra 10: parte de ema[9] = 10,38605, atr[9] = 0,36465, close[9] = 10,20 y la barra 10 (H 10,30, L 9,90, C 9,95). Obtén TR[10] = 0,40, atr[10] ≈ 0,3823, ema[10] ≈ 10,1680, y comprueba que, estando corto, 9,95 > 10,1680 es falso y se conserva el −1.
  2. Rehaz la columna "equity con 5 bps" con 10 bps. ¿Cuánto pagan en total los cuatro fills y cuál es la equity final? (Pista: 10,10 · 0,001 = 0,0101.)
  3. Deriva desde tu $SID las variantes k = 1.0 y k = 2.5, lánzalas sobre NVDA 5min y compara número de trades, duración media y fracción de entradas en la primera barra de la sesión.