Saltar a contenido

P02 · RSI: reversión con retorno desde extremos

Campo Valor
Familia Reversión · oscilador acotado 0..100; entra al volver desde un extremo y sale en la línea media
Features requeridas rsi (primitiva rsi, entrada close; Wilder, escala 0..100)
Parámetros y defaults period = 14, lower = 30, upper = 70; la línea media 50 es constante, no parámetro
Rejilla del catálogo period ∈ {7, 14, 21, 28}, lower ∈ {20, 25, 30}, upper ∈ {70, 75, 80} → 36 candidatos válidos, 4 curvas RSI distintas
variant_keys period, lower, upper
Restricciones period entero ≥ 2; 0 < lower < upper < 100; lower < 50 < upper (si no, PolicyParamError)
first_decision_index period = 14 con defaults (no period + 1: ver más abajo)
Activos y timeframes NVDA y KO en los 7 TF (1min1d); AMZN, INTC, MSFT, TSLA en 1d; BME y FX en 1d con reservas. price_basis = total_return_adjusted recomendado
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: el RelativeStrengthIndex de Nautilus se inicializa en n−1 y devuelve escala 0..1 (nt_init_period_minus_1_scaled_0_1); primera divergencia en la barra 14 de KO 1d (rsi 71,1317 frente a 69,8306). Es una variante declarada, no un bug

Fuentes: docs/estrategias/P02.md (especificación), catalog/strategy_catalog.json, docs/DATOS_POR_ESTRATEGIA.md, runs/parity/summary.json, runs/parity/KO_1d_P02_5bps_NT_ONLINE_native.md, examples/api/02a_*, 02b_*, 02c_*.

Qué vas a aprender

  • Qué mide el RSI de Wilder y por qué sus tres casos degenerados (plano = 50, solo ganancias = 100, solo pérdidas = 0) importan.
  • La diferencia entre operar un nivel ("RSI < 30") y un cruce ("RSI vuelve a subir por encima de 30"), con la convención exacta del proyecto.
  • A seguir una máquina de estados con salida prioritaria y sin reversión: largo y corto nunca se tocan.
  • Por qué 36 candidatos cuestan solo 4 curvas RSI y por qué cambiar lower/upper debe acertar en la caché de features.
  • Qué significa la divergencia del RSI nativo de Nautilus y por qué no se esconde bajo una tolerancia.

La idea en una frase

Cuando el RSI sale de la zona de sobreventa (vuelve a cruzar 30 hacia arriba), comprar; cuando sale de la zona de sobrecompra (vuelve a cruzar 70 hacia abajo), vender en corto; en ambos casos, cerrar al llegar a la línea media 50.

Intuición de mercado

El RSI resume en un número entre 0 y 100 qué parte del movimiento reciente fue de subida: 100 − 100/(1 + ganancia media / pérdida media). Un RSI de 20 dice "en las últimas ~14 barras casi todo fueron caídas". La receta no compra en ese momento (la caída puede continuar), sino cuando el RSI vuelve por encima de 30: la presión vendedora ha empezado a ceder. Y no pretende capturar una tendencia nueva, solo el rebote hasta el equilibrio (50).

Analogía. Un muelle comprimido: no lo sueltas mientras lo siguen aplastando, sino cuando notas que empieza a empujar hacia fuera; y no esperas que salte más allá de su longitud natural (la línea media).

Por qué podría funcionar. En horizontes cortos muchos activos muestran reversión: sobrerreacciones a noticias, liquidaciones forzadas y rebalanceos empujan el precio más allá de su valor y luego se corrigen parcialmente.

Cuándo falla.

  • Tendencias fuertes: en una caída sostenida el RSI puede cruzar 30 al alza, volver a caer y no llegar nunca a 50; el largo aguanta pérdidas hasta que el rebote aparezca (no hay stop).
  • Series planas: un tramo de precios idénticos produce RSI = 50 exacto y la receta se queda quieta (es la regla, no un error).
  • Saltos por dividendos o splits: el RSI trabaja con diferencias; un escalón no ajustado es una "pérdida" enorme y falsa que la RMA arrastra ~period barras y puede fabricar una entrada.

Sesgos típicos al evaluarla.

  • Pocos trades: con lower = 20 y period = 28 la estrategia puede operar muy poco; ordenar candidatos por Sharpe sin exigir un número mínimo de trades es lo que la especificación prohíbe (§10.2).
  • Umbral calculado sobre toda la muestra (percentil 10 del RSI de todo el periodo): mete futuro. Aquí 30/70/50 son constantes.
  • Huecos de sesión en KO (271 sesiones ausentes): una diferencia que abarca varios días infla el RSI; un resultado en KO con DATA_GAP_SESSIONS no es comparable sin más con NVDA.

Las reglas exactas

La regla canónica del catálogo: "Desde plano: largo al cruce ascendente de RSI sobre lower; corto al cruce descendente bajo upper. Cerrar largo si RSI>=50; cerrar corto si RSI<=50. La salida tiene prioridad; no reabrir en esa misma decisión. Es una receta de benchmark, no copia literal de la estrategia integrada de TradingView."

Con R[t] = rsi[t], las cuatro condiciones locales (convención de cruce del proyecto, §5.1 del informe, D-09 revisada: > estricta en t, <= en t−1):

enter_long[t]  :=  R[t] >  lower  and  R[t-1] <= lower
enter_short[t] :=  R[t] <  upper  and  R[t-1] >= upper
exit_long[t]   :=  R[t] >= 50
exit_short[t]  :=  R[t] <= 50
Situación en el cierre de t Objetivo target[t] Motivo (reason)
t < first_decision_index (RSI aún NaN) 0 WARMUP
largo (+1) y R[t] >= 50 0 EXIT
largo (+1) y R[t] < 50 +1 HOLD
corto (−1) y R[t] <= 50 0 EXIT
corto (−1) y R[t] > 50 −1 HOLD
plano y enter_long +1 ENTRY
plano y enter_short −1 ENTRY
plano sin entrada 0 HOLD
R[t] NaN tardío el anterior HOLD
barra no operable (is_tradable = False) se congela el publicado NOT_TRADABLE
  • Entrada: solo desde plano y solo por cruce. Estar en RSI 25 no basta: hay que volver por encima de 30.
  • Salida: por la línea media; tiene prioridad absoluta sobre cualquier entrada de la misma barra.
  • Reversión: prohibida. "No reabrir en esa misma decisión": tras salir, el objetivo queda 0 y la siguiente barra decide desde plano. Todos los deltas valen 1.
  • Filtros: ninguno. Estando largo, un nuevo cruce sobre 30 no hace nada (no hay piramidación).
  • Empates: R[t] == 30 exacto no es cruce (tocar no es cruzar); R[t−1] == 30 con R[t] > 30 lo es; R[t] == 50 hace ciertas las dos salidas a la vez y el resultado sigue siendo determinista.
# decisión en el cierre de t, fill en el open de t+1
state = 0 ; prev = NaN
para cada barra t:
    si t < first_decision_index:  target = 0                      # WARMUP
    si no, si state == +1:        target = 0 si R[t] >= 50 si no +1   # salida primero
    si no, si state == -1:        target = 0 si R[t] <= 50 si no -1   # salida primero
    si no (plano):
        si R[t] > lower y prev <= lower:   target = +1
        si no, si R[t] < upper y prev >= upper: target = -1
        si no:                             target = 0
    si target != state:
        orden(target - state)  -> fill a open[t+1]   # siempre ±1
    state = target ; prev = R[t]                     # prev se guarda aunque sea NaN

Nivel frente a cruce

Una lectura "de nivel" (R >= lower abre largo) entraría en cuanto el RSI estuviera en 60 estando plano: eso ya no es "retorno desde extremos", es otra estrategia y rompe la paridad de señales. La receta compara siempre R[t] y R[t−1], y por eso la política online memoriza _previous en lugar de indexar el array completo.

Los indicadores que usa

Feature Fórmula first_valid
rsi{n} Δ = close[t] − close[t−1]; gain = max(Δ,0), loss = max(−Δ,0); RMA de Wilder (α = 1/n) con semilla = SMA de las primeras n diferencias; RSI = 100 − 100/(1 + avgG/avgL); plano = 50, solo ganancias = 100, solo pérdidas = 0 n

first_decision_index = period. La regla consulta R[t−1], así que el primer cruce posible está en period + 1; aun así la especificación fija period (D-12): en esa barra la política decide legítimamente no entrar, porque NaN <= lower es falso por IEEE-754. Consecuencia: la primera barra con decisión válida nunca contiene una entrada. Con defaults, 14 barras de warmup; con period = 28, 28.

lower y upper no entran en la FeatureSpec: solo {"period": n}. Es el motivo de que la rejilla sea tan barata. Detalle numérico y comparación entre motores en Indicadores paso a paso y Indicadores y features.

Ejemplo barra a barra

Fixture §6 de docs/estrategias/P02.md: period = 3, lower = 30, upper = 70 (period fuera de rejilla a propósito, para calcular la RMA a mano), cash_inicial = 1.000,00, tick 0,01, sin costes. first_valid(rsi{3}) = 3first_decision_index = 3.

Las barras 0–1 son idénticas a la 2 (O=100,00, H=100,20, L=99,80, C=100,00) con RSI en NaN.

t O H L C RSI target orden fill pos. PnL acum. sin costes equity con 5 bps*
2 100,00 100,20 99,80 100,00 NaN 0 WARMUP 0 0,00 1.000,00
3 100,00 100,20 99,80 100,00 50,000000 0 HOLD (plano = 50) 0 0,00 1.000,00
4 99,50 99,70 97,80 98,00 0,000000 0 HOLD 0 0,00 1.000,00
5 97,50 97,70 95,80 96,00 0,000000 0 HOLD 0 0,00 1.000,00
6 95,50 95,70 93,80 94,00 0,000000 0 HOLD 0 0,00 1.000,00
7 94,00 94,80 93,90 94,50 15,083799 0 HOLD 0 0,00 1.000,00
8 94,60 95,20 94,40 95,00 30,751708 +1 ENTRY BUY 1 0 0,00 1.000,00
9 95,20 96,20 95,00 96,00 55,425220 0 EXIT SELL 1 95,20 +1 0,80 1.000,75
10 96,50 98,20 96,30 98,00 78,454996 0 HOLD 96,50 0 1,30 1.001,25
11 98,50 100,20 98,30 100,00 87,861849 0 HOLD 0 1,30 1.001,25
12 99,80 99,90 98,80 99,00 66,187916 −1 ENTRY SELL 1 0 1,30 1.001,25
13 98,80 98,90 97,80 98,00 48,311539 0 EXIT BUY 1 98,80 −1 2,10 1.002,00
14 97,80 97,90 96,80 97,00 34,382309 0 HOLD 97,80 0 2,30 1.002,15
15 96,80 96,90 95,80 96,00 24,001922 0 HOLD 0 2,30 1.002,15

OHLC, RSI, objetivos, motivos, fills y la equity del primer trade son literales del fixture; la columna "PnL acumulado" es la equity del fixture menos 1.000,00 (el fixture da la equity en los cierres 8, 9, 10, 13 y la final; las barras intermedias sin posición repiten la caja). *"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 en la barra del fill:

Fill Barra Precio Cuenta Comisión
1 BUY 1 9 95,20 95,20 · 0,0005 = 0,04760 0,05
2 SELL 1 10 96,50 96,50 · 0,0005 = 0,04825 0,05
3 SELL 1 13 98,80 98,80 · 0,0005 = 0,04940 0,05
4 BUY 1 14 97,80 97,80 · 0,0005 = 0,04890 0,05

Total 0,20: la equity final con costes es 1.002,30 − 0,20 = 1.002,10.

Lo que hay que ver en la tabla:

  1. Barra 3: la serie es plana, así que avgG = avgL = 0 y el RSI vale 50 exacto, no NaN ni una división por cero. Las dos salidas son ciertas a la vez, pero el estado es 0 y no hay cruce (el RSI previo es NaN): objetivo 0.
  2. Barras 4–7: el RSI se hunde a 0 y empieza a recuperarse (15,08). Estar en sobreventa no abre posición.
  3. Barra 8: 30,751708 > 30 y 15,083799 <= 30 → cruce ascendente → +1. Fill al open de 9 (95,20).
  4. Barra 9: el RSI salta a 55,43 ≥ 50 → salida. El largo ha vivido una sola barra: la regla de salida es muy corta con period = 3.
  5. Barras 10–11: RSI 78 y 88, sobrecompra, pero sin cruce descendente no hay corto.
  6. Barra 12: 66,187916 < 70 y 87,861849 >= 70 → corto. exit_long también es cierto, pero el estado es 0 y la rama de salida no se evalúa.
  7. Barra 13: 48,31 <= 50 → cierre del corto. Ningún delta vale 2: P02 nunca invierte.

Las cuentas de las barras clave

Semilla (t=3):   diferencias 0, 0, 0  -> avgG = avgL = 0  -> RSI[3] = 50 (caso plano)
avgG (α = 1/3):  avgG[6] = (1/3)·0,5 + (2/3)·0           = 1/6  = 0,166667
                 avgG[7] = (1/3)·0,5 + (2/3)·(1/6)       = 5/18 = 0,277778
avgL:            avgL[3] = 2/3 = 0,666667 ; avgL[4] = 10/9 = 1,111111
                 avgL[5] = 38/27 = 1,407407 ; avgL[6] = 76/81 = 0,938272
                 avgL[7] = (2/3)·(76/81) = 152/243       = 0,625514
Entrada (t=8):   RS = (5/18)/(152/243) = 1215/2736       = 0,444079
                 RSI[8] = 100 − 100/1,444079             = 30,751708 (exacto 13500/439)
                 30,751708 > 30  y  RSI[7] = 15,083799 <= 30  -> ENTRY +1
Corto (t=12):    avgG = (2/3)·1,341564 = 0,894376 ; avgL = (1/3)·1 + (2/3)·0,185338 = 0,456892
                 RS = 1,957530 ; RSI[12] = 100 − 100/2,957530 = 66,187916
                 66,187916 < 70  y  RSI[11] = 87,861849 >= 70  -> ENTRY −1
Caja:            1.000,00 − 95,20 = 904,80        (fill 1, open[9])
                 904,80 + 96,50   = 1.001,30      (fill 2, open[10])
                 1.001,30 + 98,80 = 1.100,10      (fill 3, open[13]); equity cierre 13 = 1.100,10 − 98,00 = 1.002,10
                 1.100,10 − 97,80 = 1.002,30      (fill 4, open[14]); plano hasta el final

El índice de las diferencias va desplazado: la diferencia j es la de la barra j+1, así que avgG[7] y avgL[7] son las que usa la barra 8.

Diagrama

RSI con zonas 30/70, línea media 50, entradas por cruce y salidas en la media

flowchart LR
    W(["WARMUP: t < period"]) -->|"RSI válido"| FLAT["Plano 0"]
    FLAT -->|"R cruza lower al alza · BUY 1"| L["Largo +1"]
    FLAT -->|"R cruza upper a la baja · SELL 1"| S["Corto -1"]
    FLAT -->|"sin cruce"| FLAT
    L -->|"R >= 50 · SELL 1"| FLAT
    S -->|"R <= 50 · BUY 1"| FLAT
    L -->|"R < 50"| L
    S -->|"R > 50"| S

No hay flecha entre largo y corto: toda posición vuelve a plano antes de abrir la contraria, y como mínimo pasa una barra entera en plano.

Cómo lanzarla

Backtest con ambos motores sobre NVDA 5min en 2024 con los defaults (petición real examples/api/02a_backtest_p02_baseline.request.json, que referencia la instancia P02-be06fa; aquí con la forma "inline" de la API de backtesting):

BASE=http://localhost:8000
SID=$(curl -sS -X POST "$BASE/v1/backtest" -H 'Content-Type: application/json' -d '{
  "request_id": "manual-p02-0001",
  "correlation_id": null,
  "schema_version": "v1",
  "payload": {
    "strategy": {"template_id": "P02", "params": {"period": 14, "lower": 30, "upper": 70},
                 "name": "RSI 30/70 baseline", "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", "indicator_provider": "native"}}
  }}' | jq -r '.payload.signature.strategy_id')
echo "$SID"    # P02-be06fa con estos parámetros
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": "P02",
                 "params": {"period": 14, "lower": 30, "upper": 70}, "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"]          # P02-be06fa
print(sid, r["parity"]["verdict"])
for res in r["results"]:
    print(res["adapter"], res["run_metrics"]["final_equity"], res["run_metrics"]["n_fills"])

La respuesta real (02a_*.response.json) da PASS en las cuatro capas (features max_abs_diff = 0, 19.536 barras comparadas desde el índice 14; 550 fills idénticos), equity final 99.921,49 y 29,54 de comisiones en ambos adaptadores.

Derivar una variante con umbrales más extremos (petición real 02b_derive_p02_25_75.request.json):

curl -sS -X POST "$BASE/v1/strategies/$SID/derive" -H 'Content-Type: application/json' -d '{
  "request_id": "manual-p02-derive", "correlation_id": null, "schema_version": "v1",
  "payload": {"param_overrides": {"lower": 25, "upper": 75}, "name": "RSI 14 25/75 extremos",
              "slug": "rsi-14-25-75", "tags": ["fase-A", "sensibilidad"]}}' \
  | jq '.payload.strategy_id, .payload.cache_forecast'

lower y upper son parámetros de política. La respuesta real crea P02-bc0dd2 y su cache_forecast dice expected_feature_hits: ["rsi"], expected_feature_misses: [], expected_result_hit: false: el RSI14 se reutiliza íntegro y solo se rehacen política y ledger ("un hit del resultado completo sería un bug"). Su backtest (02c_*) da 342 fills y equity 99.942,12. Si en cambio derivas period: 21, es parámetro de feature: fallo de caché en rsi{21}.

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-p02-sweep", "correlation_id": null, "schema_version": "v1",
  "payload": {"base_strategy_id": "'"$SID"'",
    "grid": {"period": [7, 14, 21, 28], "lower": [20, 25, 30], "upper": [70, 75, 80]},
    "candidate_policy": {"drop_invalid": true, "drop_duplicates": true,
                         "register_strategies": true,
                         "name_template": "{template_name} {period} {lower}/{upper}"},
    "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, 4 curvas RSI. El resultado se consulta en GET /v1/jobs/{job_id}.

Cuánto cuesta la rejilla

Concepto Cuenta
Candidatos cartesianos 4 period × 3 lower × 3 upper = 36
Inválidos 0 (lower máximo 30 < 50 < upper mínimo 70)
Curvas RSI distintas {7, 14, 21, 28} = 4
Peticiones lógicas de feature 36 → 4 cálculos y 32 aciertos: factor 9×, el mayor del grupo G0
Políticas y ledgers 36 y 36: cada terna es otra serie de objetivos

Es el ejemplo canónico de "en P02, muchos umbrales comparten una RSI". Contrasta con P01, donde los dos parámetros son de feature.

Qué mirar en los resultados

  • parity.verdict antes que cualquier cifra. Con NT_FEATURES la respuesta real da PASS exacto en features, señales, fills y equity.
  • Número de entradas y salidas, y barras en +1/−1/0: P02 pasa mucho tiempo plano. En P02 el número de fills iguala la suma de |delta| porque nunca invierte (en P01 no).
  • Duración media del trade y fracción de barras con RSI = 50 exacto: en 1min sobre KO los tramos planos pueden ser muchos y explican por sí solos el recuento de trades.
  • Número mínimo de trades antes de comparar candidatos (§10.2).
  • costs_total frente a la ganancia bruta: 550 fills con 5 bps suman 29,54 en el ejemplo real; en intradía el margen por trade es fino.
  • Divergencia conocida: con NT_ONLINE e indicator_provider: "native" esperas PARITY_FAIL en la capa features desde la barra 14. El informe de KO 1d muestra que el lado canónico abre un corto en la barra 15 y el nativo no: la divergencia cambia el lado de la comparación con 70, que es justo lo que decide P02, y por eso no se reescala para forzar la igualdad.

Causalidad comprobada

Test de "futuros alterados" de la especificación: KO 1d, 500 barras, {14, 30, 70}; se multiplican por 0,70 los cierres posteriores a t0 = 300. Deben quedar idénticos bit a bit en [0, 300] el RSI, objetivos, motivos, decision_index_valid, el estado interno (_target y _previous) al cierre de 300, los fills con fill_index ≤ 300 y la equity hasta el cierre 300. El test caza en particular una semilla de RMA calculada con la media global de las diferencias.

Checkpoint mínimo para continue: avgG[t0−1], avgL[t0−1], close[t0−1], el objetivo vigente y _previous = rsi[t0−1] (sin él no se puede evaluar el cruce de la primera barra), más el estado del ledger. Copiar el checkpoint de P01 no basta.

Errores típicos

Error Consecuencia Cómo evitarlo
lower >= 50 o upper <= 50 PolicyParamError: la entrada y su salida caerían en la misma barra Respeta lower < 50 < upper
lower <= 0, upper >= 100, lower >= upper, period < 2 PolicyParamError; en un sweep cuentan como inválidos, no desaparecen del denominador La API lo rechaza con el motivo
Pasar una clave midline Rechazada: la línea media no es parámetro 50 es constante de la política
Entrar por nivel (R < 30 compra) Otra estrategia; señales distintas Exige R[t−1] <= lower y R[t] > lower
Esperar reversión directa de largo a corto Tu backtest manual no cuadra Siempre pasa por plano
Reiniciar la política sin reiniciar _previous Cruce inventado en la primera barra de un rango nuevo initialize() pone _previous = NaN
Usar precios brutos con dividendos Diferencias falsas que dominan la RMA price_basis = total_return_adjusted
Comparar RSI 0..1 con umbrales 0..100 Señales absurdas El RSI nativo de Nautilus es 0..1; el canónico, 0..100

Variantes y extensiones

  • Solo largos: target_lots = [0, 1], ignorando la rama corta; habitual en acciones.
  • Salida en el umbral opuesto (70 para el largo) en lugar de 50: más recorrido y más riesgo; es otra receta.
  • Filtro de tendencia: operar solo largos si el precio está sobre una media larga (P01); o solo con ADX bajo (P15), régimen lateral.
  • Parientes: P04 Bollinger reentrada (misma lógica de "volver desde el extremo", sobre el precio) y P17 StochRSI (el RSI pasado por un estocástico).

Resumen

  • P02 entra desde plano cuando el RSI vuelve a cruzar lower al alza (largo) o upper a la baja (corto), y sale al alcanzar 50.
  • La salida tiene prioridad y nunca hay reversión: todos los deltas valen 1.
  • Cruce del proyecto: > estricta en t, <= en t−1; tocar el nivel no es cruzarlo.
  • 36 candidatos, 4 curvas RSI: lower y upper son de política y deben acertar en la caché de features.
  • Paridad PASS salvo el RSI nativo de Nautilus (0..1 e inicialización en n−1), variante declarada.

Para practicar

  1. Con el fixture, calcula a mano RSI[9] a partir de avgG[7] = 5/18, avgL[7] = 152/243 y la diferencia +1,00 de la barra 9. Debe salir 55,425220.
  2. Rehaz la columna "equity con 5 bps" con 10 bps. ¿Cambia algún redondeo respecto a 5 bps? ¿Cuál es la equity final?
  3. Lanza el backtest de P02-be06fa y de su derivado P02-bc0dd2 (02a y 02c) y comprueba en timings/caché que el segundo reutiliza rsi{14}. Después deriva period: 7 y compara cache_forecast.