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 (1min … 1d; 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 · ATRsirve 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_valuepequeñ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_rangeincorpora el salto respecto al cierre anterior; un gap overnight o un dividendo no ajustado inflan el ATR duranteperiodbarras y ensanchan el stop. - Giros en V: el stop está, por construcción, a
key_value · ATRdel 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.5en 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]ya[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) = 2 → first_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:
- 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 porquestop[1]es NaN. - 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. - 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).
- 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, noopen[10] = 9,80: ejecutar en la apertura del gap daría un beneficio fantasma de 0,80 por unidad. - 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.verdictantes que cualquier cifra. La matriz de paridad da PASS para P07 enNT_FEATURES,NT_INTENT_REPLAYyNT_ONLINE(vectorta)en los 12 casos (KO y NVDA en 1d y 1h, NVDA en 5min y 1min, con costes cero y 5 bps). EnNT_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_valuedemasiado pequeño). - Barras hasta el primer cruce: tiempo evaluado sin exposición por culpa de la semilla por abajo.
DATA_GAP_SESSIONSen 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 duranteperiodbarras. 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_losscon ATR det−1: la variante de algunas versiones de Pine; exige su propiaformula_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
hl2con 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 · ATRdel 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 det+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¶
- Con el fixture, calcula a mano
atr[8]ystop[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. - Rehaz la barra 10 suponiendo que la regla usara
stop[9]en lugar destop[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. - Lanza el sweep 5 × 4 sobre NVDA 1d y NVDA 1min. Compara el número de fills de
key_value = 0.5en ambos timeframes y discute si en 1min eso es una estrategia o ruido con comisiones.