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 (1min … 1d); 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/upperdebe 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 ~
periodbarras y puede fabricar una entrada.
Sesgos típicos al evaluarla.
- Pocos trades: con
lower = 20yperiod = 28la 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_SESSIONSno 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] == 30exacto no es cruce (tocar no es cruzar);R[t−1] == 30conR[t] > 30sí lo es;R[t] == 50hace 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}) = 3 → first_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:
- Barra 3: la serie es plana, así que
avgG = avgL = 0y 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. - Barras 4–7: el RSI se hunde a 0 y empieza a recuperarse (15,08). Estar en sobreventa no abre posición.
- Barra 8:
30,751708 > 30y15,083799 <= 30→ cruce ascendente → +1. Fill al open de 9 (95,20). - 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. - Barras 10–11: RSI 78 y 88, sobrecompra, pero sin cruce descendente no hay corto.
- Barra 12:
66,187916 < 70y87,861849 >= 70→ corto.exit_longtambién es cierto, pero el estado es 0 y la rama de salida no se evalúa. - 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¶
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.verdictantes que cualquier cifra. ConNT_FEATURESla respuesta real daPASSexacto 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
1minsobre 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_totalfrente 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_ONLINEeindicator_provider: "native"esperasPARITY_FAILen la capafeaturesdesde 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
loweral alza (largo) ouppera 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 ent,<=ent−1; tocar el nivel no es cruzarlo. - 36 candidatos, 4 curvas RSI:
loweryupperson 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¶
- Con el fixture, calcula a mano
RSI[9]a partir deavgG[7] = 5/18,avgL[7] = 152/243y la diferencia+1,00de la barra 9. Debe salir 55,425220. - Rehaz la columna "equity con 5 bps" con 10 bps. ¿Cambia algún redondeo respecto a 5 bps? ¿Cuál es la equity final?
- Lanza el backtest de
P02-be06fay de su derivadoP02-bc0dd2(02ay02c) y comprueba entimings/caché que el segundo reutilizarsi{14}. Después derivaperiod: 7y comparacache_forecast.