P15 · DMI/ADX: tendencia filtrada¶
| Campo | Valor |
|---|---|
| Familia | Tendencia · filtro de fuerza con histéresis de tres zonas (puede estar plana tras el warmup) |
| Features requeridas | dmi (primitiva nueva de fase B, entradas high, low, close; salidas plus_di, minus_di, adx; depende de atr) |
| Parámetros y defaults | period = 14 (feature), entry_adx = 25, exit_adx = 20 (política) |
| Rejilla del catálogo | period ∈ {7, 14, 21, 28}, entry_adx ∈ {20, 25, 30}, exit_adx ∈ {15, 20} → 24 cartesianos, 20 válidos, 4 curvas dmi |
variant_keys |
period, entry_adx, exit_adx |
| Restricciones | period >= 2 y 0 <= exit_adx < entry_adx <= 100 (si no, PolicyParamError) |
first_decision_index |
2·period − 2 = 26 con defaults (54 con period = 28) |
| Activos y timeframes | NVDA y KO en los 7 TF (1min … 1d); 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). UNSUPPORTED en NT_ONLINE(native) (12/12): "nautilus_trader 1.231.0 no ofrece un indicador nativo para 'dmi'". Nautilus tiene DirectionalMovement, pero sin ADX, y sin ADX no hay zonas |
Fuentes: docs/estrategias/P15.md (especificación), catalog/strategy_catalog.json, docs/DATOS_POR_ESTRATEGIA.md, runs/parity/summary.json.
Qué vas a aprender¶
- La diferencia entre fuerza de tendencia (ADX) y dirección (+DI frente a −DI), y por qué la regla nunca las mezcla.
- Cómo funciona una histéresis de tres zonas: entrar arriba de un umbral, salir abajo de otro y no hacer nada en medio.
- La cadena completa TR → ATR → DM suavizados → DI → DX → ADX y por qué el ADX tarda
2·period − 2barras. - Por qué un empate "decimal" de movimiento direccional no es un empate en
float64. - Por qué 20 candidatos solo necesitan 4 curvas.
La idea en una frase¶
Cuando la tendencia es fuerte (ADX alto), ponte del lado del indicador direccional dominante; cuando se debilita del todo (ADX bajo), sal a plano; en la franja intermedia, no toques nada.
Intuición de mercado¶
El sistema direccional de Wilder separa dos preguntas. +DI y −DI miden qué parte del rango reciente se ha debido a subidas y qué parte a bajadas: responden "¿hacia dónde?". El ADX promedia lo desequilibrados que están los dos DI: responde "¿con cuánta convicción?", sin decir hacia dónde. Un ADX de 95 no es alcista ni bajista; es "muy direccional".
P15 opera solo cuando hay convicción. La histéresis evita el parpadeo: si entraras y salieras con el mismo umbral, un ADX que oscila alrededor de 25 abriría y cerraría posición cada barra.
Analogía. El termostato de una calefacción: enciende a 19 °C y apaga a 21 °C. Entre medias no cambia de estado. Si encendiera y apagara a 20 °C exactos, la caldera no pararía de arrancar.
Por qué podría funcionar. Los sistemas de tendencia pierden sobre todo en mercados laterales. Un filtro de fuerza intenta quedarse fuera precisamente ahí.
Cuándo falla.
- El ADX llega tarde: es una media de una media; cuando confirma la tendencia, buena parte ya ha pasado.
- Tendencias que se agotan con ADX aún alto: la zona media conserva la posición mientras el ADX baja lentamente hacia
exit_adx. - Activos con ADX estructuralmente bajo: si el ADX de un activo casi nunca supera 30, el candidato con
entry_adx = 30apenas opera y sus métricas no significan nada.
Sesgos típicos al evaluarla.
- Confundir ADX con dirección: el ADX nunca decide el signo.
- Umbrales elegidos sobre el mismo periodo que evalúas: mira antes la distribución del ADX del activo.
- Borrar los candidatos inválidos del denominador: los 4 candidatos con
exit_adx = entry_adx = 20se rechazan, pero cuentan en la rejilla.
Las reglas exactas¶
La regla canónica del catálogo: "Cuando ADX>=entry_adx, objetivo +1 si +DI>−DI y −1 si −DI>+DI. Cuando ADX<exit_adx, objetivo=0. Entre los umbrales, conservar objetivo. Igualdad DI conserva. No confundir ADX, que mide fuerza, con dirección."
Situación en el cierre de t |
Objetivo target[t] |
Motivo (reason) |
|---|---|---|
t < 2·period − 2 (ADX aún NaN) |
0 | WARMUP |
Zona alta adx[t] >= entry_adx y +DI > −DI |
+1 | ENTRY / REVERSAL / HOLD |
Zona alta y −DI > +DI |
−1 | ENTRY / REVERSAL / HOLD |
Zona alta y +DI == −DI |
el anterior | HOLD |
Zona baja adx[t] < exit_adx |
0, sin mirar los DI | EXIT / HOLD |
Zona media exit_adx <= adx[t] < entry_adx |
el anterior | HOLD |
| barra no operable | se congela el publicado; el estado interno avanza | NOT_TRADABLE |
- Entrada: desde plano, solo en zona alta.
- Salida: a plano, solo en zona baja.
adx == exit_adxcae en zona media y no sale. - Reversión: sí, directa en zona alta si cambia el DI dominante (delta 2). Es la diferencia con P16 y P17.
- Filtros: el propio ADX es el filtro.
# decisión en el cierre de t, fill en el open de t+1
target_prev = 0
para cada barra t:
si t < 2*period - 2: target = 0 # WARMUP
si no, si adx[t] >= entry_adx: # zona alta
si plus_di[t] > minus_di[t]: target = +1
si no, minus_di[t] > plus_di[t]: target = -1
si no: target = target_prev # empate DI
si no, si adx[t] < exit_adx: target = 0 # zona baja
si no: target = target_prev # zona media
si target != target_prev:
orden(target - target_prev) -> fill a open[t+1] # ±1 o ±2
target_prev = target
Las tres zonas no se solapan porque exit_adx < entry_adx
[entry, ∞), (−∞, exit) y [exit, entry) cubren la recta sin huecos. Con exit_adx == entry_adx la zona media desaparece y deja de haber histéresis; con exit_adx > entry_adx una misma barra pediría dos objetivos. Por eso ambas combinaciones son PolicyParamError.
Los indicadores que usa¶
| Paso | Fórmula | first_valid |
|---|---|---|
TR |
max(H−L, abs(H−C[t−1]), abs(L−C[t−1])); TR[0] = H0 − L0 |
0 |
+DM, −DM |
up = H[t]−H[t−1], down = L[t−1]−L[t]; +DM = up si up > down y up > 0; −DM = down si down > up y down > 0; si no 0. DM[0] = 0 |
0 |
ATR, S+DM, S−DM |
RMA de Wilder (α = 1/n, semilla SMA de los n primeros) |
n − 1 |
+DI, −DI |
100 · S±DM / ATR (0 si ATR == 0), escala 0..100 |
n − 1 = 13 |
DX |
100 · abs(+DI − −DI) / (+DI + −DI) (0 si el denominador es 0) |
n − 1 |
adx |
semilla mean(DX[n−1 .. 2n−2]), luego ADX[t] = ADX[t−1] + (DX[t] − ADX[t−1]) / n |
2n − 2 = 26 |
first_decision_index = 2·period − 2: los DI están listos en la barra 13, pero la política no decide hasta que las tres salidas lo están. Con la rejilla: 12, 26, 40 y 54.
La especificación elige custom_reference como proveedor porque el adx de VectorTA mide first_valid = 27 frente al 26 canónico: una barra de diferencia en la semilla desplaza toda la curva y el punto en que se cruza entry_adx. Detalle en Las 23 primitivas de fase B y en Indicadores y features.
Ejemplo barra a barra¶
Fixture §6 de docs/estrategias/P15.md: 16 barras, parámetros fuera de rejilla a propósito para que las tres zonas aparezcan en 16 barras: period = 2, entry_adx = 62, exit_adx = 40 (emiten PolicyParamWarning, no error). cash_inicial = 1.000,00, tick 0,01, sin costes. first_valid(DI) = 1, first_valid(adx) = 2 → first_decision_index = 2.
Se muestran 14 de las 16 barras: se omiten la barra 0 (warmup, O=10,00 H=10,40 L=9,90 C=10,30) y la 4 (zona alta, +1 mantenido, ADX 100,0000).
| t | O | H | L | C | +DI | −DI | ADX | zona | target | orden | fill | pos. | PnL acum. sin costes | equity con 5 bps* |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 10,30 | 10,80 | 10,20 | 10,70 | 36,3636 | 0,0000 | NaN | — | 0 WARMUP | — | — | 0 | 0,00 | 1.000,00 |
| 2 | 10,70 | 11,20 | 10,60 | 11,10 | 52,1739 | 0,0000 | 100,0000 | alta | +1 ENTRY | BUY 1 | — | 0 | 0,00 | 1.000,00 |
| 3 | 11,10 | 11,60 | 11,00 | 11,50 | 59,5745 | 0,0000 | 100,0000 | alta | +1 | — | 11,10 | +1 | 0,40 | 1.000,39 |
| 5 | 11,60 | 11,75 | 11,35 | 11,45 | 34,4371 | 5,2980 | 86,6667 | alta | +1 | — | — | +1 | 0,35 | 1.000,34 |
| 6 | 11,45 | 11,70 | 11,30 | 11,55 | 18,6380 | 8,6022 | 61,7544 | media | +1 | — | — | +1 | 0,45 | 1.000,44 |
| 7 | 11,55 | 11,65 | 11,25 | 11,40 | 9,7196 | 10,4673 | 32,7290 | baja | 0 EXIT | SELL 1 | — | +1 | 0,30 | 1.000,29 |
| 8 | 11,40 | 11,50 | 10,95 | 11,00 | 4,1969 | 35,5125 | 55,7954 | media | 0 | — | 11,40 | 0 | 0,30 | 1.000,28 |
| 9 | 11,00 | 11,10 | 10,55 | 10,60 | 1,9645 | 55,3079 | 74,4676 | alta | −1 ENTRY | SELL 1 | — | 0 | 0,30 | 1.000,28 |
| 10 | 10,60 | 10,70 | 10,15 | 10,20 | 0,9519 | 64,2870 | 85,7748 | alta | −1 | — | 10,60 | −1 | 0,70 | 1.000,67 |
| 11 | 10,20 | 10,30 | 9,85 | 9,90 | 0,5163 | 65,3758 | 92,1038 | alta | −1 | — | — | −1 | 1,00 | 1.000,97 |
| 12 | 9,90 | 10,00 | 9,40 | 9,60 | 0,2326 | 70,6651 | 95,7239 | alta | −1 | — | — | −1 | 1,30 | 1.001,27 |
| 13 | 9,60 | 10,05 | 9,35 | 10,00 | 0,1019 | 30,9616 | 97,5339 | alta | −1 (empate DM) | — | — | −1 | 0,90 | 1.000,87 |
| 14 | 10,00 | 10,35 | 9,90 | 10,30 | 28,0198 | 17,9760 | 59,6851 | media | −1 | — | — | −1 | 0,60 | 1.000,57 |
| 15 | 10,30 | 10,70 | 10,25 | 10,65 | 50,7180 | 9,7758 | 63,6825 | alta | +1 REVERSAL | BUY 2 | sin fill | −1 | 0,25 | 1.000,22 |
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: fill 1 → 11,10 · 0,0005 = 0,00555 → 0,01; fill 2 → 11,40 · 0,0005 = 0,0057 → 0,01; fill 3 → 10,60 · 0,0005 = 0,0053 → 0,01. El PnL acumulado es equity − 1.000,00 del fixture.
Lo que hay que ver en la tabla:
- Barra 1: los DI ya existen, pero el ADX no; la política no decide.
- Barra 6: ADX 61,75, un pelo por debajo de 62. No es zona alta ni baja: se conserva el largo. Es el caso propio de P15.
- Barra 7: ADX 32,73 < 40 → a plano, aunque
−DI > +DI. La zona baja ignora los DI por completo. - Barra 8: el ADX rebota a 55,8, por encima de 40 pero por debajo de 62. No se reabre: la histéresis también funciona desde plano.
- Barra 14:
+DIya domina (28,02 > 17,98), pero el ADX está en zona media: se conserva el corto. - Barra 15: zona alta con
+DIdominante → reversión directa de −1 a +1 (delta 2), sin pasar por 0. No hay barra 16, así que queda sin fill; el corto se valora al último cierre y la equity final es1.010,90 − 10,65 = 1.000,25.
Las cuentas de las barras clave¶
Primera decisión (t=2), period = 2:
TR[0..2] = 0,50 ; 0,60 ; 0,60
ATR[1] = (0,50 + 0,60)/2 = 0,5500 ATR[2] = 0,55 + (0,60 - 0,55)/2 = 0,5750
+DM[0..2] = 0 (DM0 = 0) ; 0,40 ; 0,40 -DM = 0 ; 0 ; 0
S+DM[1] = (0 + 0,40)/2 = 0,2000 S+DM[2] = 0,20 + (0,40 - 0,20)/2 = 0,3000
+DI[2] = 100 · 0,3000 / 0,5750 = 52,1739 -DI[2] = 0
DX[1] = DX[2] = 100 ADX[2] = mean(DX[1], DX[2]) = 100,0000
100 >= 62 y 52,17 > 0 -> +1 ENTRY
Salida (t=7):
up = 11,65 - 11,70 = -0,05 ; down = 11,30 - 11,25 = +0,05 -> +DM = 0 ; -DM = 0,05
ATR[7] = 0,4180 ; +DI = 9,7196 ; -DI = 10,4673
DX[7] = 100 · 0,7477 / 20,1869 = 3,7037
ADX[7] = 61,7544 + (3,7037 - 61,7544)/2 = 32,7290 < 40 -> 0 EXIT
Caja: 1.000,00 - 11,10 = 988,90 (open[3]) ; + 11,40 = 1.000,30 (open[8]) ; + 10,60 = 1.010,90 (open[10])
Equity final: 1.010,90 + (-1)·10,65 = 1.000,25
El empate de la barra 13. El fixture elige H12 = 10,00, H13 = 10,05, L12 = 9,40, L13 = 9,35 porque 10.05 − 10.00 y 9.40 − 9.35 dan el mismo float64 (0.05000000000000071). Con la regla estricta, up > down y down > up son ambas falsas y los dos DM valen 0. Si se hubieran elegido L12 = 9,60, L13 = 9,55, la resta binaria daría 0.049999999999998934 y el "empate" desaparecería. Con >= en lugar de >, +DM[13] valdría 0,05 y +DI[13] pasaría de 0,1019 a 4,1099.
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 lo advierte y usa otro fixture con gap para ese test.
Diagrama¶
flowchart LR
W(["WARMUP: t < 2·period-2"]) --> FLAT["Plano 0"]
FLAT -->|"ADX >= entry y +DI > -DI · BUY 1"| L["Largo +1"]
FLAT -->|"ADX >= entry y -DI > +DI · SELL 1"| S["Corto -1"]
L -->|"ADX < exit · SELL 1"| FLAT
S -->|"ADX < exit · BUY 1"| FLAT
L -->|"ADX >= entry y -DI > +DI · SELL 2"| S
S -->|"ADX >= entry y +DI > -DI · BUY 2"| L
L -->|"zona media o empate DI"| L
S -->|"zona media o empate DI"| S
FLAT -->|"zona media o baja"| FLAT
Cómo lanzarla¶
Backtest con ambos motores sobre KO diario (2015–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-p15-0001",
"correlation_id": null,
"schema_version": "v1",
"payload": {
"strategy": {"template_id": "P15",
"params": {"period": 14, "entry_adx": 25, "exit_adx": 20},
"name": "DMI/ADX 14 25/20 base", "register": true},
"data": {"asset_id": "KO", "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": "P15",
"params": {"period": 14, "entry_adx": 25, "exit_adx": 20},
"register": True},
"data": {"asset_id": "KO", "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"],
res["run_metrics"].get("warnings"))
Con KO espera el aviso DATA_GAP_SESSIONS (271 sesiones ausentes): los RMA continúan a través del hueco.
Derivar una variante más exigente para entrar:
curl -sS -X POST "$BASE/v1/strategies/$SID/derive" -H 'Content-Type: application/json' -d '{
"request_id": "manual-p15-derive", "correlation_id": null, "schema_version": "v1",
"payload": {"param_overrides": {"entry_adx": 30}, "name": "DMI/ADX 14 30/20",
"slug": "dmi-adx-14-30-20", "tags": ["sensibilidad"]}}' | jq '.payload.strategy_id, .payload.cache_forecast'
entry_adx (igual que exit_adx) es parámetro de política: según §5.3 de la especificación, la curva dmi se reutiliza entera y solo se rehacen política y ledger. El cache_forecast debe anunciar dmi como acierto esperado y ningún fallo. Si cambiaras period, en cambio, habría que recalcular ATR, los dos RMA de DM, los DI, el DX y el ADX; solo se reutilizaría tr.
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-p15-sweep", "correlation_id": null, "schema_version": "v1",
"payload": {"base_strategy_id": "'"$SID"'",
"grid": {"period": [7, 14, 21, 28], "entry_adx": [20, 25, 30], "exit_adx": [15, 20]},
"candidate_policy": {"drop_invalid": true, "drop_duplicates": true,
"register_strategies": true,
"name_template": "{template_name} {period} {entry_adx}/{exit_adx}"},
"data": {"asset_id": "KO", "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.dropped, .payload.factorization_plan'
Según §5.4 de la especificación: 24 candidatos solicitados, 4 inválidos (la pareja entry_adx = 20, exit_adx = 20, uno por cada period), 20 efectivos y 4 curvas dmi distintas. El resultado se consulta en GET /v1/jobs/{job_id}.
Cuánto cuesta la rejilla¶
| Concepto | Cuenta |
|---|---|
| Candidatos cartesianos | 4 × 3 × 2 = 24 |
| Parejas de umbrales válidas | 5 de 6: (20,15) (25,15) (25,20) (30,15) (30,20); (20,20) viola exit < entry |
| Candidatos válidos | 4 × 5 = 20 (los 4 rechazados siguen contando en el denominador) |
Curvas dmi distintas |
4, una por period; factor de reutilización 5× |
| Dependencia compartida | una sola tr; atr(14) es la misma clave de caché que piden P16 y P19 |
Qué mirar en los resultados¶
parity.verdictenNT_FEATURES: PASS en la matriz (features con tolerancia, señales y fills exactos, equity al céntimo).- Fracción de barras en cada zona (alta, media, baja) por candidato: explica casi todo. Un candidato con el 90 % de barras en zona baja apenas opera.
- Fracción de barras con objetivo 0 tras el warmup: debe ser grande. Si es 0, algo falla en la zona baja.
- Reversiones directas frente a ciclos completos (±1 → ∓1 frente a ±1 → 0 → ∓1): mide si la histéresis actúa.
- Distribución del ADX (percentiles 10/25/50/75/90) por activo y frecuencia, para decidir si la rejilla 20/25/30 tiene sentido en ese activo.
- Contador de aciertos de caché del sweep: 20 candidatos, 4 cálculos de
dmi. - Divergencia conocida: si comparas con
vector_ta.adx, espera un desfase de una barra en la semilla (27 frente a 26). Es una variante, no un bug, y no se esconde bajo una tolerancia.
Causalidad comprobada¶
La especificación enumera las fugas y errores que su test de futuros alterados y su fixture detectan:
upcalculado comoH[t+1] − H[t](unnp.diffmal alineado): adelanta la señal una barra y mejora el resultado de forma sistemática.- Semilla del ADX con los DX de
n … 2n−1en lugar den−1 … 2n−2: no mira el futuro, pero desplaza la curva; es exactamente la variante que se sospecha en VectorTA. >=en las comparaciones de DM: otra fórmula; la barra 13 del fixture lo caza.- Umbrales dentro de la
FeatureSpec: no es una fuga, pero destruye la factorización 5× y rompe el test de caché.
El test de futuros alterados desplaza +5,00 high, low y close desde la barra 230 de una serie de 300 y exige igualdad bit a bit de plus_di, minus_di, adx, objetivos y fills hasta la barra 200. Un complemento perturba solo high[230]: plus_di[229] y adx[229] no cambian, plus_di[230] sí.
Para reanudar en modo continue bastan siete escalares: los cuatro RMA (ATR, S+DM, S−DM, ADX en T) y la barra anterior (H[T], L[T], C[T], para el siguiente TR y los siguientes DM), más el objetivo vigente y el ledger. Guardar solo adx, plus_di y minus_di no basta: los DI son un cociente y no permiten reconstruir numerador y denominador. Y la histéresis es estado: sin target[T] la zona media no sabe qué conservar.
Errores típicos¶
| Error | Consecuencia | Cómo evitarlo |
|---|---|---|
exit_adx >= entry_adx |
PolicyParamError (las zonas se solapan o desaparece la histéresis) |
Respeta exit_adx < entry_adx; en un sweep cuenta como inválido, no desaparece |
period = 1 o umbrales fuera de [0, 100] |
PolicyParamError |
period >= 2; el ADX es un porcentaje |
| Usar el ADX para decidir el signo | Otra estrategia | El signo sale solo de +DI frente a −DI |
| Decidir desde la barra 13 porque los DI ya existen | Señales sin ADX válido | La primera decisión es 2·period − 2 |
Tratar adx == exit_adx como salida |
Salidas de más en la frontera | < estricto: la frontera cae en zona media |
| Escala 0..1 en los DI o el ADX | Umbrales 20/25/30 sin sentido | La escala canónica es 0..100 |
Confundir adxr con adx |
Curva más suave y retrasada | adxr es (ADX[t] + ADX[t−n]) / 2, otro indicador |
| Esperar que P15 nunca invierta | Sorpresa con fills de 2 unidades | En zona alta un cambio de DI dominante invierte directamente |
Variantes y extensiones¶
- Sin histéresis (
entry_adx == exit_adx): no es un candidato válido de P15; sería otra receta. - Solo largos:
target_lots = [0, 1]; la zona alta con−DIdominante pone plano. - ADX como filtro de otra estrategia: combinarlo con el cruce de P01 o con el SAR de P14 para operar solo en tendencia.
- Reinicio por sesión de los RMA: no es la convención canónica; cambiaría el ADX intradía.
- Parientes: P02 comparte la estructura de umbrales con histéresis sobre un oscilador; P16 comparte el
atr.
Resumen¶
- El ADX decide la zona (alta, media, baja); los DI deciden el signo solo en zona alta.
- Zona baja pone plano sin mirar los DI; zona media conserva lo que haya, tanto largo como plano.
- Puede invertir directamente en zona alta (delta 2), a diferencia de P16 y P17.
first_decision_index = 2·period − 2(26 con defaults); el fixture lo muestra conperiod = 2y umbrales 62/40 fuera de rejilla.- 24 candidatos, 20 válidos, 4 curvas: los umbrales son de política y no recalculan nada.
Para practicar¶
- Con el fixture, calcula a mano la barra 8: parte de
ATR[7] = 0,4180,S+DM[7] = 0,0406,S−DM[7] = 0,0438,ADX[7] = 32,7290y de la barra 8 (H 11,50, L 10,95; H7 11,65, L7 11,25, C7 11,40). Comprueba−DM[8] = 0,30,ADX[8] = 55,7954y que el objetivo se queda en 0. - Rehaz la columna "equity con 5 bps" con 20 bps. ¿Qué comisión paga cada uno de los tres fills y cuál es la equity final?
- Lanza el sweep de 24 candidatos sobre KO 1d y comprueba en la respuesta que
dropped.invalidvale 4 y que el plan de factorización tiene 4 curvasdmi. Después, paraperiod = 14, compara la fracción de barras en zona alta conentry_adx = 20y conentry_adx = 30.