P16 · Keltner: ruptura tendencial¶
| Campo | Valor |
|---|---|
| Familia | Volatilidad-ruptura con sesgo de tendencia · entra al romper el canal y sale a plano; nunca invierte |
| Features requeridas | ema (entrada close) y atr (entradas high, low, close; depende de tr), ambas ya registradas; las bandas se calculan en la política |
| Parámetros y defaults | period = 20 (EMA), atr_period = 10 (ATR), k = 2.0 (ancho, de política) |
| Rejilla del catálogo | period ∈ {10, 20, 40}, atr_period ∈ {10, 14, 20}, k ∈ {1.0, 1.5, 2.0, 2.5} → 36 candidatos válidos, 3 EMA + 3 ATR |
variant_keys |
period, atr_period, k |
| Restricciones | period >= 2, atr_period >= 1, k > 0 (si no, PolicyParamError); sin restricción cruzada entre periodos |
first_decision_index |
max(period − 1, atr_period − 1) = 19 con defaults (39 con period = 40) |
| Activos y timeframes | NVDA y KO en los 7 TF (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). PARITY_FAIL nativa en NT_ONLINE(native) (12/12), capa features: la EMA nativa de Nautilus usa semilla "primer valor" (nt_seed_first_value) y la del banco la media corrida enmascarada (vta_running_mean_masked); primera divergencia en la barra 19 de KO 1d (ema 45,3790 frente a 45,3564). Es una variante declarada, no un bug |
Fuentes: docs/estrategias/P16.md (especificación), catalog/strategy_catalog.json, docs/DATOS_POR_ESTRATEGIA.md, runs/parity/summary.json, runs/parity/KO_1d_P16_5bps_NT_ONLINE_native.md.
Qué vas a aprender¶
- Qué es un canal de Keltner (EMA ± k·ATR) y por qué aquí se persigue la ruptura en lugar de revertirla como en P04.
- La convención de cruce entre dos series (
a[t] > b[t]ya[t−1] <= b[t−1]) y en qué se diferencia de un cruce contra un nivel fijo. - Una máquina de estados con salida a plano, prioridad de la salida y prohibición de invertir.
- Por qué
kno toca la caché: 36 candidatos, 6 curvas. - Qué significa la regla D-12 para la primera barra de decisión cuando la regla mira
t−1.
La idea en una frase¶
Cuando el cierre rompe hacia arriba un canal de volatilidad alrededor de su media, entrar largo (hacia abajo, corto); cuando el cierre vuelve al otro lado de la media, salir a plano.
Intuición de mercado¶
El canal de Keltner envuelve una EMA con una banda proporcional al ATR, la volatilidad típica reciente. Mientras el precio se mueve dentro del canal, el movimiento es "ruido normal". Un cierre fuera del canal es un desplazamiento anormalmente grande respecto a la volatilidad reciente, y P16 apuesta a que ese impulso continúa. Sale cuando el precio vuelve a cruzar la media: el impulso se ha agotado.
Analogía. Un río con diques. Mientras el agua sube y baja dentro del cauce, no pasa nada. Si desborda el dique, algo ha cambiado aguas arriba y la crecida suele seguir un rato; das la alarma cuando el agua vuelve a su nivel medio, no antes.
Por qué podría funcionar. Normalizar por el ATR hace la regla comparable entre activos y regímenes de volatilidad; las rupturas de volatilidad son un punto de partida clásico de las tendencias.
Cuándo falla.
- Rupturas falsas: el cierre sale del canal y vuelve al día siguiente. Con
kbajo pasa a menudo. - Gaps de apertura intradía: la primera barra de la sesión rompe el canal por el gap nocturno. La EMA y el ATR no se reinician por sesión, así que en 1min buena parte de las entradas pueden ser gaps.
- Dividendos con precio ajustado: el escalón del ajuste mete un TR artificial que ensancha el canal justo en la barra ex-dividendo. Por eso se recomienda
raw_split_adjusted.
Sesgos típicos al evaluarla.
- Elegir
ksobre el mismo periodo: con 4 valores dekpor pareja de periodos es fácil sobreajustar. - Atribuir a la receta lo que son gaps: mide la fracción de entradas en la primera barra de la sesión.
- Usar un Keltner "estándar": muchas plataformas centran el canal en el precio típico
(H+L+C)/3y usan un solo periodo; es otra curva.
Las reglas exactas¶
La regla canónica del catálogo: "Canal EMA(C,n)±k·ATR(a). Entrar largo al cruce del cierre por la banda superior; corto al cruce por la inferior. Salir cuando el cierre atraviese la EMA contra la posición; sin inversión en la decisión de salida."
Bandas, calculadas en la política: upper[t] = ema[t] + k·atr[t], lower[t] = ema[t] − k·atr[t].
| Estado previo | Situación en el cierre de t |
Objetivo | Motivo (reason) |
|---|---|---|---|
| — | t < first_decision_index |
0 | WARMUP |
| plano 0 | close[t] > upper[t] y close[t−1] <= upper[t−1] |
+1 | ENTRY |
| plano 0 | close[t] < lower[t] y close[t−1] >= lower[t−1] |
−1 | ENTRY |
| plano 0 | ninguna de las dos | 0 | HOLD |
| largo +1 | close[t] < ema[t] (estricto) |
0 | EXIT |
| largo +1 | si no | +1 | HOLD |
| corto −1 | close[t] > ema[t] (estricto) |
0 | EXIT |
| corto −1 | si no | −1 | HOLD |
| cualquiera | barra no operable | se congela el publicado | NOT_TRADABLE |
- Entrada: por cruce de la banda, con la convención de dos series; un cierre que ya estaba fuera no vuelve a entrar (no hay piramidación).
- Salida: por nivel contra la EMA, no contra la banda. Se lee como nivel para que un largo no quede atrapado si el cruce ocurrió en una barra no operable.
- Reversión: prohibida. Desde largo solo se va a plano; un fill de 2 unidades en P16 es un bug.
- Prioridad: estando en posición, se evalúa primero la salida y, si sale, esa barra no reabre.
- Empates:
close == upperno entra;close == emano sale;close[t−1] == upper[t−1]sí permite el cruce.
# decisión en el cierre de t, fill en el open de t+1
target_prev = 0
para cada barra t:
si t < first_decision_index: target = 0 # WARMUP
upper = ema[t] + k*atr[t] ; lower = ema[t] - k*atr[t]
si target_prev == +1:
target = 0 si close[t] < ema[t] si no +1 # salida primero
si no, si target_prev == -1:
target = 0 si close[t] > ema[t] si no -1
si no: # desde plano
si close[t] > upper y close[t-1] <= upper[t-1]: target = +1
si no, si close[t] < lower y close[t-1] >= lower[t-1]: target = -1
si no: target = 0
si target != target_prev:
orden(target - target_prev) -> fill a open[t+1] # siempre ±1
target_prev = target
No es el crossed_up de P04
El crossed_up del código de políticas compara contra un nivel constante con previous < level y current >= level. Aquí el "nivel" es una serie que cambia cada barra y la convención es la opuesta en los empates: > estricto en t y <= en t−1. Un cierre que toca exactamente la banda no entra con esta regla y sí con crossed_up.
Los indicadores que usa¶
| Feature | Fórmula | first_valid |
|---|---|---|
ema{period} |
α = 2/(n+1); semilla SMA(close[0..n−1]); luego ema[t] = α·close[t] + (1−α)·ema[t−1] |
period − 1 = 19 |
tr |
max(H−L, abs(H−C[t−1]), abs(L−C[t−1])); TR[0] = H0 − L0 |
0 |
atr{atr_period} |
RMA de Wilder del TR (α = 1/n, semilla SMA) |
atr_period − 1 = 9 |
upper, lower |
ema ± k·atr (en la política, no en una feature) |
max de las dos |
first_decision_index = max(period − 1, atr_period − 1) sin sumar 1, por la regla D-12: en la barra 19 la regla consulta upper[18], que es NaN, la comparación da False y la decisión legítima es "no entrar". La barra 19 se marca HOLD con decision_index_valid = True, no WARMUP, y ningún objetivo cambia por ello.
No hace falta ninguna primitiva nueva: P16 reutiliza ema, atr y tr. Se descarta a propósito una primitiva keltner_ema (nombre reservado y no registrado) porque metería k en la clave de caché. Fórmulas y comparación entre motores en Indicadores paso a paso y en Indicadores y features.
Ejemplo barra a barra¶
Fixture §6 de docs/estrategias/P16.md: 16 barras, parámetros fuera de rejilla a propósito (con los de la rejilla no hay ninguna ruptura en 16 barras): period = 3 (α = 0,5), atr_period = 2, k = 0.5. cash_inicial = 1.000,00, tick 0,01, sin costes. first_valid(ema) = 2, first_valid(atr) = 1 → first_decision_index = 2.
Se muestran 14 de las 16 barras: se omiten la 0 (warmup, O=10,00 H=10,20 L=9,95 C=10,10) y la 11 (corto mantenido: C=9,70, ema 9,9340, upper 10,1296, lower 9,7384, equity 1.000,50).
| t | O | H | L | C | ema(3) | atr(2) | upper | lower | target | orden | fill | pos. | PnL acum. sin costes | equity con 5 bps* |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 10,10 | 10,25 | 10,00 | 10,05 | NaN | 0,2500 | NaN | NaN | 0 WARMUP | — | — | 0 | 0,00 | 1.000,00 |
| 2 | 10,05 | 10,30 | 10,00 | 10,20 | 10,1167 | 0,2750 | 10,2542 | 9,9792 | 0 HOLD | — | — | 0 | 0,00 | 1.000,00 |
| 3 | 10,20 | 10,65 | 10,15 | 10,60 | 10,3583 | 0,3875 | 10,5521 | 10,1646 | +1 ENTRY | BUY 1 | — | 0 | 0,00 | 1.000,00 |
| 4 | 10,60 | 11,00 | 10,55 | 10,95 | 10,6542 | 0,4187 | 10,8635 | 10,4448 | +1 | — | 10,60 | +1 | 0,35 | 1.000,34 |
| 5 | 10,95 | 11,20 | 10,85 | 11,10 | 10,8771 | 0,3844 | 11,0693 | 10,6849 | +1 | — | — | +1 | 0,50 | 1.000,49 |
| 6 | 11,10 | 11,15 | 10,80 | 10,90 | 10,8885 | 0,3672 | 11,0721 | 10,7049 | +1 | — | — | +1 | 0,30 | 1.000,29 |
| 7 | 10,90 | 11,00 | 10,55 | 10,60 | 10,7443 | 0,4086 | 10,9486 | 10,5400 | 0 EXIT | SELL 1 | — | +1 | 0,00 | 999,99 |
| 8 | 10,60 | 10,70 | 10,35 | 10,40 | 10,5721 | 0,3793 | 10,7618 | 10,3825 | 0 | — | 10,60 | 0 | 0,00 | 999,98 |
| 9 | 10,40 | 10,50 | 10,15 | 10,20 | 10,3861 | 0,3646 | 10,5684 | 10,2037 | −1 ENTRY | SELL 1 | — | 0 | 0,00 | 999,98 |
| 10 | 10,20 | 10,30 | 9,90 | 9,95 | 10,1680 | 0,3823 | 10,3592 | 9,9769 | −1 | — | 10,20 | −1 | 0,25 | 1.000,22 |
| 12 | 9,70 | 9,95 | 9,60 | 9,85 | 9,8920 | 0,3706 | 10,0773 | 9,7067 | −1 | — | — | −1 | 0,35 | 1.000,32 |
| 13 | 9,85 | 10,15 | 9,80 | 10,10 | 9,9960 | 0,3603 | 10,1761 | 9,8159 | 0 EXIT | BUY 1 | — | −1 | 0,10 | 1.000,07 |
| 14 | 10,10 | 10,35 | 10,05 | 10,30 | 10,1480 | 0,3301 | 10,3131 | 9,9829 | 0 | — | 10,10 | 0 | 0,10 | 1.000,06 |
| 15 | 10,30 | 10,55 | 10,25 | 10,50 | 10,3240 | 0,3151 | 10,4815 | 10,1665 | +1 ENTRY | BUY 1 | sin fill | 0 | 0,10 | 1.000,06 |
Todo es literal del fixture (§6.1, §6.2, §6.3 y la serie de equity de §6.5) salvo la última columna. *"Equity con 5 bps" es cálculo de este manual: escenario hypothetical_5bps (fee_rate = 0,0005, sin spread ni deslizamiento), comisión por fill = |delta| · precio · 0,0005 redondeada al céntimo half-even y restada de la caja en la barra del fill: 10,60 · 0,0005 = 0,0053 → 0,01 (fills 1 y 2), 10,20 · 0,0005 = 0,0051 → 0,01, 10,10 · 0,0005 = 0,00505 → 0,01. Cuatro fills, 0,04 en total. El PnL acumulado es equity − 1.000,00 del fixture.
Lo que hay que ver en la tabla:
- Barra 2: primera barra de decisión (D-12). Aunque
close = 10,20yupper = 10,2542, el cruce necesitaupper[1], que es NaN: objetivo 0. (La lista de casos de §6.3 de la especificación todavía llama "warmup" a la barra 2; su propia tabla y §6.4 la marcanHOLD, que es lo que manda D-12.) - Barra 3:
10,60 > 10,5521y10,20 <= 10,2542: las dos mitades del cruce. Fill al open de 4 (10,60). - Barras 4–5: el cierre sigue por encima de la banda, pero no hay nuevo cruce (
close[t−1] <= upper[t−1]es falso). No se piramida. - Barra 6: el cierre cae bajo la banda pero sigue sobre la EMA: se conserva. La salida es contra la EMA.
- Barra 7:
10,60 < 10,7443→ plano. Un solo fill de 1 unidad. El trade largo termina en 0,00 exactos (compra y venta a 10,60). - Barra 9:
10,20 < 10,2037(por 0,0037) y10,40 >= 10,3825: entrada corta. - Barras 13–15: salida del corto, una barra plana y reentrada larga. El objetivo pasa de −1 a +1 a través de 0. La última señal queda sin fill y la posición final es 0: no hay nada abierto que valorar. Equity final 1.000,10.
Las cuentas de las barras clave¶
Semillas: TR[0..3] = 0,25 ; 0,25 ; 0,30 ; 0,50
atr[1] = (0,25 + 0,25)/2 = 0,2500 ; atr[2] = 0,25 + (0,30 - 0,25)/2 = 0,2750
atr[3] = 0,2750 + (0,50 - 0,2750)/2 = 0,3875
ema[2] = (10,10 + 10,05 + 10,20)/3 = 10,116667
ema[3] = 10,116667 + 0,5·(10,60 - 10,116667) = 10,358333
Entrada (t=3): upper[2] = 10,116667 + 0,5·0,2750 = 10,254167
upper[3] = 10,358333 + 0,5·0,3875 = 10,552083
10,60 > 10,552083 y 10,20 <= 10,254167 -> +1 ENTRY
Salida (t=7): ema[7] = 10,8885 + 0,5·(10,60 - 10,8885) = 10,74425
10,60 < 10,74425 -> 0 EXIT (1 unidad)
Corto (t=9): ema[9] = 10,5721 + 0,5·(10,20 - 10,5721) = 10,38605 ; atr[9] = 0,36465
lower[9] = 10,38605 - 0,182325 = 10,203725 ; lower[8] = 10,38245
10,20 < 10,203725 y 10,40 >= 10,38245 -> -1 ENTRY
Caja: 1.000,00 - 10,60 = 989,40 ; + 10,60 = 1.000,00 ; + 10,20 = 1.010,20 ; - 10,10 = 1.000,10
Este fixture no sirve para probar el fill en la apertura siguiente
Sus barras están encadenadas (open[t+1] == close[t]), así que un fill a close[t] y uno a open[t+1] dan el mismo precio. La especificación usa otro fixture con gap para ese test.
Diagrama¶
flowchart LR
W(["WARMUP: t < max(period, atr_period) - 1"]) --> FLAT["Plano 0"]
FLAT -->|"C cruza upper hacia arriba · BUY 1"| L["Largo +1"]
FLAT -->|"C cruza lower hacia abajo · SELL 1"| S["Corto -1"]
L -->|"C < EMA · SELL 1"| FLAT
S -->|"C > EMA · BUY 1"| FLAT
L -->|"C >= EMA"| L
S -->|"C <= EMA"| S
FLAT -->|"sin cruce"| FLAT
No hay flecha directa entre largo y corto: es la traducción literal de "sin inversión en la decisión de salida".
Cómo lanzarla¶
Backtest con ambos motores sobre NVDA 5min en 2024, con los defaults (forma "inline" de la API de backtesting; el resto del payload como examples/api/01_backtest_p01_both.request.json):
BASE=http://localhost:8000
SID=$(curl -sS -X POST "$BASE/v1/backtest" -H 'Content-Type: application/json' -d '{
"request_id": "manual-p16-0001",
"correlation_id": null,
"schema_version": "v1",
"payload": {
"strategy": {"template_id": "P16",
"params": {"period": 20, "atr_period": 10, "k": 2.0},
"name": "Keltner 20/10 k2 base", "register": true},
"data": {"asset_id": "NVDA", "timeframe": "5min",
"window": {"start": "2024-01-01T00:00:00Z", "end": "2025-01-01T00:00:00Z",
"mode": "reset_flat"}},
"execution": {"lane": "SIM-S", "contract_version": "1", "initial_cash": 100000,
"target_lots": [-1, 0, 1], "cost_scenario": "hypothetical_5bps"},
"engine": "both",
"cache": {"cache_mode": "WARM_FEATURES", "write_through": true},
"output": {"output_mode": "metrics", "include_timings": true,
"include_parity": true, "tail_rows": 5},
"engine_options": {"vectorta": {"kernel": "scalar", "use_batch": true},
"nautilus": {"adapter": "NT_FEATURES"}}
}}' | jq -r '.payload.signature.strategy_id')
echo "$SID"
import uuid, requests
BASE = "http://localhost:8000"
env = lambda payload: {"request_id": str(uuid.uuid4()), "correlation_id": None,
"schema_version": "v1", "payload": payload}
payload = {
"strategy": {"template_id": "P16",
"params": {"period": 20, "atr_period": 10, "k": 2.0},
"register": True},
"data": {"asset_id": "NVDA", "timeframe": "5min",
"window": {"start": "2024-01-01T00:00:00Z",
"end": "2025-01-01T00:00:00Z", "mode": "reset_flat"}},
"execution": {"lane": "SIM-S", "contract_version": "1", "initial_cash": 100000,
"target_lots": [-1, 0, 1], "cost_scenario": "hypothetical_5bps"},
"engine": "both",
"output": {"output_mode": "metrics", "include_parity": True},
"engine_options": {"vectorta": {"kernel": "scalar", "use_batch": True},
"nautilus": {"adapter": "NT_FEATURES"}},
}
r = requests.post(f"{BASE}/v1/backtest", json=env(payload)).json()["payload"]
SID = r["signature"]["strategy_id"]
print(SID, r["parity"]["verdict"])
for res in r["results"]:
print(res["adapter"], res["run_metrics"]["final_equity"], res["run_metrics"]["n_fills"])
Si pides NT_ONLINE con indicator_provider: "native", espera PARITY_FAIL en la capa features en la barra period − 1: es la variante declarada de la semilla EMA.
Derivar una variante con un canal más ancho:
curl -sS -X POST "$BASE/v1/strategies/$SID/derive" -H 'Content-Type: application/json' -d '{
"request_id": "manual-p16-derive", "correlation_id": null, "schema_version": "v1",
"payload": {"param_overrides": {"k": 2.5}, "name": "Keltner 20/10 k2.5",
"slug": "keltner-20-10-k2-5", "tags": ["sensibilidad"]}}' | jq '.payload.strategy_id, .payload.cache_forecast'
k es parámetro de política: según §5.3 de la especificación, ema(20) y atr(10) se reutilizan enteras y solo se rehacen las bandas (dos operaciones vectoriales), la política y el ledger. El cache_forecast debe anunciar ema y atr como aciertos esperados. Si cambiaras period, fallaría solo la EMA (el ATR se reutiliza); si cambiaras atr_period, fallaría solo el ATR (la EMA y el tr se reutilizan).
Sweep de sus variant_keys (rejilla completa del catálogo, con la forma de 03_sweep_p01_4x4.request.json):
curl -sS -X POST "$BASE/v1/sweep" -H 'Content-Type: application/json' -d '{
"request_id": "manual-p16-sweep", "correlation_id": null, "schema_version": "v1",
"payload": {"base_strategy_id": "'"$SID"'",
"grid": {"period": [10, 20, 40], "atr_period": [10, 14, 20], "k": [1.0, 1.5, 2.0, 2.5]},
"candidate_policy": {"drop_invalid": true, "drop_duplicates": true,
"register_strategies": true,
"name_template": "{template_name} {period}/{atr_period} k{k}"},
"data": {"asset_id": "NVDA", "timeframe": "5min",
"window": {"start": "2024-01-01T00:00:00Z", "end": "2025-01-01T00:00:00Z", "mode": "reset_flat"}},
"execution": {"lane": "SIM-S", "contract_version": "1", "initial_cash": 100000,
"cost_scenario": "hypothetical_5bps"},
"engine": "vectorta", "cache": {"cache_mode": "OPT_FULL"},
"output": {"output_mode": "metrics"},
"engine_options": {"vectorta": {"kernel": "scalar", "use_batch": true}}}}' | jq '.payload.job_id, .payload.factorization_plan'
Según §5.4 de la especificación: 36 candidatos, 36 válidos, 6 curvas (ema 10, 20, 40 y atr 10, 14, 20) más una sola tr. El resultado se consulta en GET /v1/jobs/{job_id}.
Cuánto cuesta la rejilla¶
| Concepto | Cuenta |
|---|---|
| Candidatos cartesianos | 3 × 3 × 4 = 36 |
| Inválidos | 0 (ningún k <= 0 y sin restricción cruzada: period = 10, atr_period = 20 es un candidato legítimo) |
| Curvas de feature | 3 EMA + 3 ATR = 6, más 1 tr = 7 cálculos |
| Reutilización | cada EMA la usan 12 candidatos y cada ATR otros 12; factor 36/6 = 6× |
| Entre estrategias | ema(20) es la misma clave que la ema_slow de P01; atr(14) la comparten P06, P15 y P19 |
Es, con P15, la mejor factorización del grupo, y el contraste con P14 (12 candidatos, 12 curvas) es el mejor ejemplo del manual de por qué importa separar parámetros de feature y de política.
Qué mirar en los resultados¶
parity.verdictenNT_FEATURES: PASS en la matriz.- Número de fills con
qty == 2: debe ser exactamente 0. Es una métrica de corrección. - Número de trades completos (
0 → ±1 → 0) y duración media: es lo quekgobierna. - Fracción de barras planas tras el warmup: debe ser alta. Si es 0, la salida está mal leída.
- Ancho medio del canal
2·k·atr / ema, para compararkentre activos. - Fracción de entradas en la primera barra de la sesión (intradía): si es muy alta, estás midiendo gaps, no rupturas.
- Distancia entre el cierre de entrada y la banda rota: cuánto "se pasa" la ruptura antes de que el fill ocurra en el open siguiente.
- Divergencia conocida: con
NT_ONLINE(native)la matriz daPARITY_FAILenfeaturesdesde la barra 19 de KO 1d (ema45,378999900000004 frente a 45,356433020734514; diferencia máxima 0,0226). El mismo informe muestra que elatrnativo (semillant_seed_first_value_sma_inner) también difiere desde antes, pero la primera divergencia registrada es la de la EMA. Una diferencia pequeña en la banda puede mover el índice exacto de un cruce, así que no se esconde bajo una tolerancia.
Causalidad comprobada¶
Las dos curvas son recursiones causales; las bandas son aritmética de la misma barra. Las fugas posibles están en la política: comparar close[t] con upper[t+1] (un np.roll en la dirección equivocada) o decidir con la banda o el precio de la barra de fill. Ambas adelantan las entradas una barra y mejoran el resultado de forma sistemática.
El test de futuros alterados de la especificación multiplica por 1,5 open, high, low y close desde la barra 300 de una serie de 400 y exige igualdad bit a bit de ema, atr, bandas, objetivos y fills hasta la barra 250. Su complemento perturba solo close[300]: target[299] no puede cambiar, target[300] sí, y target[301] también, porque close[300] es la mitad t−1 del cruce de la barra 301. Una implementación que no muestre el efecto en 301 tiene el cruce mal construido.
Para reanudar en modo continue bastan tres escalares: ema[T], atr[T] y close[T] (este se usa dos veces: para TR[T+1] y para la mitad t−1 del cruce), más el objetivo vigente y el ledger. upper[T] y lower[T] se derivan de ellos con k.
Errores típicos¶
| Error | Consecuencia | Cómo evitarlo |
|---|---|---|
k <= 0 |
PolicyParamError: con k = 0 las bandas colapsan en la EMA y cada trade dura una barra |
Usa k > 0 |
period = 1 |
PolicyParamError: la EMA sería el propio cierre y la salida no se cumpliría nunca |
period >= 2 |
| Invertir de +1 a −1 en una barra | Un fill de 2 unidades, prohibido por el catálogo | La salida lleva a 0; la entrada contraria se evalúa en barras siguientes |
| Salir contra la banda en vez de contra la EMA | Trades mucho más cortos: otra estrategia | Salida close < ema (largo) / close > ema (corto) |
Entrar por nivel (close > upper) sin exigir cruce |
Reentradas en cada barra fuera del canal | Exige también close[t−1] <= upper[t−1] |
Usar crossed_up con la banda como nivel |
Diferencias en los empates | Convención de dos series: > en t, <= en t−1 |
Meter k en la FeatureSpec |
36 curvas en vez de 6; el test de caché falla | Las bandas se calculan en la política |
Usar vector_ta.keltner o el KeltnerChannel de Nautilus |
Otra curva (precio típico, periodo único) | La fórmula canónica es EMA(close, n) ± k·ATR(a) con dos periodos |
Variantes y extensiones¶
- Reversión en el canal: P04 (Bollinger) usa el mismo tipo de canal para apostar a la vuelta a la media.
- Canal de Keltner con SMA:
keltner_sma(SMA ± k·SMA(TR)) es la primitiva de P08; no se confunde con este canal. - Solo largos:
target_lots = [0, 1], ignorando la ruptura de la banda inferior. - Stop real en la banda (SIM-R): entrar intrabar al tocar la banda es otra estrategia.
- Filtro de tendencia: exigir ADX alto (P15) para aceptar la ruptura.
- Parientes de ruptura: P11 Donchian (máximos y mínimos del canal) y P05 (compresión de ancho).
Resumen¶
- P16 entra cuando el cierre cruza fuera del canal
EMA ± k·ATRy sale a plano cuando el cierre vuelve al otro lado de la EMA. - Nunca invierte: el ciclo es
0 → ±1 → 0; todos los fills son de 1 unidad. - Primera decisión en
max(period − 1, atr_period − 1)(19 con defaults), sin sumar 1 por elt−1del cruce (D-12). kes de política: 36 candidatos, 6 curvas.- Paridad PASS en los carriles comparables; el carril nativo de Nautilus diverge por la semilla de la EMA, una variante declarada.
Para practicar¶
- Con el fixture, calcula a mano la barra 10: parte de
ema[9] = 10,38605,atr[9] = 0,36465,close[9] = 10,20y la barra 10 (H 10,30, L 9,90, C 9,95). ObténTR[10] = 0,40,atr[10] ≈ 0,3823,ema[10] ≈ 10,1680, y comprueba que, estando corto,9,95 > 10,1680es falso y se conserva el −1. - Rehaz la columna "equity con 5 bps" con 10 bps. ¿Cuánto pagan en total los cuatro fills y cuál es la equity final? (Pista:
10,10 · 0,001 = 0,0101.) - Deriva desde tu
$SIDlas variantesk = 1.0yk = 2.5, lánzalas sobre NVDA 5min y compara número de trades, duración media y fracción de entradas en la primera barra de la sesión.