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 1min … 1h. 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 = 0es 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
groupbyel 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: unshiftglobal forma "cruces" entre dos distribuciones distintas.- Tolerancias heredadas:
varpierde dígitos por cancelación; la paridad devaryzse compara conrtol = 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) sivar == 0ocum_volume == 0.after_min[t] = minutes_since_session_open[t] >= min_minutes(la barra que cierra justo a los M minutos sí es evaluable).- Cruce según la convención del proyecto (§5.1): estricto en
t, no estricto ent−1, cont−1en 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 >= 0en largo), no por cruce;z == 0exacto 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:
- Barras 0 y 8:
var = 0exacto (una sola barra acumulada:Σ(v·p²)/Σv = p² = vwap²). No hay z. Además, antes de M. - Barras 1 y 9: ya son evaluables (
60 >= 60), peroz[t−1]es NaN: la primera barra evaluable de una sesión nunca entra. - Barra 3 → 4:
zpasa de −2,1362 (extremo) a 0,6323: retorno ascendente por −2 → largo. No se entra en la 3 (llegar al extremo no basta). - Barra 5:
z = 1,5844 >= 0→ salida por nivel. Comoz[4]ya era positivo, una regla de "cruce de cero" no habría cerrado nunca. La posición dura una barra. - 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.
- Barra 11: el corto sigue con
z = 2,0596 > z_entry: no hay stop. - 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), concum_volume == 0y convolume == 0(alto en KO 1min). - Barras con
|z|a menos de 1e-6 de ±z_entryo 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.
VolumeWeightedAveragePricede 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_vwapcon 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. zcomo 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_entrytrasmin_minutes; sale por nivel enz = 0o 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 hayz[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¶
- 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 compruebavwap[5] = 100,2095yz[5] = 1,5844. - 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? - Lanza el sweep 4×3 sobre NVDA 5min y comprueba en
factorization_plancuántas features distintas calcula. Compara la duración media de la posición dez_entry = 1.0yz_entry = 2.5.