Saltar a contenido

P13 · VWAP de sesión: reversión

Campo Valor
Familia Reversión anclada a la sesión · z-score del cierre frente al VWAP intradía; alterna plano y posición, nunca overnight
Features requeridas session_vwap (sin parámetros; salidas vwap, var, cum_volume; depende de hlc3) y session_position (sin parámetros; usa minutes_since_session_open, is_penultimate_regular_bar, is_last_regular_bar); la política lee además close y session_id
Parámetros y defaults z_entry = 2.0, min_minutes = 30
Rejilla del catálogo z_entry ∈ {1.0, 1.5, 2.0, 2.5}, min_minutes ∈ {15, 30, 60} → 12 candidatos en 1min, 5min y 15min (8 en 2min y 30min, 4 en 1h)
variant_keys z_entry, min_minutes
Restricciones z_entry > 0; min_minutes entero ≥ 1 y múltiplo de la duración de la barra; timeframe intradía (1d → PolicyParamError)
first_decision_index 0 en la práctica (primera barra de la primera sesión presente; no hay medias ni recursión global). Dentro de cada sesión: minutes_since_session_open >= min_minutes, var > 0 y un z[t−1] válido de la misma sesión
Activos y timeframes Solo NVDA y KO (twelvedata, XNYS, con volumen real) en 1min1h. UNSUPPORTED en 1d, en las series diarias de Yahoo, en BME y en FX (sin volumen)
Paridad en la matriz En NT_FEATURES, NT_INTENT_REPLAY y NT_ONLINE(vectorta): PASS 4/12 (NVDA 5min y 1min, ambos costes) y UNSUPPORTED 8/12: en 1d, PolicyParamError "var = 0 por construcción en 1d"; en 1h, PolicyParamError "min_minutes=30: debe ser múltiplo de la duración de la barra (60 min en 1h)". NT_ONLINE(native): UNSUPPORTED 12/12 ("nautilus_trader 1.231.0 no ofrece un indicador nativo para 'session_vwap'")

Fuentes: docs/estrategias/P13.md (especificación), catalog/strategy_catalog.json, docs/DATOS_POR_ESTRATEGIA.md, runs/parity/summary.json.

Qué vas a aprender

  • Cómo se calculan el VWAP y la varianza ponderada de la sesión con tres acumulados, y por qué se reinician en cada sesión.
  • Por qué la primera barra de cada sesión nunca tiene z-score (var = 0 es una identidad) y la primera evaluable nunca puede entrar.
  • La diferencia entre entrar al volver del extremo (cruce) y salir por nivel (z >= 0), y por qué el fixture lo necesita.
  • Por qué los 12 candidatos de la rejilla comparten una sola curva de VWAP: los dos parámetros son de política.
  • El riesgo numérico propio de P13: la cancelación catastrófica en var.

La idea en una frase

Cuando el cierre se ha alejado del VWAP de la sesión más de z_entry desviaciones típicas y vuelve hacia él, entra apostando por la vuelta; sal cuando el z-score llegue a cero o antes del cierre del día.

Intuición de mercado

El VWAP es el precio medio al que se ha negociado cada acción del día, ponderado por volumen. Muchos algoritmos institucionales ejecutan contra él, así que actúa como un imán: los alejamientos grandes tienden a corregirse dentro de la sesión. Dividir la distancia por la desviación típica ponderada convierte "está lejos" en una medida comparable entre días tranquilos y agitados.

Analogía. Un perro atado con una correa elástica a un poste que se mueve despacio (el VWAP). Cuando el perro se ha ido muy lejos y empieza a volver, apuestas por que llegue al poste. No apuestas cuando todavía está tirando hacia fuera: eso sería "coger un cuchillo que cae".

Por qué podría funcionar. Reversión intradía hacia el precio de referencia de ejecución, sobre todo en valores líquidos y días sin noticias.

Cuándo falla.

  • Días de tendencia: el precio se aleja y no vuelve; la regla no tiene stop (la barra 11 del fixture mantiene un corto con z = 2,06) y solo el cierre programado limita la pérdida.
  • Primeras barras de la sesión: la varianza es minúscula y los z-scores explotan.
  • Comisiones: la posición puede durar una sola barra (la primera del fixture); con 5 bps la receta es muy sensible al coste.

Sesgos típicos al evaluarla.

  • VWAP de la sesión completa difundido: calcular con un groupby el VWAP final del día y compararlo con el cierre de cada barra. A media mañana "sabrías" dónde cierra el VWAP.
  • z[t−1] de ayer: un shift global forma "cruces" entre dos distribuciones distintas.
  • Tolerancias heredadas: var pierde dígitos por cancelación; la paridad de var y z se compara con rtol = 1e-7 (declarado de antemano), pero las señales, exactas.

Las reglas exactas

La regla canónica del catálogo: "VWAP=sum(V·HLC3)/sum(V), var=max(sum(V·HLC3²)/sum(V)-VWAP²,0), z=(C−VWAP)/sqrt(var). Tras M minutos, largo al retorno ascendente por −z_entry desde abajo; corto al retorno descendente por +z_entry. Salir al cruzar cero o con el mismo cierre programado que ORB."

Definiciones (spec §4.b):

  • z[t] = (close[t] − vwap[t]) / sqrt(var[t]), inválido (NaN) si var == 0 o cum_volume == 0.
  • after_min[t] = minutes_since_session_open[t] >= min_minutes (la barra que cierra justo a los M minutos es evaluable).
  • Cruce según la convención del proyecto (§5.1): estricto en t, no estricto en t−1, con t−1 en la misma sesión.
Situación en el cierre de t (en este orden) Objetivo target[t] Motivo (reason)
Antes de la primera sesión presente 0 WARMUP
Cambio de session_id reinicia state = 0 y z_prev = NaN (los acumulados los reinicia la feature) (reinicio)
Última barra regular 0 HOLD / EXIT
Penúltima barra regular (cierre programado) 0 EXIT si había posición
Largo y z[t] >= 0 0 EXIT
Corto y z[t] <= 0 0 EXIT
Plano, after_min, z[t] > −z_entry y z[t−1] <= −z_entry +1 ENTRY
Plano, after_min, z[t] < z_entry y z[t−1] >= z_entry −1 ENTRY
Resto (antes de M, z inválido, sin cruce) el anterior HOLD
Barra no operable se congela, salvo en penúltima y última NOT_TRADABLE
  • Entrada: al volver del extremo, no al llegar a él.
  • Salida: por nivel (z >= 0 en largo), no por cruce; z == 0 exacto cierra. Con la lectura de cruce, la salida de la barra 5 del fixture no ocurriría nunca.
  • Reentradas: permitidas dentro de la sesión (el catálogo de P12 limita a una; el de P13 no). Es la diferencia de regla más visible con P12.
  • Nunca overnight: el cierre programado va antes de cualquier comparación con z, así que un NaN no deja posiciones abiertas.
# decisión en el cierre de t, fill en el open de t+1
para cada barra t:
    si session_id[t] != session_id[t-1]:  state = 0 ; z_prev = NaN      # sesión nueva
    z = (close[t] - vwap[t]) / sqrt(var[t])  si var[t] > 0 y cum_v[t] > 0  si no NaN
    si es_ultima[t] o es_penultima[t]:    target = 0                      # cierre programado
    si no, si state == +1:                target = 0 si z >= 0 si no +1   # NaN conserva
    si no, si state == -1:                target = 0 si z <= 0 si no -1
    si no, si after_min[t] y z > -z_entry y z_prev <= -z_entry:  target = +1
    si no, si after_min[t] y z <  z_entry y z_prev >=  z_entry:  target = -1
    si no:                                target = 0
    si target != state:
        orden(target - state)  -> fill a open[t+1]
    state = target ; z_prev = z

Acumulados hasta t inclusive, y solo de la sesión

vwap[t] usa las barras de la sesión hasta t, nunca el total del día; y el z[t−1] de la primera barra de hoy es NaN, nunca el de ayer. Son las dos fugas silenciosas de P13: dan números plausibles y una estrategia distinta.

Los indicadores que usa

Feature Fórmula Validez
hlc3 (high + low + close) / 3 0
session_vwap.cum_volume Σ volume de la sesión hasta t desde la primera barra de cada sesión (session_local)
session_vwap.vwap Σ(v·hlc3) / Σv si Σv > 0
session_vwap.var max(Σ(v·hlc3²)/Σv − vwap², 0) (poblacional, sin Bessel) si vwap es válido; puede valer 0 exacto
session_position.minutes_since_session_open (market_close_ns − apertura del calendario) / 60e9 siempre
session_position.is_penultimate_regular_bar / is_last_regular_bar posición en la rejilla del calendario siempre

z no es una salida de feature: la calcula la política. La feature reporta el hecho (var = 0); la política decide que entonces no hay z. Los minutos se publican como f64 de 0 a ~390 y no como timestamp en nanosegundos, porque un 1,73e18 no cabe exactamente en f64.

first_decision_index = primera barra de la primera sesión presente (0 en NVDA 5min según la matriz). Ambas features llevan calendar_version en la clave de caché. Más contexto en Las 23 primitivas de fase B y en Indicadores y features.

Ejemplo barra a barra

Fixture §6 de docs/estrategias/P13.md: barras de 30 minutos, min_minutes = 60, z_entry = 2.0, las mismas dos sesiones cortas reales que P12: A = 2024-07-03 (EDT) y B = 2024-11-29 (EST), 8 barras cada una. cash_inicial = 1.000,00, tick 0,01, sin costes. Valores a 4 decimales (la aritmética es f64). No hay barra WARMUP: los ceros antes de M son HOLD (decisión informada).

Se omiten la barra 7 (última de A: z 1,0380, objetivo 0) y la 13 (z −0,8261, sin señal).

t sesión · local O H L C V Σv vwap var z min. pen/últ target orden fill pos. PnL acum. sin costes equity con 5 bps*
0 A · 09:30 100,00 100,40 99,90 100,20 1000 1000 100,1667 0,0000 30 0 HOLD 0 0,00 1.000,00
1 A · 10:00 100,20 100,60 100,10 100,50 1000 2000 100,2833 0,0136 1,8571 60 0 HOLD (sin z previo) 0 0,00 1.000,00
2 A · 10:30 100,50 100,70 100,30 100,40 1000 3000 100,3444 0,0165 0,4319 90 0 HOLD 0 0,00 1.000,00
3 A · 11:00 100,40 100,50 99,60 99,70 2000 5000 100,1800 0,0505 −2,1362 120 0 (se arma) 0 0,00 1.000,00
4 A · 11:30 99,70 100,40 99,65 100,30 1000 6000 100,1694 0,0426 0,6323 150 +1 ENTRY BUY 1 0 0,00 1.000,00
5 A · 12:00 100,30 100,60 100,20 100,55 1000 7000 100,2095 0,0462 1,5844 180 0 EXIT (z ≥ 0) SELL 1 100,30 +1 0,25 1.000,20
6 A · 12:30 100,55 100,70 100,35 100,45 1000 8000 100,2458 0,0496 0,9164 210 pen 0 cierre prog. 100,55 0 0,25 1.000,15
8 B · 09:30 99,00 99,30 98,90 99,10 800 800 99,1000 0,0000 30 0 HOLD (sesión nueva) 0 0,25 1.000,15
9 B · 10:00 99,10 99,40 99,00 99,35 800 1600 99,1750 0,0056 2,3333 60 0 (se arma) 0 0,25 1.000,15
10 B · 10:30 99,35 99,50 99,20 99,25 800 2400 99,2222 0,0082 0,3066 90 −1 ENTRY SELL 1 0 0,25 1.000,15
11 B · 11:00 99,25 100,20 99,20 100,10 1600 4000 99,4667 0,0946 2,0596 120 −1 (sin stop) 99,25 −1 −0,60 999,25
12 B · 11:30 100,10 100,20 99,30 99,40 800 4800 99,4944 0,0827 −0,3285 150 0 EXIT (z ≤ 0) BUY 1 −1 0,10 999,95
14 B · 12:30 99,25 99,40 99,05 99,15 800 6400 99,4396 0,0724 −1,0760 210 pen 0 cierre prog. 0 0,10 999,90
15 B · 13:00–13:01 99,15 99,30 99,00 99,20 800 7200 99,4093 0,0717 −0,7813 211 últ 0 0 0,10 999,90

El fill de la orden de la barra 12 (BUY 1) ocurre en la barra 13 omitida, a open[13] = 99,40. OHLC, volúmenes, features, z, objetivos, los cuatro fills (4→5 a 100,30; 5→6 a 100,55; 10→11 a 99,25; 12→13 a 99,40) y la equity en las barras 5, 6, 11 y 15 son literales del fixture; el resto del PnL es caja + pos · close − 1.000,00 con la caja del fixture. *"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 1 (barra 5): 100,30 · 0,0005 = 0,05015 → 0,05;
  • fill 2 (barra 6): 100,55 · 0,0005 = 0,050275 → 0,05;
  • fill 3 (barra 11): 99,25 · 0,0005 = 0,049625 → 0,05;
  • fill 4 (barra 13): 99,40 · 0,0005 = 0,0497 → 0,05.

Total 0,20: el beneficio bruto de 0,10 se convierte en una pérdida de 0,10 (equity final 999,90). Es la sensibilidad al coste que la spec anticipa para posiciones de una o dos barras.

Lo que hay que ver en la tabla:

  1. Barras 0 y 8: var = 0 exacto (una sola barra acumulada: Σ(v·p²)/Σv = p² = vwap²). No hay z. Además, antes de M.
  2. Barras 1 y 9: ya son evaluables (60 >= 60), pero z[t−1] es NaN: la primera barra evaluable de una sesión nunca entra.
  3. Barra 3 → 4: z pasa de −2,1362 (extremo) a 0,6323: retorno ascendente por −2 → largo. No se entra en la 3 (llegar al extremo no basta).
  4. Barra 5: z = 1,5844 >= 0 → salida por nivel. Como z[4] ya era positivo, una regla de "cruce de cero" no habría cerrado nunca. La posición dura una barra.
  5. Barra 8: los acumulados vuelven a empezar (Σv = 800, no 9.800); si no, el VWAP de B estaría dominado por los precios de ~100,3 de A.
  6. Barra 11: el corto sigue con z = 2,0596 > z_entry: no hay stop.
  7. Barras 6 y 14: cierres programados sin posición: lo importante es que no se abre nada. Ninguna posición llega viva al cierre programado en este fixture (sí en el de P12).

Las cuentas de las barras clave

Barra 0:  hlc3 = (100,40 + 99,90 + 100,20)/3 = 100,166667
          var = max(10033,361111 − 100,166667², 0) = 0              -> z inválido
          minutes = (10:00 − 09:30) = 30 < 60                        -> no evaluable
Barra 3:  cum_v = 5000 ; cum_vp = 500900,000000 ; cum_vp2 = 50180463,333333
          vwap = 100,180000 ; var = 10036,092667 − 10036,032400 = 0,050489
          z = (99,70 − 100,18)/0,224697 = −2,136207 < −2
Barra 4:  cum_v = 6000 ; vwap = 100,169444 ; var = 10033,968519 − 10033,925887 = 0,042631
          z = (100,30 − 100,169444)/0,206473 = 0,632312
          enter_long = (0,6323 > −2) y (−2,1362 <= −2) y after_min   -> +1
Barra 5:  z = (100,55 − 100,209524)/0,214893 = 1,584396 >= 0         -> 0
Barra 9:  var = 9835,686250 − 9835,680625 = 0,005625 (quedan 4 cifras de 10)
          z = (99,35 − 99,175)/0,075 = 2,333333
Barra 10: z = (99,25 − 99,222222)/0,090608 = 0,306570
          enter_short = (0,3066 < 2) y (2,3333 >= 2)                 -> −1
Caja:  1.000,00 − 100,30 = 899,70 ; + 100,55 = 1.000,25 ; + 99,25 = 1.099,50 ; − 99,40 = 1.000,10
Equity: cierre 5 = 899,70 + 100,55 = 1.000,25 ; cierre 11 = 1.099,50 − 100,10 = 999,40 ; final 1.000,10

Observa la resta de la barra 9: de diez cifras significativas en cada operando quedan cuatro. Con NVDA a 1.200 USD antes del split de 2024 se pierden ~8 de los ~16 dígitos de f64 (spec §8.4).

Diagrama

flowchart LR
    NS(["Sesión nueva: state 0, z_prev = NaN"]) --> WAIT["Plano, antes de M o sin z"]
    WAIT -->|"after_min y var > 0"| FLAT["Plano evaluable"]
    FLAT -->|"z_prev <= -z_entry y z > -z_entry · BUY 1"| L["Largo +1"]
    FLAT -->|"z_prev >= z_entry y z < z_entry · SELL 1"| S["Corto -1"]
    L -->|"z >= 0 · SELL 1"| FLAT
    S -->|"z <= 0 · BUY 1"| FLAT
    L -->|"penúltima barra · SELL 1"| END["Fin de sesión: plano"]
    S -->|"penúltima barra · BUY 1"| END
    FLAT -->|"penúltima barra"| END
    END -->|"cambio de session_id"| NS

A diferencia de P12, tras salir se vuelve a "plano evaluable": se puede reentrar en la misma sesión.

Cómo lanzarla

Backtest con ambos motores sobre NVDA 5min en 2024, con los defaults:

BASE=http://localhost:8000
SID=$(curl -sS -X POST "$BASE/v1/backtest" -H 'Content-Type: application/json' -d '{
  "request_id": "manual-p13-0001",
  "correlation_id": null,
  "schema_version": "v1",
  "payload": {
    "strategy": {"template_id": "P13", "params": {"z_entry": 2.0, "min_minutes": 30},
                 "name": "VWAP z 2.0 / 30 min 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": "P13", "params": {"z_entry": 2.0, "min_minutes": 30},
                 "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"])

Derivar una variante más exigente (z_entry = 2.5):

curl -sS -X POST "$BASE/v1/strategies/$SID/derive" -H 'Content-Type: application/json' -d '{
  "request_id": "manual-p13-derive", "correlation_id": null, "schema_version": "v1",
  "payload": {"param_overrides": {"z_entry": 2.5}, "name": "VWAP z 2.5 / 30 min",
              "slug": "vwap-z25-30", "tags": ["sensibilidad"]}}' | jq '.payload.strategy_id, .payload.cache_forecast'

z_entry es parámetro de política (tabla de invalidación, §5.3 de la spec): session_vwap y session_position se reutilizan íntegras y solo se rehacen política y ledger, como en el derivado real de P02 (P02-bc0dd2). Lo mismo vale para min_minutes: a diferencia del range_minutes de P12, no redefine ninguna feature.

Sweep de sus variant_keys:

curl -sS -X POST "$BASE/v1/sweep" -H 'Content-Type: application/json' -d '{
  "request_id": "manual-p13-sweep", "correlation_id": null, "schema_version": "v1",
  "payload": {"base_strategy_id": "'"$SID"'",
    "grid": {"z_entry": [1.0, 1.5, 2.0, 2.5], "min_minutes": [15, 30, 60]},
    "candidate_policy": {"drop_invalid": true, "drop_duplicates": true,
                         "register_strategies": true,
                         "name_template": "{template_name} {z_entry}/{min_minutes}"},
    "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'

Resultado en GET /v1/jobs/{job_id}.

Cuánto cuesta la rejilla

Concepto Cuenta (spec §5.4)
Candidatos cartesianos 4 z_entry × 3 min_minutes = 12
Válidos en 5min (o 1min, 15min) 12; en 2min y 30min, 8; en 1h, 4; en 1d, 0
Total sobre los 6 TF intradía 56
session_vwap distintas por TF 1 (11 aciertos de caché)
session_position distintas por TF 1, compartida además con P12
hlc3 1
Políticas y ledgers 12 y 12

Es el mejor caso de factorización de su grupo y el contraste exacto con P12, donde cada valor de M es un rango nuevo.

Qué mirar en los resultados

  • Exposición overnight = 0 exactamente.
  • Trades por sesión (distribución, no acotada a {0, 1}) y duración media de la posición: si la mediana es 1 barra, la comisión manda.
  • Reparto de salidas: cruce de cero frente a cierre programado.
  • Barras con var == 0 (≈ una por sesión en TF finos), con cum_volume == 0 y con volume == 0 (alto en KO 1min).
  • Barras con |z| a menos de 1e-6 de ±z_entry o de 0: señal numéricamente frágil; un recuento alto invalida la paridad exacta y cualquier comparación de velocidad.
  • missing_sessions / DATA_GAP_SESSIONS (KO: 271) aparte de las sesiones truncadas: una sesión truncada sí opera (el VWAP de un tramo observado está bien definido; al revés que en P12).
  • Paridad: PASS en NVDA 5min y 1min. VolumeWeightedAveragePrice de Nautilus no sirve: reinicia por día natural UTC (medianoche UTC = 19:00 en EST) y no da varianza.

Causalidad comprobada

La spec (§8.2) perturba precios y volúmenes de las barras 5–7 y exige vwap, var, z, objetivos en 0–4 y el fill de la señal 4 idénticos bit a bit. La variante más afilada multiplica por 100 solo el volumen de las barras 5–7: con acumulados correctos, vwap[0..4] no se mueve ni un bit; con un VWAP de sesión difundido, sí. Otra variante aísla las dos sesiones entre sí (reinicio de acumulados y de z_prev).

Para continue a mitad de sesión hacen falta target, z_prev (NaN si no hay), session_id, los tres acumulados y el ancla: sin los acumulados el VWAP arrancaría de cero a media mañana, con números plausibles y falsos. Lo robusto es cortar en un límite de sesión.

Errores típicos

Error Consecuencia Cómo evitarlo
timeframe = "1d" PolicyParamError: "var = 0 por construcción en 1d" Solo intradía de NVDA o KO
min_minutes no múltiplo de la barra (30 en 1h) PolicyParamError 60 en 1h; cualquier valor de la rejilla en 1min, 5min, 15min
z_entry <= 0 PolicyParamError (entrada y salida coincidirían) Umbral positivo
VWAP de toda la sesión en cada barra Fuga de futuro Acumulados hasta t inclusive
No reiniciar acumulados o z_prev por sesión VWAP contaminado por ayer; cruces inventados Detectar cambio de session_id
Salida por cruce de cero Posiciones que no se cierran hasta el reloj Salida por nivel (z >= 0)
Aplicar min_minutes con > Desplaza una barra la primera evaluable >=
Usar vector_ta.vwap o el VWAP de Nautilus sin verificar el reinicio Otra estrategia con números plausibles session_vwap propia (custom_reference)
Sustituir la fórmula por Welford "porque es más estable" Otros números, otra formula_version La receta es la del catálogo; Welford sería una versión nueva comparable
Ejecutar en FX Sin volumen: Σv = 0 UNSUPPORTED, sin proxy

Variantes y extensiones

  • Una entrada por sesión: añadir el candado de P12; cambia formula_version.
  • session_vwap con Welford ponderado (formula_version = "2"): recomendada por la spec para producción, comparable contra la actual.
  • Stop por z extremo (p. ej. salir si |z| > 3): parámetro nuevo, otra receta.
  • z como salida de la feature: se calcularía una vez para los 12 candidatos; propuesta abierta en la spec.
  • Parientes: P12 ORB (misma sesión y mismo cierre programado, lógica de ruptura), P02 RSI (misma estructura de entrada por retorno y salida por nivel medio) y P04 (reversión a bandas).

Resumen

  • P13 mide la distancia del cierre al VWAP de la sesión en desviaciones típicas ponderadas, con tres acumulados que se reinician en cada sesión.
  • Entra al volver de ∓z_entry tras min_minutes; sale por nivel en z = 0 o con el cierre programado; puede reentrar en la misma sesión.
  • La primera barra de cada sesión no tiene z (var = 0) y la primera evaluable no puede entrar (no hay z[t−1]).
  • Los dos parámetros son de política: 12 candidatos, una sola curva de VWAP.
  • Solo NVDA y KO intradía; en la matriz, PASS 4/12 y UNSUPPORTED 8/12 por PolicyParamError.

Para practicar

  1. Con los acumulados de la barra 4 del fixture (cum_v = 6000, cum_vp = 601016,666667, cum_vp2 = 60203811,111111), añade la barra 5 (hlc3 = 100,45, V = 1000) y comprueba vwap[5] = 100,2095 y z[5] = 1,5844.
  2. Rehaz la columna "equity con 5 bps" con 1 bp (fee_rate = 0,0001). ¿Cuánto vale cada comisión tras el redondeo half-even? ¿Sigue siendo negativa la equity final?
  3. Lanza el sweep 4×3 sobre NVDA 5min y comprueba en factorization_plan cuántas features distintas calcula. Compara la duración media de la posición de z_entry = 1.0 y z_entry = 2.5.