Saltar a contenido

P07 · UT Bot: trailing ATR

Campo Valor
Familia Tendencia · stop dinámico recursivo por volatilidad; dirección continua con memoria (tras el primer cruce, siempre largo o corto)
Features requeridas atr_trailing_stop (salida stop), construida sobre atr{period}tr; la política lee además la columna close
Parámetros y defaults period = 10, key_value = 1.0
Rejilla del catálogo period ∈ {5, 10, 14, 21}, key_value ∈ {0.5, 1.0, 1.5, 2.0, 3.0} → 20 candidatos, todos válidos
variant_keys key_value, period
Restricciones period entero ≥ 1; key_value > 0 estricto (si no, PolicyParamError); sin restricción cruzada
first_decision_index period − 1 = 9 con defaults (20 en el peor punto de la rejilla, 4 en el mejor)
Activos y timeframes NVDA y KO en los 7 TF (1min1d; en 1min con key_value bajo degenera: n_loss < tick); AMZN, INTC, MSFT, TSLA en 1d; BME con total_return_adjusted; FX solo con OHLC reparado
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 'atr_trailing_stop'"

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

Nuestra receta, no el script de TradingView

El catálogo lo exige literalmente: "no afirmar paridad exacta con UT Bot hasta cotejar su versión". Esta ficha describe nuestra implementación de la familia ATR trailing, con semilla explícita. Si algún día se coteja con el original y difiere, será una variante, no un error.

Qué vas a aprender

  • Cómo un stop que "persigue" al precio a una distancia de key_value · ATR sirve a la vez de señal de entrada y de reversión.
  • Las cuatro ramas de la recursión del stop y por qué la rama C es la que "da la vuelta".
  • Por qué P07 arranca siempre esperando la primera bajada: la semilla se coloca por debajo del cierre.
  • A seguir en el fixture de la especificación una entrada corta, una reversión en una barra con gap y otra reversión.
  • Por qué key_value, que parece un multiplicador barato, obliga a recalcular toda la recursión.

La idea en una frase

Arrastrar un stop a key_value · ATR del cierre; cuando el cierre lo cruza hacia arriba, estar largo; cuando lo cruza hacia abajo, estar corto.

Intuición de mercado

Imagina una cuerda atada al precio que solo puede tensarse en una dirección. Mientras el precio sube, el stop sube detrás de él a una distancia proporcional a la volatilidad reciente (el ATR), pero nunca baja. Si el precio se da la vuelta y cae por debajo de la cuerda, el sistema concluye que la tendencia ha cambiado, invierte la posición y la cuerda salta al otro lado del precio, ahora por encima y solo pudiendo bajar.

Analogía. Un perro pastor que sigue al rebaño: cuando el rebaño avanza, el perro avanza detrás; si el rebaño retrocede de golpe y pasa por encima del perro, el perro corre al otro lado y empieza a empujar en la dirección contraria.

Por qué podría funcionar. Es un seguidor de tendencia con distancia adaptativa: en mercados tranquilos el stop se pega al precio y reacciona pronto; en mercados agitados se separa y aguanta el ruido. Así evita parte del whipsaw de un stop de distancia fija.

Cuándo falla.

  • Laterales con volatilidad baja: con key_value pequeño el stop queda a uno o dos ticks del cierre y cada vaivén es un cruce con reversión (delta 2, doble comisión).
  • Gaps y dividendos: el true_range incorpora el salto respecto al cierre anterior; un gap overnight o un dividendo no ajustado inflan el ATR durante period barras y ensanchan el stop.
  • Giros en V: el stop está, por construcción, a key_value · ATR del cierre; un giro rápido devuelve esa distancia antes de invertir.

Sesgos típicos al evaluarla.

  • Confundirla con el UT Bot de TradingView y comparar resultados con un script que usa otra semilla o el ATR de t−1.
  • Elegir key_value = 0.5 en 1min "porque sale más activo": es un régimen degenerado, no una estrategia.
  • Olvidar las barras sin exposición entre el warmup y el primer cruce: cuentan en el Sharpe.

Las reglas exactas

La regla canónica del catálogo: "nLoss=a·ATR(n); src=C, nunca Heikin Ashi. En el primer índice válido, stop=src-nLoss y objetivo=0. Si src y src_prev están sobre stop_prev, stop=max(stop_prev,src-nLoss); si ambos debajo, min(stop_prev,src+nLoss); en otro caso stop=src-nLoss si src>stop_prev y src+nLoss si no. Objetivo +1/-1 al cruce del cierre sobre/bajo el stop; mantener hasta cruce opuesto."

Situación en el cierre de t Objetivo target[t] Motivo (reason)
t < period − 1 (ATR aún NaN) 0 WARMUP
t = period − 1 (barra semilla: stop[t−1] es NaN, no puede haber cruce) 0 HOLD (decisión válida, no warmup)
close[t] > stop[t] y close[t−1] <= stop[t−1] (cruce al alza) +1 ENTRY / REVERSAL
close[t] < stop[t] y close[t−1] >= stop[t−1] (cruce a la baja) −1 ENTRY / REVERSAL
ningún cruce (incluido close[t] == stop[t]) el anterior HOLD
barra no operable (is_tradable = False) se congela el publicado NOT_TRADABLE
  • Entrada: el primer cruce tras la semilla. Como la semilla está por debajo del cierre, el primer evento posible es siempre un cruce a la baja.
  • Salida: no hay salida a plano; tras el primer cruce el sistema está siempre en mercado.
  • Reversión: permitida; delta de 2 unidades.
  • Convención de cruce: la general del proyecto (a[t] > b[t] y a[t−1] <= b[t−1]), no la de P04.
# decisión en el cierre de t, fill en el open de t+1
target_prev = 0
para cada barra t:
    si t < period - 1:                               target = 0            # WARMUP
    si no:
        stop[t] ya calculado (feature, datos hasta t inclusive)
        cruce_arriba = close[t] > stop[t] y close[t-1] <= stop[t-1]
        cruce_abajo  = close[t] < stop[t] y close[t-1] >= stop[t-1]
        si cruce_arriba:     target = +1
        si no, cruce_abajo:  target = -1
        si no:               target = target_prev                         # HOLD
    si target != target_prev:
        delta = target - target_prev                  # ±1 al entrar, ±2 al invertir
        orden(delta)  -> fill a open[t+1]             # nunca a close[t] ni a open[t]
    target_prev = target

El cruce se evalúa con el stop de t, no con el de t−1

La recursión usa stop[t−1] para calcular stop[t], y es muy fácil arrastrar ese stop[t−1] a la comparación del cruce. Sería otra regla: cruces una barra tarde o pronto. El orden correcto es: primero se recalcula el stop con el cierre de t; después se compara close[t] con ese stop nuevo.

Los indicadores que usa

Feature Fórmula first_valid
tr TR0 = H0 − L0; después max(H − L, |H − C_prev|, |L − C_prev|) 0
atr{period} RMA de Wilder: semilla SMA(TR[0..period−1]); después (TR[t] + (period−1)·ATR[t−1]) / period period − 1
atr_trailing_stop n_loss[t] = key_value · atr[t]; semilla stop[f] = close[f] − n_loss[f]; recursión de cuatro ramas (abajo) period − 1 (encadenada, +0)

Las cuatro ramas, con sp = stop[t−1], s = close[t], sq = close[t−1], nl = n_loss[t]:

Rama A  (ambos SOBRE sp):    s > sp y sq > sp  ->  stop = max(sp, s - nl)   # solo sube
Rama B  (ambos DEBAJO de sp): s < sp y sq < sp  ->  stop = min(sp, s + nl)   # solo baja
Rama C↑ (mixto, s > sp):                        ->  stop = s - nl           # da la vuelta: queda debajo
Rama C↓ (mixto, s <= sp, incluye empate):       ->  stop = s + nl           # da la vuelta: queda encima

Las desigualdades de A y B son estrictas: un cierre exactamente igual al stop cae en C↓ y coloca el stop nuevo por encima del cierre. n_loss[t] usa el ATR de t (causal: se calcula con datos hasta t); algunas versiones de Pine usan el de t−1, y eso es otra receta.

first_decision_index = period − 1 (regla D-12: sin +1 aunque la regla consulte t−1). La recursión del stop no tiene proveedor nativo en ningún motor: es custom_reference sobre el ATR de VectorTA, el mismo patrón que P06 Supertrend. Detalle del TR y el ATR en Indicadores paso a paso y de la primitiva en Las 23 primitivas de fase B.

Ejemplo barra a barra

Fixture §6 de docs/estrategias/P07.md: period = 3 (fuera de rejilla a propósito), key_value = 1.0, cash_inicial = 1.000,00, tick 0,01, sin costes. first_valid(atr) = first_valid(stop) = 2first_decision_index = 2. El fixture tiene 16 barras; las barras 0–1 son warmup (TR 0,1000 y 0,4000, ATR NaN) y se omiten. La tabla muestra las 14 barras 2–15.

t O H L C atr stop rama target orden fill pos. equity sin costes equity con 5 bps*
2 10,30 10,35 10,05 10,10 0,266667 9,833333 S 0 HOLD 0 1.000,00 1.000,00
3 10,10 10,55 10,05 10,50 0,344444 10,155556 A 0 HOLD 0 1.000,00 1.000,00
4 10,50 10,95 10,45 10,90 0,396296 10,503704 A 0 HOLD 0 1.000,00 1.000,00
5 10,90 11,25 10,85 11,20 0,397531 10,802469 A 0 HOLD 0 1.000,00 1.000,00
6 11,20 11,25 10,85 10,90 0,398354 10,802469 A 0 HOLD 0 1.000,00 1.000,00
7 10,90 10,95 10,35 10,40 0,465569 10,865569 C↓ −1 ENTRY SELL 1 0 1.000,00 1.000,00
8 10,40 10,45 9,95 10,00 0,477046 10,477046 B −1 10,40 −1 1.000,40 1.000,39
9 10,00 10,05 9,75 9,80 0,418031 10,218031 B −1 −1 1.000,60 1.000,59
10 9,80 10,65 9,75 10,60 0,578687 10,021313 C↑ +1 REVERSAL BUY 2 −1 999,80 999,79
11 10,60 11,05 10,55 11,00 0,552458 10,447542 A +1 10,60 +1 1.000,20 1.000,18
12 11,00 11,35 10,95 11,30 0,501639 10,798361 A +1 +1 1.000,50 1.000,48
13 11,30 11,35 11,05 11,10 0,434426 10,798361 A +1 +1 1.000,30 1.000,28
14 11,10 11,15 10,65 10,70 0,456284 11,156284 C↓ −1 REVERSAL SELL 2 +1 999,90 999,88
15 10,70 10,75 10,25 10,30 0,470856 10,770856 B −1 10,70 −1 1.000,30 1.000,27

OHLC, atr, stop, rama, objetivos, motivos y fills son literales del fixture (con key_value = 1, n_loss = atr). *Las dos columnas de equity son cálculo de este manual: equity sin costes = caja + posición · cierre, a partir de los fills del fixture (el fixture da explícitamente 1.000,40 en la barra 8 y 1.000,20 en la 11, que coinciden); equity con 5 bps = 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 → 10,40 · 0,0005 = 0,0052 → 0,01; fill 2 → 2 · 10,60 · 0,0005 = 0,0106 → 0,01; fill 3 → 2 · 10,70 · 0,0005 = 0,0107 → 0,01. Con precios de 10 €, los 5 bps apenas son un céntimo por fill.

Lo que hay que ver en la tabla:

  1. Barra 2 (semilla): stop = 10,10 − 0,266667 = 9,833333, por debajo del cierre. Hay decisión (objetivo 0, HOLD), no warmup: el cruce no puede dispararse porque stop[1] es NaN.
  2. Barras 3–6 (rama A): el stop sube (o se queda: en la barra 6 max(10,802469, 10,90 − 0,398354) conserva 10,802469). Objetivo 0: para cruzar hacia arriba hay que venir de abajo, y el cierre nunca ha estado debajo.
  3. Barra 7: cierre 10,40 bajo el stop anterior 10,802469 con el cierre previo encima → rama C↓, el stop salta por encima del cierre (10,865569) y hay cruce a la baja. Fill en el open de 8 (10,40).
  4. Barra 10, el test de gap: abre en 9,80 (por debajo del stop 10,218031) y cierra en 10,60 (por encima). El cierre previo (9,80) estaba debajo, así que es rama C↑, no A. El fill de la reversión es open[11] = 10,60, no open[10] = 9,80: ejecutar en la apertura del gap daría un beneficio fantasma de 0,80 por unidad.
  5. Barra 14: otra rama C↓ y reversión a corto; fill en el open de 15 (10,70). La posición corta queda abierta al final, sin liquidación automática.

Las cuentas de las barras clave

TR y ATR de la semilla (t=2):  tr = 0,1000 ; 0,4000 ; 0,3000
                               atr[2] = (0,10 + 0,40 + 0,30)/3 = 0,2666667
                               stop[2] = 10,10 - 0,2666667 = 9,8333333             (S)
Entrada (t=7):                 tr[7] = max(0,60 ; 0,05 ; 0,55) = 0,6000
                               atr[7] = (0,6000 + 2·0,3983539)/3 = 0,4655693
                               s=10,40 < sp=10,8024691 ; sq=10,90 > sp  -> rama C↓
                               stop[7] = 10,40 + 0,4655693 = 10,8655693
                               10,40 < 10,8655693 y 10,90 >= 10,8024691  -> cruce abajo, -1
Gap (t=10):                    tr[10] = max(0,90 ; 0,85 ; 0,05) = 0,9000
                               atr[10] = (0,9000 + 2·0,4180308)/3 = 0,5786872
                               s=10,60 > sp=10,2180308 ; sq=9,80 < sp  -> rama C↑
                               stop[10] = 10,60 - 0,5786872 = 10,0213128
                               10,60 > 10,0213128 y 9,80 <= 10,2180308   -> cruce arriba, +1
Caja:                          1.000,00 + 10,40         = 1.010,40  (SELL 1 en open[8])
                               1.010,40 - 2·10,60       =   989,20  (BUY 2 en open[11])
                               989,20 + 2·10,70         = 1.010,60  (SELL 2 en open[15])
Equity final:                  1.010,60 + (-1)·10,30    = 1.000,30

Diagrama

flowchart LR
    W(["WARMUP: t < period-1"]) -->|"t = period-1 · semilla stop = C - nl"| E["Plano 0 (esperando el primer cruce)"]
    E -->|"C cruza el stop hacia abajo · SELL 1"| S["Corto -1 · stop encima, solo baja (rama B)"]
    E -->|"C cruza el stop hacia arriba · BUY 1"| L["Largo +1 · stop debajo, solo sube (rama A)"]
    L -->|"cruce abajo · rama C↓ · SELL 2"| S
    S -->|"cruce arriba · rama C↑ · BUY 2"| L
    L -->|"sin cruce"| L
    S -->|"sin cruce"| S

Con la semilla por debajo, la rama "plano → largo" solo es alcanzable si el cierre cae primero bajo el stop sin cruzarlo (imposible por la convención de cruce): en la práctica la primera posición es siempre corta, como en el fixture.

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; plantilla de petición tomada de 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-p07-0001",
  "correlation_id": null,
  "schema_version": "v1",
  "payload": {
    "strategy": {"template_id": "P07", "params": {"period": 10, "key_value": 1.0},
                 "name": "UT Bot 10/1.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": "P07", "params": {"period": 10, "key_value": 1.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"])
for res in r["results"]:
    print(res["adapter"], res["run_metrics"]["final_equity"], res["run_metrics"]["n_fills"])

No pidas indicator_provider: "native" en Nautilus: para P07 es UNSUPPORTED por motivo terminal (ningún indicador de Nautilus implementa esta recursión y su AverageTrueRange usa otra semilla que no converge a la canónica).

Derivar una variante con un stop más holgado:

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

key_value es parámetro de feature. Según la tabla de invalidación §5.3 de la especificación, se reutilizan tr y atr{10} y se rehace la recursión entera del stop: key_value entra dentro de un max/min encadenado, así que el stop de 2.0 no se puede derivar del de 1.0. Cambiar period invalida además atr{period}.

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-p07-sweep", "correlation_id": null, "schema_version": "v1",
  "payload": {"base_strategy_id": "'"$SID"'",
    "grid": {"period": [5, 10, 14, 21], "key_value": [0.5, 1.0, 1.5, 2.0, 3.0]},
    "candidate_policy": {"drop_invalid": true, "drop_duplicates": true,
                         "register_strategies": true,
                         "name_template": "{template_name} {period}/{key_value}"},
    "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'

El resultado se consulta en GET /v1/jobs/{job_id}.

Cuánto cuesta la rejilla

Concepto Cuenta
Candidatos cartesianos 4 period × 5 key_value = 20
Inválidos 0 (ningún period < 1, ningún key_value <= 0)
tr distintas 1 (no depende de parámetros; compartida con P06, P08, P16…)
atr{period} distintas 4, cada una compartida por los 5 key_value
Recursiones de stop 20, una por candidato; no se factorizan
Políticas y ledgers 20 y 20

Lectura honesta: el ATR se ahorra un 80 % (20 → 4), pero el kernel caro es la recursión del stop, que no se ahorra nada. Informar solo "4 ATR de 20" daría una impresión falsa de factorización.

Qué mirar en los resultados

  • parity.verdict antes que cualquier cifra. La matriz de paridad da PASS para P07 en NT_FEATURES, NT_INTENT_REPLAY y NT_ONLINE(vectorta) en los 12 casos (KO y NVDA en 1d y 1h, NVDA en 5min y 1min, con costes cero y 5 bps). En NT_ONLINE(native) el estado es UNSUPPORTED con el motivo literal "nautilus_trader 1.231.0 no ofrece un indicador nativo para 'atr_trailing_stop'": no hay nada que comparar, y no se finge.
  • Número de fills y reversiones: todas las transiciones posteriores a la primera son reversiones de 2 unidades. En la matriz, con los defaults, el motor de referencia registra 178 fills en KO 1d y 243 en NVDA 1d; en NVDA 1min, 8.289 sobre 60.000 barras. Un número de fills cercano al de barras delata el régimen degenerado.
  • Cruces por 1.000 barras y barras con n_loss < tick: es la métrica diagnóstica de P07. La especificación propone marcar como degenerado (sin eliminarlo del denominador) un candidato con más del 5 % de barras en ese régimen.
  • Distribución de ramas (A, B, C↑, C↓): una serie sana pasa la mayoría de barras en A o B; mucha C significa que el stop da la vuelta constantemente (key_value demasiado pequeño).
  • Barras hasta el primer cruce: tiempo evaluado sin exposición por culpa de la semilla por abajo.
  • DATA_GAP_SESSIONS en KO (271 sesiones ausentes): la primera barra tras el hueco lleva un TR con todo el salto acumulado, infla el ATR y ensancha el stop durante period barras. La recursión atraviesa el hueco sin reiniciarse; el aviso da trazabilidad, no corrección.

Causalidad comprobada

El test de futuros alterados de la especificación usa 300 barras sintéticas con (10, 1.0): multiplica por 1,5 high, low y close a partir de t0 = 200 y exige que tr, atr, stop, cruces, objetivos, motivos y fills con fill_index ≤ 200 queden idénticos bit a bit. El stop es la columna crítica: una recursión suave esconde muy bien un desplazamiento de una barra. Un segundo test perturba solo la barra t0 y comprueba que stop[t0−1] no cambia y stop[t0] sí (la recursión solo propaga hacia delante). Un tercero comprueba que el stop no depende del capital, las comisiones ni la posición inicial.

Para reanudar en modo continue hacen falta cuatro escalares: close[t0−1], atr[t0−1], stop[t0−1] y el objetivo vigente (más los metadatos). Guardar solo el stop no basta: sin el ATR no se calcula n_loss y sin el cierre no se calcula el TR ni la rama.

Errores típicos

Error Consecuencia Cómo evitarlo
key_value <= 0 PolicyParamError (el stop colapsa sobre el cierre y la regla deja de estar definida) La API lo rechaza; en un sweep cuenta como inválido
Comparar el cruce con stop[t−1] Cruces una barra tarde o pronto Recalcular el stop con close[t] y comparar con stop[t]
Mirar solo close[t] en las ramas A/B En un gap toma A en vez de C y deja el stop mal colocado Las ramas exigen close[t] y close[t−1] del mismo lado
Sembrar el stop "según la tendencia" Otra receta (la de algunas versiones de UT Bot) La semilla es siempre close − n_loss
Usar atr[t−1] en n_loss[t] Stops distintos a los del motor n_loss[t] = key_value · atr[t]
Llenar la reversión de un gap en open[t] Beneficio fantasma (0,80 por unidad en el fixture) El ledger llena siempre en open[t+1]
Tratar key_value como el k barato de P04 Planificador que "deriva" un stop de otro Cada key_value es una recursión nueva
Correr BME sin ajuste por dividendos 26 episodios de stop artificialmente ancho en BBVA.MC price_basis = total_return_adjusted es obligatorio en .MC

Variantes y extensiones

  • UT Bot con Heikin Ashi: el catálogo lo excluye (src = C); sería otra entrada.
  • n_loss con ATR de t−1: la variante de algunas versiones de Pine; exige su propia formula_version.
  • Solo largos: target_lots = [0, 1] con la regla −1 → 0; útil en acciones sin préstamo de títulos.
  • Filtro de régimen: operar solo con ADX alto (P15) para evitar los laterales donde el stop gira sin parar.
  • Parientes: P06 Supertrend (stop ATR sobre hl2 con bandas finales), P19 Chandelier (stop desde máximos/mínimos) y P14 Parabolic SAR (stop que acelera).

Resumen

  • P07 arrastra un stop a key_value · ATR del cierre: sube (rama A) o baja (rama B) sin retroceder, y salta al otro lado (rama C) cuando el cierre lo cruza.
  • El objetivo cambia solo en cruces: +1 al alza, −1 a la baja; se mantiene entre cruces y las reversiones mueven 2 unidades.
  • La semilla está siempre por debajo del cierre, así que la primera posición es corta.
  • Decisión al cierre de t, fill en el open de t+1; el fixture lo ilustra con un gap en la barra 10 que se ejecuta a 10,60 y no a 9,80.
  • Paridad PASS en todos los carriles con features precalculadas o de VectorTA; la ruta nativa de Nautilus es UNSUPPORTED porque la recursión no existe allí.

Para practicar

  1. Con el fixture, calcula a mano atr[8] y stop[8] a partir de los valores de la barra 7 (tr[8] = 0,5000) e identifica la rama. Debes obtener 0,477046, 10,477046 y rama B.
  2. Rehaz la barra 10 suponiendo que la regla usara stop[9] en lugar de stop[10] para el cruce. ¿Cambia la señal en este fixture? Construye un gap algo menor (por ejemplo, cierre 10,25) en el que sí cambie.
  3. Lanza el sweep 5 × 4 sobre NVDA 1d y NVDA 1min. Compara el número de fills de key_value = 0.5 en ambos timeframes y discute si en 1min eso es una estrategia o ruido con comisiones.