Saltar a contenido

P06 · Supertrend

Campo Valor
Familia Tendencia · parada dinámica por volatilidad con bandas recursivas; dirección continua (siempre largo o corto tras el warmup)
Features requeridas supertrend (salidas upper_final, lower_final, direction), que depende de atr{period} (RMA de tr) y de hl2
Parámetros y defaults period = 10, multiplier = 3.0
Rejilla del catálogo period ∈ {7, 10, 14, 21}, multiplier ∈ {1.5, 2.0, 2.5, 3.0, 4.0} → 20 candidatos válidos; 1 tr + 1 hl2 + 4 atr compartidas y 20 recursiones propias
variant_keys period, multiplier
Restricciones period entero ≥ 1 y multiplier > 0 (si no, PolicyParamError)
first_decision_index period − 1 = 9 con defaults (20 con period = 21)
Activos y timeframes NVDA y KO en los 7 TF (1min1d); AMZN, INTC, MSFT, TSLA en 1d; BME en 1d con reservas; FX solo con OHLC reparado (las filas con high < low rompen el True Range)
Paridad en la matriz PASS en NT_FEATURES, NT_INTENT_REPLAY y NT_ONLINE(vectorta) (12/12 cada una). UNSUPPORTED en NT_ONLINE(native) (12/12): "nautilus_trader 1.231.0 no ofrece un indicador nativo para 'supertrend'". No es una divergencia: no hay variante nativa que ejecutar

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

Qué vas a aprender

  • Cómo una banda de volatilidad (HL2 ± m·ATR) se convierte en una parada con memoria gracias a una recursión.
  • Por qué la banda final no es un mínimo acumulado: la cláusula de reinicio por ruptura del cierre previo la deja volver a subir.
  • Que el primer largo de P06 es una semilla (direction = +1 por definición), no una señal.
  • A seguir en el fixture de la especificación dos reversiones de 2 unidades y la caja fill a fill.
  • Por qué cambiar multiplier recalcula toda la recursión aunque reutilice el ATR.

La idea en una frase

Sigue la tendencia con un "trailing stop" que se mueve solo a favor: mientras el cierre no cruce la banda del lado contrario, mantén la dirección; cuando la cruce, invierte.

Intuición de mercado

El Supertrend dibuja una banda a m ATR por encima y por debajo del punto medio de la barra. Estando largo solo importa la banda inferior, que actúa como stop: puede subir (se acerca al precio) pero no bajar mientras el precio respete el stop. Si el cierre perfora esa banda, la tendencia se da por rota y el sistema pasa a corto; desde ese momento vigila la banda superior, que solo puede bajar.

Analogía. Un perro atado a una correa que solo se recoge: mientras el paseante (el precio) avanza, la correa se acorta y el perro (la banda) le sigue de cerca; si el paseante retrocede, la correa no se alarga. Cuando el paseante tira en sentido contrario con fuerza suficiente para cruzar al perro, se cambia de dirección y se ata otra correa por el otro lado.

Por qué podría funcionar. Combina momentum (seguir la dirección vigente) con una distancia de salida proporcional a la volatilidad: en mercados agitados el stop se aleja, en mercados tranquilos se ciñe. Evita salir por ruido "normal" para ese régimen.

Cuándo falla.

  • Mercados laterales con volatilidad baja: con multiplier pequeño las bandas se ciñen y cada oscilación genera una reversión de 2 unidades.
  • Gaps y huecos de sesión: el True Range de la barra tras un hueco usa el cierre anterior al hueco; la RMA lo arrastra ~5·period barras, ensancha las bandas y suprime giros legítimos (KO tiene 271 sesiones ausentes).
  • Costes: cada cambio de dirección mueve 2 unidades, así que paga doble comisión por señal frente a estrategias con estado plano.

Sesgos típicos al evaluarla.

  • Contar la semilla como acierto: el primer largo existe siempre, sea el mercado alcista o bajista.
  • Sobreajuste del multiplicador: 20 candidatos con 20 recursiones distintas dan mucho margen para elegir "el mejor" por azar.
  • Splits y dividendos: con precios sin ajustar, un split 4:1 genera un TR del 75 % del precio; usa total_return_adjusted.

Las reglas exactas

La regla canónica del catálogo: "Bandas básicas HL2±m·ATR(n). Banda superior final=min(básica,final previa), salvo que el cierre previo supere la superior previa, en cuyo caso se reinicia a básica; inferior simétrica con max y ruptura previa. Dirección inicial +1. Cambiar de -1 a +1 si C>superior final; de +1 a -1 si C<inferior final. Objetivo=dirección cuando es válido."

Toda la recursión vive en la feature supertrend; la política solo traduce direction (±1,0) a objetivo (±1).

Situación en el cierre de t Objetivo target[t] Motivo (reason)
t < period − 1 (ATR aún NaN) 0 WARMUP
t = period − 1 (siembra, direction = +1) +1 ENTRY
direction[t] = +1 +1 HOLD / REVERSAL
direction[t] = −1 −1 HOLD / REVERSAL
direction[t] NaN tardío el anterior HOLD
barra no operable (is_tradable = False) se congela el publicado NOT_TRADABLE

Recursión de la feature para t > s = period − 1:

basic_upper[t] = hl2[t] + m·atr[t]          basic_lower[t] = hl2[t] - m·atr[t]

upper_final[t] = basic_upper[t]  si basic_upper[t] < upper_final[t-1]  o  C[t-1] > upper_final[t-1]
                 upper_final[t-1] en otro caso
lower_final[t] = basic_lower[t]  si basic_lower[t] > lower_final[t-1]  o  C[t-1] < lower_final[t-1]
                 lower_final[t-1] en otro caso

direction[t] = +1  si direction[t-1] = -1 y C[t] > upper_final[t]
               -1  si direction[t-1] = +1 y C[t] < lower_final[t]
               direction[t-1] en otro caso
  • Entrada: en t = period − 1 siempre largo (semilla).
  • Salida: no hay estado plano; la salida de un largo es la reversión a corto.
  • Reversión: la única transición posible tras el warmup; delta de 2 unidades.
  • Desigualdades estrictas en los cuatro sitios: C[t] == upper_final[t] estando corto no invierte.
  • Filtros: ninguno.
# decisión en el cierre de t, fill en el open de t+1
target_prev = 0
para cada barra t:
    actualizar tr, atr, hl2, bandas finales y direction con datos hasta t
    si t < period - 1:            target = 0              # WARMUP
    si no, si direction[t] > 0:   target = +1
    si no, si direction[t] < 0:   target = -1
    si no:                        target = target_prev    # NaN tardío
    si target != target_prev:
        delta = target - target_prev   # +1 en la siembra, ±2 al invertir
        orden(delta) -> fill a open[t+1]
    target_prev = target

Las bandas comparan con t−1; la dirección, con t

La banda final de t usa la básica de t y el cierre de t−1 frente a la final de t−1. El cambio de dirección compara el cierre de t con la banda final de t. Todo se conoce al cierre de t, así que es causal. Usar C[t] en la cláusula de reinicio es la fuga clásica de las implementaciones caseras.

Los indicadores que usa

Feature Fórmula first_valid
tr TR[0] = H[0] − L[0]; después max(H − L, |H − Cprev|, |L − Cprev|) 0
hl2 (H + L)/2 0
atr{n} RMA de Wilder de tr: semilla SMA(TR[0..n−1]) en n−1; después atr[t] = atr[t−1] + (TR[t] − atr[t−1])/n n − 1
supertrend{n,m} bandas básicas hl2 ± m·atr, recursión de bandas finales y dirección (arriba); siembra direction[n−1] = +1 n − 1 (regla chained, no añade barras)

first_decision_index = period − 1: 9 con defaults, 20 con period = 21. Que TR[0] no sea NaN es lo que hace que el ATR valga ya en n − 1 y no en n.

En el proveedor VectorTA la recursión es NumPy propio sobre vector_ta.atr (variant = "custom_reference_on_vta_atr"): vector_ta.supertrend existe, pero usa otra semilla y devuelve una bandera 0/1, así que no se usa. Su tiempo no debe atribuirse a VectorTA. Más detalle en Las 23 primitivas de fase B, Indicadores paso a paso e Indicadores y features.

Ejemplo barra a barra

Fixture §6 de docs/estrategias/P06.md: period = 3, multiplier = 1.0 (fuera de rejilla a propósito, para seguir la recursión a mano), cash_inicial = 1.000,00, tick 0,01, sin costes. first_valid(atr{3}) = first_valid(supertrend) = 2first_decision_index = 2.

Las barras 0–1 son WARMUP (ATR NaN): t=0 O=99,50 H=101,00 L=99,00 C=100,00; t=1 O=100,50 H=102,00 L=100,00 C=101,00; ambas con TR = 2,000000, objetivo 0, sin órdenes.

t O H L C ATR(3) upper_final lower_final dir. target orden fill pos. PnL acum. sin costes equity con 5 bps*
2 101,50 103,00 101,00 102,00 2,000000 104,000000 100,000000 +1 +1 ENTRY (siembra) BUY 1 0 0,00 1.000,00
3 102,50 104,00 102,00 103,00 2,000000 104,000000 101,000000 +1 +1 HOLD 102,50 +1 0,50 1.000,45
4 103,50 105,00 102,00 103,00 2,333333 104,000000 101,166667 +1 +1 HOLD +1 0,50 1.000,45
5 102,50 103,00 100,00 101,00 2,555556 104,000000 101,166667 −1 −1 REVERSAL SELL 2 +1 −1,50 998,45
6 100,50 101,00 98,00 99,00 2,703704 102,203704 96,796296 −1 −1 HOLD 100,50 −1 −0,50 999,35
7 98,50 99,00 96,00 97,00 2,802469 100,302469 96,796296 −1 −1 HOLD −1 1,50 1.001,35
8 96,50 97,00 94,00 95,00 2,868313 98,368313 96,796296 −1 −1 HOLD −1 3,50 1.003,35
9 94,50 96,00 93,00 95,00 2,912209 97,412209 91,587791 −1 −1 HOLD −1 3,50 1.003,35
10 95,50 98,00 95,00 97,00 2,941472 97,412209 93,558528 −1 −1 HOLD −1 1,50 1.001,35
11 97,50 100,00 97,00 99,00 2,960982 97,412209 95,539018 +1 +1 REVERSAL BUY 2 −1 −0,50 999,35
12 99,50 102,00 99,00 101,00 2,973988 103,473988 97,526012 +1 +1 HOLD 99,50 +1 0,50 1.000,25
13 101,50 104,00 101,00 103,00 2,982658 103,473988 99,517342 +1 +1 HOLD +1 2,50 1.002,25
14 103,50 105,00 102,00 104,00 2,988439 103,473988 100,511561 +1 +1 HOLD +1 3,50 1.003,25
15 104,50 106,00 103,00 105,00 2,992293 107,492293 101,507707 +1 +1 HOLD +1 4,50 1.004,25

OHLC, ATR, bandas finales, dirección, objetivos, fills y equity (PnL = equity − 1.000,00) son literales del fixture §6 de docs/estrategias/P06.md; la columna "pos." indica la posición tras el fill en la apertura de esa barra. *"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:

  • fill 1 (t=3): 1 · 102,50 · 0,0005 = 0,05125 → 0,05
  • fill 2 (t=6): 2 · 100,50 · 0,0005 = 0,1005 → 0,10
  • fill 3 (t=12): 2 · 99,50 · 0,0005 = 0,0995 → 0,10

Comisiones acumuladas: 0,05 → 0,15 → 0,25. Equity final con 5 bps: 1.004,50 − 0,25 = 1.004,25.

Lo que hay que ver en la tabla:

  1. Barra 2: primera barra válida y primera entrada a la vez. No es una señal: es la semilla "Dirección inicial +1".
  2. Barras 3–5: upper_final se queda clavada en 104,000000 mientras la básica sube a 105,000000 y 105,833333 (regla del min).
  3. Barra 5: el largo se corta contra lower_final = 101,166667, conservada de la barra 4; la básica de la barra 5 estaba mucho más abajo (98,944444). Con una banda "rolling" independiente el corte no habría ocurrido.
  4. Barra 11: 99,00 > 97,412209 → reversión a largo, fill de 2 unidades en el open de 12 (99,50).
  5. Barras 12 y 15: reinicio de upper_final por ruptura del cierre previo (C[11] = 99,00 > 97,412209; C[14] = 104,00 > 103,473988). La posición larga queda abierta al final y se valora al último cierre.

Las cuentas de las barras clave

Siembra (t=2):   TR[0] = 101-99 = 2;  TR[1] = max(2, 2, 0) = 2;  TR[2] = max(2, 2, 0) = 2
                 ATR[2] = (2+2+2)/3 = 2,000000;  HL2[2] = (103+101)/2 = 102,000000
                 upper_final[2] = 102 + 1·2 = 104,000000;  lower_final[2] = 100,000000
                 direction[2] = +1  -> target +1 (ENTRY)
Reversión (t=5): TR[4] = max(3, 2, 1) = 3;  TR[5] = max(3, 0, 3) = 3
                 ATR[4] = (1/3)·3 + (2/3)·2    = 7/3  = 2,333333
                 ATR[5] = (1/3)·3 + (2/3)·(7/3) = 23/9 = 2,555556
                 basic_lower[5] = 101,50 - 2,555556 = 98,944444
                 ¿98,944444 > lower_final[4]=101,166667? No. ¿C[4]=103 < 101,166667? No.
                 -> lower_final[5] = 101,166667 (se conserva)
                 C[5] = 101,00 < 101,166667 -> direction -1 (REVERSAL, delta -2)
Reversión (t=11): C[11] = 99,00 > upper_final[11] = 97,412209 -> direction +1 (delta +2)
Reinicio (t=15): C[14] = 104,00 > upper_final[14] = 103,473988 -> upper_final[15] = básica = 107,492293
Caja:            1.000,00 - 102,50   = 897,50    (fill 1, open[3])
                 897,50 + 2·100,50   = 1.098,50  (fill 2, open[6])
                 1.098,50 - 2·99,50  = 899,50    (fill 3, open[12])
Equity final:    899,50 + 1·105,00   = 1.004,50

Sin la cláusula de reinicio, upper_final[15] se habría quedado en 103,473988, por debajo del cierre: el test test_p06_band_reset_on_previous_close_break de la especificación compara exactamente esa variante mutilada.

Diagrama

Supertrend: bandas finales, cambio de dirección y fills en la apertura siguiente

flowchart LR
    W(["WARMUP: t < period-1"]) -->|"t = period-1 · siembra · BUY 1"| L["Largo +1"]
    L -->|"C < lower_final · SELL 2"| S["Corto -1"]
    S -->|"C > upper_final · BUY 2"| L
    L -->|"C >= lower_final"| L
    S -->|"C <= upper_final"| S

No existe estado plano tras el warmup: el primer paso es siempre a largo y después solo hay reversiones de 2 unidades.

Cómo lanzarla

Backtest con ambos motores sobre NVDA 1d con los defaults (la estrategia se pasa por plantilla y parámetros, forma "inline" de la API de backtesting):

BASE=http://localhost:8000
SID=$(curl -sS -X POST "$BASE/v1/backtest" -H 'Content-Type: application/json' -d '{
  "request_id": "manual-p06-0001",
  "correlation_id": null,
  "schema_version": "v1",
  "payload": {
    "strategy": {"template_id": "P06", "params": {"period": 10, "multiplier": 3.0},
                 "name": "Supertrend 10/3.0 base", "register": true},
    "data": {"asset_id": "NVDA", "timeframe": "1d",
             "window": {"start": "2015-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": "P06", "params": {"period": 10, "multiplier": 3.0},
                 "register": True},
    "data": {"asset_id": "NVDA", "timeframe": "1d",
             "window": {"start": "2015-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"])

No pidas indicator_provider: "native" en Nautilus: para P06 es UNSUPPORTED por motivo terminal (Nautilus no tiene supertrend, tr ni hl2).

Derivar una variante con un multiplicador más ceñido:

curl -sS -X POST "$BASE/v1/strategies/$SID/derive" -H 'Content-Type: application/json' -d '{
  "request_id": "manual-p06-derive", "correlation_id": null, "schema_version": "v1",
  "payload": {"param_overrides": {"multiplier": 2.0}, "name": "Supertrend 10/2.0",
              "slug": "supertrend-10-2-0", "tags": ["sensibilidad"]}}' | jq '.payload.strategy_id, .payload.cache_forecast'

multiplier es parámetro de feature. Según la tabla de invalidación §5.3 de la especificación, cache_forecast debe anunciar aciertos en tr, hl2 y atr{10} y un fallo en supertrend{10, 2.0}: la recursión entera se recalcula porque min(básica, final previa) no se distribuye sobre el multiplicador. Cambiar period (10 → 14) falla además en atr{14}.

Sweep de sus variant_keys (rejilla completa del catálogo):

curl -sS -X POST "$BASE/v1/sweep" -H 'Content-Type: application/json' -d '{
  "request_id": "manual-p06-sweep", "correlation_id": null, "schema_version": "v1",
  "payload": {"base_strategy_id": "'"$SID"'",
    "grid": {"period": [7, 10, 14, 21], "multiplier": [1.5, 2.0, 2.5, 3.0, 4.0]},
    "candidate_policy": {"drop_invalid": true, "drop_duplicates": true,
                         "register_strategies": true,
                         "name_template": "{template_name} {period}/{multiplier}"},
    "data": {"asset_id": "NVDA", "timeframe": "1d",
             "window": {"start": "2015-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: 20 candidatos, los 20 válidos. El resultado se consulta en GET /v1/jobs/{job_id}.

Cuánto cuesta la rejilla

Concepto Cuenta
Candidatos cartesianos 4 period × 5 multiplier = 20
Inválidos 0 (todos cumplen period ≥ 1 y multiplier > 0)
Curvas compartidas tr (1) + hl2 (1) + atr{7,10,14,21} (4) = 6
Recursiones propias 20, una por pareja, 3 salidas cada una; no se comparten
Pasadas sin factorizar / con factorizar 80 / 26 (ahorro de 54 pasadas, 67,5 %)

Es el caso peor de invalidación del grupo: a diferencia de P04, donde cambiar k recombina curvas ya calculadas, aquí cada multiplier alimenta una recursión acoplada distinta. El bucle de la recursión es el coste dominante y el candidato natural a Numba. Un cambio de fee_rate o de initial_cash no toca features: solo rehace el ledger.

Qué mirar en los resultados

  • parity.verdict en los carriles soportados; con NT_ONLINE usa la ruta por defecto (stream propio sobre AtrStream), no la nativa.
  • Número de cambios de dirección: en P06 es igual al número de fills tras el primero, y la suma de |delta_q| = 2 × (fills − 1) + 1. Es un invariante comprobable.
  • Duración media de cada tramo direccional y fracción de barras en +1 frente a −1.
  • Diagnóstico de banda: fracción de barras con ATR == 0 (típico en 1min) y ATR media relativa al precio (atr/close); explican un número anómalo de giros en intradía.
  • Fracción de reinicios por ruptura del cierre previo: es lo que distingue esta implementación de una "rolling monótona".
  • Costes: pesan el doble por señal que en P02 o P11; dilo al comparar estrategias con 5 bps.
  • Aviso DATA_GAP_SESSIONS en KO: la cadena recursiva arrastra el TR del hueco; el resultado debe publicarse con la advertencia.
  • Paridad por salida: la primera divergencia se reporta por separado en upper_final, lower_final y direction, para distinguir "difiere el ATR" de "difiere la recursión".

Causalidad comprobada

El test de "futuros alterados" de la especificación (test_p06_future_perturbation): sobre NVDA 1d (600 barras, 10/3.0) se ensancha el rango de cada barra posterior a la 400 (high += 4, low −= 4, sin tocar el cierre), de modo que la perturbación entra solo por el True Range. Todo lo calculado hasta la barra 400 (TR, ATR, HL2, las tres salidas de supertrend, objetivos, motivos, fills con fill_index ≤ 400, caja y equity) debe quedar idéntico bit a bit. Es el test más exigente del grupo: una sola contaminación del ATR se propagaría a toda la recursión posterior, y la variante "hacia atrás" con np.minimum.accumulate lo suspende de inmediato.

Para reanudar en modo continue, el checkpoint necesita cinco escalares de feature: atr[t0−1], close[t0−1], upper_final[t0−1], lower_final[t0−1] y direction[t0−1]. Sin las bandas y la dirección no hay extensión incremental: es un replay completo.

Errores típicos

Error Consecuencia Cómo evitarlo
multiplier <= 0 PolicyParamError: las bandas colapsan en HL2 y el indicador se vuelve ruido La API lo rechaza; en un sweep cuenta como inválido y no desaparece del denominador
Usar C[t] en la cláusula de reinicio Otra señal, no reproducible entre motores La cláusula compara C[t−1] con upper_final[t−1]
Calcular la banda con np.minimum.accumulate Banda monótona que nunca vuelve a subir (falla en la barra 15 del fixture) La recursión es un bucle con reinicio; no es un rolling
Contar el primer largo como señal Sesgo en tasas de acierto Es la semilla direction = +1; exclúyelo del análisis
Reutilizar un stream incremental entre candidatos El segundo candidato hereda bandas del primero, con números plausibles Los adaptadores construyen streams nuevos por candidato (D-11)
Usar precios sin ajustar por splits TR gigantes y falsos que suprimen giros durante ~5·period barras price_basis = total_return_adjusted
Usar el Supertrend de VectorTA "porque existe" Otra semilla y bandera 0/1: otra estrategia La receta usa custom_reference_on_vta_atr

Variantes y extensiones

  • Solo largos: target_lots = [0, 1] y −1 → 0; habitual en acciones.
  • Supertrend con ATR de otra media (SMA o EMA del TR): exige su propia entrada de catálogo y otra formula_version.
  • Pariente directo: P07 UT Bot, otra parada ATR recursiva, pero con estado plano inicial y cruce del cierre sobre el stop.
  • Chandelier: P19 ancla la parada al máximo o mínimo de una ventana en vez de a HL2.
  • Parabolic SAR: P14, parada que acelera con el tiempo.
  • Filtro de régimen: combinarla con ADX (P15) para evitar los mercados laterales.

Resumen

  • P06 sigue una dirección continua (+1/−1) que solo cambia cuando el cierre cruza la banda final del lado contrario.
  • Las bandas finales son recursivas: se mueven solo a favor, salvo el reinicio cuando el cierre previo rompe la banda previa.
  • La primera entrada es la semilla direction = +1, no una señal; después cada cambio es una reversión de 2 unidades.
  • En el fixture: largo a 102,50, reversión a corto a 100,50, reversión a largo a 99,50; equity final 1.004,50 (1.004,25 con 5 bps, cálculo propio).
  • Paridad PASS en los carriles soportados; la variante nativa de Nautilus es UNSUPPORTED porque no existe el indicador.

Para practicar

  1. Con el fixture, calcula a mano ATR[6], basic_upper[6] y upper_final[6] a partir de ATR[5] = 23/9 y comprueba 2,703704 y 102,203704. ¿Qué condición (minU o brkU) hace que la banda cambie?
  2. Rehaz la columna "equity con 5 bps" con 10 bps. ¿Cuánto suman las comisiones? (Pista: 0,10 + 0,20 + 0,20.)
  3. Lanza el sweep 4×5 sobre NVDA 1d y KO 1d. Compara el número de fills entre multiplier = 1.5 y 4.0 con period = 10, y comprueba el invariante Σ|delta_q| = 2·(fills − 1) + 1.