P04 · Bollinger: reversión por reentrada¶
| Campo | Valor |
|---|---|
| Familia | Reversión · banda de volatilidad; entra al volver a entrar en la banda y sale en la media |
| Features requeridas | bbands (salidas middle, upper, lower; depende de sma{period} y stddev{period} sobre close) y el rol stddev para detectar la banda degenerada; columna de barra close |
| Parámetros y defaults | period = 20, k = 2.0 |
| Rejilla del catálogo | period ∈ {10, 20, 30, 50}, k ∈ {1.5, 2.0, 2.5, 3.0} → 16 candidatos válidos; 8 curvas "caras" (4 sma + 4 stddev) y 16 recombinaciones baratas de bandas |
variant_keys |
period, k |
| Restricciones | period entero ≥ 2, k > 0 (si no, PolicyParamError) |
first_decision_index |
period − 1 = 19 con defaults (49 con period = 50) |
| Activos y timeframes | NVDA y KO en los 7 TF (1min … 1d); AMZN, INTC, MSFT, TSLA en 1d; BME y FX en 1d con reservas |
| 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 'stddev'". Además, el BollingerBands nativo de Nautilus usa el precio típico (H+L+C)/3, no el cierre: otra definición |
Fuentes: docs/estrategias/P04.md (especificación), catalog/strategy_catalog.json, docs/DATOS_POR_ESTRATEGIA.md, runs/parity/summary.json.
Qué vas a aprender¶
- Cómo se construyen las bandas de Bollinger con desviación poblacional (
ddof = 0) y qué significak. - La regla de reentrada: cada cierre se compara con la banda de su propia barra, ayer con ayer y hoy con hoy.
- Por qué P04 es la única excepción a la convención de cruce del proyecto (y por qué la receta manda).
- El caso degenerado
stddev = 0y por qué se detecta con la desviación y no comparando bandas. - Por qué cambiar
kes un fallo de caché "barato": reutiliza media y desviación.
La idea en una frase¶
Si el precio cierra por debajo de la banda inferior y a la barra siguiente vuelve a entrar, comprar; simétrico en la banda superior para el corto; en ambos casos, salir al tocar la media.
Intuición de mercado¶
Las bandas de Bollinger dibujan un "pasillo" alrededor de la media móvil cuyo ancho es k desviaciones típicas recientes. Un cierre fuera del pasillo es un movimiento inusual para la volatilidad del momento. P04 no compra al salir por abajo (el movimiento podría acelerar), sino cuando el precio vuelve a entrar: la señal de que el exceso empieza a corregirse. El objetivo es modesto: volver a la media.
Analogía. Un perro con correa extensible: cuando se aleja más de lo normal no tiras en ese instante; esperas a que empiece a volver y lo acompañas hasta tus pies (la media), no más allá.
Por qué podría funcionar. En activos y horizontes con reversión (acciones grandes en intradía, divisas en rango), los excesos respecto a la volatilidad reciente tienden a corregirse parcialmente.
Cuándo falla.
- Rupturas genuinas: el precio vuelve a la banda un momento y sigue saliendo; sin stop, el trade aguanta hasta que el precio cruce la media.
- Cambios de régimen de volatilidad: las bandas se ensanchan con el propio movimiento y la reentrada llega tarde o nunca.
- Ventanas planas (intradía de baja actividad): la desviación es 0, las bandas colapsan y la receta se niega a entrar.
Sesgos típicos al evaluarla.
- Ejecutar al precio de la banda tocada intrabar: un fill a
lower[t]porquelow[t] < lower[t]es una orden límite que nadie colocó. El fill esopen[t+1]. - Pocos trades: con
k = 3.0yperiod = 50puede haber muy pocas reentradas; exige un número mínimo antes de comparar. - Huecos de sesión en KO: una diferencia multi-día infla la desviación durante
periodbarras y suprime reentradas.
Las reglas exactas¶
La regla canónica del catálogo: "Bandas SMA(n)±k·std(n,ddof=0). Largo cuando C[t-1]
degenerate[t] := stddev[t] == 0.0
enter_long[t] := C[t-1] < L[t-1] and C[t] >= L[t] and not degenerate[t]
enter_short[t] := C[t-1] > U[t-1] and C[t] <= U[t] and not degenerate[t]
exit_long[t] := C[t] >= M[t]
exit_short[t] := C[t] <= M[t]
Situación en el cierre de t |
Objetivo target[t] |
Motivo (reason) |
|---|---|---|
t < period − 1 (bandas NaN) |
0 | WARMUP |
largo (+1) y C[t] >= M[t] |
0 | EXIT |
largo (+1) y C[t] < M[t] |
+1 | HOLD |
corto (−1) y C[t] <= M[t] |
0 | EXIT |
corto (−1) y C[t] > M[t] |
−1 | HOLD |
plano y enter_long |
+1 | ENTRY |
plano y enter_short |
−1 | ENTRY |
plano sin reentrada, o stddev == 0 |
0 | HOLD |
| bandas NaN tardías | el anterior | HOLD |
barra no operable (is_tradable = False) |
se congela el publicado | NOT_TRADABLE |
- Entrada: solo desde plano y solo por reentrada.
<estricta ayer (estar fuera de verdad) y>=hoy (volver incluye tocar la banda exactamente). - Salida: por la media; prioridad absoluta sobre las entradas de la misma barra.
- Reversión: prohibida ("No invertir en la decisión de salida"). Estando corto, una reentrada larga no hace nada.
- Filtro:
stddev == 0exacto anula las dos entradas (con las bandas colapsadas, entrada y salida caerían en la misma barra: coste puro).
# decisión en el cierre de t, fill en el open de t+1
state = 0 ; pC = pL = pU = NaN
para cada barra t:
si t < period - 1: target = 0 # WARMUP
si no, si state == +1: target = 0 si C[t] >= M[t] si no +1 # salida primero
si no, si state == -1: target = 0 si C[t] <= M[t] si no -1
si no (plano):
si stddev[t] > 0 y pC < pL y C[t] >= L[t]: target = +1
si no, si stddev[t] > 0 y pC > pU y C[t] <= U[t]: target = -1
si no: target = 0
si target != state:
orden(target - state) -> fill a open[t+1] # siempre ±1
state = target ; pC, pL, pU = C[t], L[t], U[t] # se guardan aunque sean NaN
La única excepción a la convención de cruce
El proyecto define "cruce" como a[t] > b[t] and a[t−1] <= b[t−1] (§5.1, D-09 revisada), y así lo usa P02. P04 escribe literalmente sus desigualdades en la regla del catálogo (< en t−1, >= en t), y la receta manda sobre la convención. Es la única excepción documentada.
Banda de ayer con cierre de ayer
Comparar C[t−1] con L[t] (la banda de hoy) mezcla dos instantes y produce señales fantasma cuando la banda se ensancha. Por eso la política online memoriza tres escalares: C[t−1], L[t−1] y U[t−1].
Los indicadores que usa¶
| Feature | Fórmula | first_valid |
|---|---|---|
sma{n} |
media de close[t−n+1..t] |
n − 1 |
stddev{n} |
desviación poblacional (ddof = 0) de la misma ventana; varianza negativa minúscula limitada a 0 con VARIANCE_CLIP_ATOL = 1e−12 |
n − 1 |
bbands{n,k} |
middle = sma, upper = middle + k·stddev, lower = middle − k·stddev |
n − 1 |
sma y stddev son dependencias paralelas (misma ventana): no se suman. first_decision_index = period − 1 (19 con defaults), sin +1 aunque la regla mire t−1 (D-12): en esa barra la banda previa es NaN, ninguna entrada puede dispararse y la decisión "no entrar" es legítima.
Por qué stddev == 0 y no "bandas iguales a la media". Con aritmética f64 el redondeo no es simétrico en las fronteras de potencias de dos: con middle = 1024,0, k = 1,5 y stddev = 6,56e−14 > 0, upper redondea a 1024,0 exacto y lower no. Una comprobación "upper == middle o lower == middle" bloquearía entradas con desviación positiva. La spec lo documenta en §4.b.7.
Detalle numérico en Indicadores paso a paso e Indicadores y features.
Ejemplo barra a barra¶
Fixture §6 de docs/estrategias/P04.md: period = 3, k = 1,0 (fuera de rejilla a propósito, para calcular la desviación a mano), cash_inicial = 1.000,00, tick 0,01, sin costes. first_decision_index = 2.
Barras 0–1 idénticas a la 2 (O=100,00, H=100,20, L=99,80, C=100,00) con bandas NaN.
| t | O | H | L | C | middle | stddev | upper | lower | target | orden | fill | pos. | PnL acum. sin costes* | equity con 5 bps* |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2 | 100,00 | 100,20 | 99,80 | 100,00 | 100,000000 | 0,000000 | 100,000000 | 100,000000 | 0 HOLD (degenerada) | — | — | 0 | 0,00 | 1.000,00 |
| 3 | 100,00 | 100,20 | 99,80 | 100,00 | 100,000000 | 0,000000 | 100,000000 | 100,000000 | 0 HOLD (degenerada) | — | — | 0 | 0,00 | 1.000,00 |
| 4 | 99,50 | 99,70 | 96,80 | 97,00 | 99,000000 | 1,414214 | 100,414214 | 97,585786 | 0 HOLD | — | — | 0 | 0,00 | 1.000,00 |
| 5 | 97,50 | 99,20 | 97,30 | 99,00 | 98,666667 | 1,247219 | 99,913886 | 97,419448 | +1 ENTRY | BUY 1 | — | 0 | 0,00 | 1.000,00 |
| 6 | 99,50 | 101,20 | 99,30 | 101,00 | 99,000000 | 1,632993 | 100,632993 | 97,367007 | 0 EXIT | SELL 1 | 99,50 | +1 | 1,50 | 1.001,45 |
| 7 | 100,50 | 100,70 | 95,80 | 96,00 | 98,666667 | 2,054805 | 100,721471 | 96,611862 | −1 ENTRY | SELL 1 | 100,50 | 0 | 1,00 | 1.000,90 |
| 8 | 96,50 | 100,20 | 96,30 | 100,00 | 99,000000 | 2,160247 | 101,160247 | 96,839753 | −1 HOLD (reentrada larga ignorada) | — | 96,50 | −1 | −2,50 | 997,35 |
| 9 | 100,50 | 101,20 | 100,30 | 101,00 | 99,000000 | 2,160247 | 101,160247 | 96,839753 | −1 HOLD | — | — | −1 | −3,50 | 996,35 |
| 10 | 101,50 | 103,20 | 101,30 | 103,00 | 101,333333 | 1,247219 | 102,580552 | 100,086114 | −1 HOLD | — | — | −1 | −5,50 | 994,35 |
| 11 | 103,50 | 105,20 | 103,30 | 105,00 | 103,000000 | 1,632993 | 104,632993 | 101,367007 | −1 HOLD | — | — | −1 | −7,50 | 992,35 |
| 12 | 105,50 | 110,20 | 105,30 | 110,00 | 106,000000 | 2,943920 | 108,943920 | 103,056080 | −1 HOLD | — | — | −1 | −12,50 | 987,35 |
| 13 | 109,50 | 109,70 | 103,80 | 104,00 | 106,333333 | 2,624669 | 108,958003 | 103,708664 | 0 EXIT | BUY 1 | — | −1 | −6,50 | 993,35 |
| 14 | 104,50 | 104,70 | 102,80 | 103,00 | 105,666667 | 3,091206 | 108,757873 | 102,575461 | 0 HOLD | — | 104,50 | 0 | −7,00 | 992,80 |
| 15 | 103,50 | 103,70 | 99,80 | 100,00 | 102,333333 | 1,699673 | 104,033007 | 100,633660 | 0 HOLD | — | — | 0 | −7,00 | 992,80 |
OHLC, bandas, objetivos, motivos y fills son literales del fixture. *El fixture da la equity en los cierres 5, 6, 7, 8, 12 y la final (993,00); la columna "PnL acumulado" rellena las demás barras con la regla del ledger equity = cash + posición · close (cálculo de este manual, trivial: caja 900,50 → 1.001,00 → 1.097,50 → 993,00) y coincide con el fixture donde este da cifra. "Equity con 5 bps" es cálculo propio: hypothetical_5bps (fee_rate = 0,0005, sin spread ni deslizamiento), comisión = |delta| · precio · 0,0005 redondeada al céntimo half-even, restada en la barra del fill:
| Fill | Barra | Precio | Cuenta | Comisión |
|---|---|---|---|---|
| 1 BUY 1 | 6 | 99,50 | 0,04975 |
0,05 |
| 2 SELL 1 | 7 | 100,50 | 0,05025 |
0,05 |
| 3 SELL 1 | 8 | 96,50 | 0,04825 |
0,05 |
| 4 BUY 1 | 14 | 104,50 | 0,05225 |
0,05 |
Lo que hay que ver en la tabla:
- Barras 2–3: ventana plana →
stddev = 0→ las tres bandas valen 100 ydegenerateanula las entradas. Las dos salidas son ciertas a la vez, pero el estado es 0: resultado determinista. - Barra 5:
C[4] = 97,00 < L[4] = 97,585786(fuera ayer) yC[5] = 99,00 >= L[5] = 97,419448(dentro hoy) → largo. Fill en el open de 6 (99,50). - Barra 6:
101,00 >= 99,000000→ salida. Largo de una barra, +1,00 en caja. - Barra 7:
C[6] = 101,00 > U[6] = 100,632993yC[7] = 96,00 <= U[7]→ corto desde plano. - Barra 8: la reentrada larga es cierta (
96,00 < 96,611862y100,00 >= 96,839753), pero el estado es −1 y100,00 <= 99,00es falso: no se invierte. Es la barra que distingue P04 de una estrategia que diera la vuelta. - Barras 9–12: el precio sube contra el corto y la media no se alcanza: −12,50 en el peor momento. Sin stop, la receta aguanta.
- Barra 13: la reentrada corta y la salida del corto son ciertas a la vez; manda la salida → 0. Equity final 993,00: la receta pierde en este fixture.
Las cuentas de las barras clave¶
Barra 4 (banda de "ayer" para t=5):
ventana (100; 100; 97) middle = 297/3 = 99,000000
desviaciones (1; 1; −2) varianza = (1 + 1 + 4)/3 = 2 stddev = √2 = 1,414214
lower[4] = 99 − 1,414214 = 97,585786
Barra 5 (entrada larga):
ventana (100; 97; 99) middle = 296/3 = 98,666667
desviaciones (4/3; −5/3; 1/3) varianza = (16 + 25 + 1)/27 = 42/27 = 1,555556
stddev = 1,247219 lower[5] = 97,419448 ; upper[5] = 99,913886
97,00 < 97,585786 y 99,00 >= 97,419448 y stddev > 0 -> ENTRY +1
Barra 13 (salida del corto):
ventana (105; 110; 104) middle = 319/3 = 106,333333
varianza = (16 + 121 + 49)/27 = 186/27 = 6,888889 stddev = 2,624669
104,00 <= 106,333333 -> EXIT (la reentrada corta, también cierta, no se mira)
Caja: 1.000,00 − 99,50 = 900,50 (fill 1, open[6]); equity cierre 6 = 900,50 + 101,00 = 1.001,50
900,50 + 100,50 = 1.001,00 (fill 2, open[7])
1.001,00 + 96,50 = 1.097,50 (fill 3, open[8]); equity cierre 12 = 1.097,50 − 110,00 = 987,50
1.097,50 − 104,50 = 993,00 (fill 4, open[14]); equity final 993,00
Diagrama¶
flowchart LR
W(["WARMUP: t < period-1"]) -->|"bandas válidas"| FLAT["Plano 0"]
FLAT -->|"C[t-1] < L[t-1] y C >= L · BUY 1"| L["Largo +1"]
FLAT -->|"C[t-1] > U[t-1] y C <= U · SELL 1"| S["Corto -1"]
FLAT -->|"sin reentrada o stddev == 0"| FLAT
L -->|"C >= media · SELL 1"| FLAT
S -->|"C <= media · BUY 1"| FLAT
L -->|"C < media"| L
S -->|"C > media (reentradas ignoradas)"| S
Como en P02, no hay flecha directa entre largo y corto: todas las transiciones mueven 1 unidad.
Cómo lanzarla¶
Backtest con ambos motores sobre NVDA 5min en 2024 con los defaults (forma "inline" de la API de backtesting; mismo payload que 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-p04-0001",
"correlation_id": null,
"schema_version": "v1",
"payload": {
"strategy": {"template_id": "P04", "params": {"period": 20, "k": 2.0},
"name": "Bollinger 20/2 reentrada", "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": "P04", "params": {"period": 20, "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"])
NT_FEATURES funciona porque recibe las bandas ya calculadas (D-14). La ruta NT_ONLINE con indicator_provider: "native" es UNSUPPORTED: Nautilus no ofrece stddev y su Bollinger usa el precio típico.
Derivar una variante con bandas más anchas:
curl -sS -X POST "$BASE/v1/strategies/$SID/derive" -H 'Content-Type: application/json' -d '{
"request_id": "manual-p04-derive", "correlation_id": null, "schema_version": "v1",
"payload": {"param_overrides": {"k": 3.0}, "name": "Bollinger 20/3 reentrada",
"slug": "bollinger-20-3", "tags": ["sensibilidad"]}}' \
| jq '.payload.strategy_id, .payload.cache_forecast'
k es parámetro de feature, pero con invalidación parcial (fila "BB k2 → k3" de §5.3 de la spec): la clave de bbands{20, 3.0} es nueva (fallo), mientras que sma{20} y stddev{20} son aciertos; solo se recombinan middle ± k·stddev (cuatro operaciones por barra) y se rehacen política y ledger. Cambiar period sí es un fallo completo: sma y stddev nuevas.
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-p04-sweep", "correlation_id": null, "schema_version": "v1",
"payload": {"base_strategy_id": "'"$SID"'",
"grid": {"period": [10, 20, 30, 50], "k": [1.5, 2.0, 2.5, 3.0]},
"candidate_policy": {"drop_invalid": true, "drop_duplicates": true,
"register_strategies": true, "name_template": "{template_name} {period}/{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 spec: 16 candidatos, 16 válidos. Resultados en GET /v1/jobs/{job_id}.
Cuánto cuesta la rejilla¶
| Concepto | Cuenta |
|---|---|
| Candidatos cartesianos | 4 period × 4 k = 16 |
| Inválidos | 0 (todos period ≥ 2, k > 0) |
| Curvas caras (ventanas rodantes) | 4 sma + 4 stddev = 8, frente a 32 sin factorizar (ahorro del 75 %) |
Entradas de caché bbands |
16 (la clave incluye k), pero cada una es una recombinación trivial |
| Políticas y ledgers | 16 y 16 |
"En P04, distintos multiplicadores comparten media y desviación": las 16 claves finales no se comparten, pero el trabajo pesado (la stddev rodante) sí.
Qué mirar en los resultados¶
parity.verdict: PASS esperado enNT_FEATURES,NT_INTENT_REPLAYyNT_ONLINE(vectorta).- Número de reentradas y salidas; barras en +1/−1/0: P04 pasa mucho tiempo plano. Los fills igualan la suma de
|delta|(nunca invierte). - Fracción de barras degeneradas (
stddev == 0): en intradía puede ser grande y explicar por sí sola pocos trades. - Anchura relativa media
(upper − lower)/middley fracción de cierres fuera de bandas: descriptivas, no de selección. - Número mínimo de trades antes de ordenar candidatos (§10.2).
feature_build_nsdesglosado ensma,stddevy recombinación:stddevdomina.
Causalidad comprobada¶
Test de "futuros alterados" de la spec: NVDA 1d, 600 barras, {20, 2.0}; a partir de t0 = 400 se inyecta un régimen de alta volatilidad (close[u] += 12·(−1)^u). Deben quedar idénticos bit a bit en [0, 400] las tres bandas, objetivos, motivos, el estado interno (_target y los tres previous) y los fills con fill_index ≤ 400. Una desviación con sumas acumuladas sobre toda la serie (la fórmula "rápida" E[x²] − E[x]², que la referencia canónica rechaza) fallaría este test.
Checkpoint mínimo para continue: la ventana completa close[t0−period .. t0−1] (media y desviación rodantes no se resumen en un escalar, a diferencia de las EMA de P01), el objetivo vigente y _previous_close, _previous_lower, _previous_upper.
Errores típicos¶
| Error | Consecuencia | Cómo evitarlo |
|---|---|---|
period = 1 |
PolicyParamError: stddev ≡ 0, nunca entraría |
period ≥ 2 |
k <= 0 |
PolicyParamError: bandas colapsadas |
k > 0 |
Desviación muestral (ddof = 1) |
Bandas un factor √(n/(n−1)) más anchas: otras señales |
ddof = 0, como dice la regla |
Comparar C[t−1] con L[t] |
Señales fantasma al ensancharse la banda | Cada cierre con la banda de su barra |
Usar la convención §5.1 (> hoy, <= ayer) |
Otra estrategia en los empates | P04 usa < ayer y >= hoy, literal del catálogo |
Detectar std = 0 comparando bandas |
Entradas bloqueadas con desviación positiva | stddev == 0.0 exacto |
| Fill al precio de la banda intrabar | Resultado optimista e irreal | Fill en open[t+1] |
| Bollinger nativo de Nautilus | Media sobre (H+L+C)/3, no sobre close |
Usa bandas precalculadas (NT_FEATURES) |
Variantes y extensiones¶
- Entrada al tocar la banda (sin esperar reentrada): más señales y más riesgo de "cuchillo que cae"; es otra receta.
- Salida en la banda opuesta en lugar de la media.
- Solo largos con
target_lots = [0, 1]. - Filtro de régimen: evitar reentradas cuando la anchura de banda se expande, justo lo contrario de P05, que busca la ruptura tras la compresión.
- Parientes: P02 RSI (retorno desde extremos sobre un oscilador), P16 Keltner (bandas con ATR en vez de desviación típica).
Resumen¶
- Bandas
SMA(n) ± k·std(n, ddof=0); largo al volver a entrar por abajo, corto al volver a entrar por arriba, salida en la media. - Cada cierre se compara con la banda de su propia barra; desigualdades literales del catálogo (única excepción a la convención de cruce).
- Nunca invierte: la salida tiene prioridad y todas las transiciones mueven 1 unidad.
stddev == 0exacto bloquea la entrada; no se infiere comparando bandas.- 16 candidatos cuestan 8 ventanas rodantes; cambiar
ksolo recombina bandas.
Para practicar¶
- Con el fixture, calcula a mano
middle[6],stddev[6],upper[6]ylower[6](ventana 97; 99; 101) y comprueba 99,000000; 1,632993; 100,632993; 97,367007. ¿Por qué con estos valores la barra 7 abre corto? - Rehaz la tabla suponiendo que la receta sí invirtiera en la barra 8 (de −1 a +1 con la reentrada larga). ¿Qué fills cambian y cuál sería la equity final sin costes?
- Lanza el backtest base y deriva
k = 3.0. Comprueba encache_forecastqué features se reutilizan, y compara número de fills y equity final entre ambos.