//@version=6
strategy("Estrategia JM - Backtest Sesiones (A+)", shorttitle = "JM Backtest Sesiones", overlay = true, initial_capital = 50000, currency = currency.USD, default_qty_type = strategy.fixed, default_qty_value = 1, pyramiding = 0, process_orders_on_close = true, commission_type = strategy.commission.cash_per_contract, commission_value = 0.5, slippage = 1, margin_long = 0, margin_short = 0)
// =====================================================================
// BACKTEST - "Estrategia de Trading JM" (M5)
// Usa la MISMA lógica de detección del indicador de alertas (versión 6) y la
// convierte en operaciones para medir resultados en el Strategy Tester.
// Úsalo en un gráfico de 5 minutos, de UN solo activo (ej. MNQ1!).
//
// Reglas aplicadas:
// - Entrada: al cierre de la vela que forma el imbalance de M5 (señal A+ o A)
// - Stop A+ : bajo/sobre el VWAP o la toma de liquidez, el que quede más cerca
// - Stop A : en la toma de liquidez
// - Objetivo: 1:2 (configurable)
// - Riesgo fijo en dólares por operación; contratos = riesgo / (stop x valor del punto)
// - Máximo 2 operaciones por día, una posición a la vez
// - Solo escenario A+ (puedes activar también A en la sección 2)
// - Horario: solo tus sesiones, en hora de Nueva York (igual que el indicador):
// Asia 20-22 (domingo a jueves) · Londres 02-04 y NY 09-12 (lunes a viernes)
// - Bias: MANUAL por día (lista), o sin bias / solo compras / solo ventas.
// La sesión de Asia (domingo a jueves por la tarde) usa el bias del DÍA SIGUIENTE:
// Asia del domingo = lunes. Londres y NY usan el bias de su propio día.
// - Una posición a la vez; se cierra X minutos después de terminar la sesión en que se abrió
// - Al final del gráfico aparece una tabla con el resultado por sesión.
// - DIAGNÓSTICO (abajo a la derecha): separa las operaciones en las que, después de entrar,
// el imbalance M5 del gatillo SE INVIRTIÓ (cierre con cuerpo al otro lado de la zona) de las
// que no, con ganadas/perdidas y las velas M5 que pasaron hasta la inversión.
// OJO: es para estudiar. No se puede filtrar por algo que solo se sabe después de entrar.
// - STOP EN ATR: tercera opción de stop, a una distancia fija de N veces el ATR del precio de entrada.
// - STOP DEL A+: elige "El más cercano (VWAP o liquidez)" o "Siempre la toma de liquidez".
// - DISTANCIA MÁXIMA DEL STOP (en ATR): descarta señales cuyo stop queda demasiado lejos del precio.
// - ESTADÍSTICA DE STOP Y TP (abajo a la izquierda): por separado para ganadoras y perdedoras,
// cuánto retrocedió el precio en contra (MAE) y cuánto avanzó a favor (MFE) antes de cerrar,
// en múltiplos del riesgo (R) y en puntos, más el tamaño del stop en puntos y en ATR.
// - PREMIUM / DESCUENTO: compras solo en la mitad baja y ventas solo en la mitad alta de la estructura
// de M15 y/o H1 (último swing alto a último swing bajo de ese marco). Apagado por defecto.
// - CONTEXTO DE LA ESTRUCTURA (arriba a la izquierda): agrupa las operaciones por
// (1) ubicación en el rango del día (0 = lado favorable: abajo para compras, arriba para ventas),
// (2) pendiente del VWAP (plano / moderado / inclinado) y (3) distancia al VWAP en ATR,
// con operaciones, ganadas, % de acierto y neto de cada grupo.
// - CONFIRMACIÓN: "Esperar N velas M5" entra N velas después del A+ solo si el imbalance
// sigue sin invertirse y el precio no tocó el stop (0 = entrar al cierre del A+).
// - Para comparar "con bias" contra "sin bias": corre dos veces (Manual / Sin bias).
// =====================================================================
// ------------------------- ENTRADAS ----------------------------------
gLog = "1) Lógica del setup (igual que el indicador)"
gOp = "2) Operativa"
gBi = "3) Bias y horario"
zzATR = input.float(2.0, "Retroceso para confirmar un swing (en ATR)", minval = 0.5, step = 0.1, group = gLog)
sweepTol = input.float(1.0, "Tolerancia del barrido (en ATR)", minval = 0.0, step = 0.05, group = gLog)
minGap = input.float(0.1, "Tamaño mínimo del imbalance (en ATR)", minval = 0.0, step = 0.05, group = gLog)
maxBars = input.int(24, "Vigencia del barrido hasta la ruptura (velas M5)", minval = 3, group = gLog)
postBos = input.int(36, "Vigencia del imbalance tras la ruptura (velas M5)", minval = 1, group = gLog)
flipWin = input.int(12, "Cruce reciente del VWAP (velas)", minval = 1, group = gLog)
nearATR = input.float(1.0, "Precio 'cerca' del VWAP (en ATR)", minval = 0.1, step = 0.1, group = gLog)
riskUSD = input.float(250.0, "Riesgo por operación (USD)", minval = 1.0, group = gOp, tooltip = "Tus reglas: cuenta 25K challenge 250, fondeada 100; cuenta 50K challenge 500, fondeada 200.")
rr = input.float(2.0, "Objetivo (1:R)", minval = 0.5, step = 0.5, group = gOp)
maxTrades = input.int(2, "Máximo de operaciones por día", minval = 1, group = gOp)
onlyAplus = input.bool(true, "Operar solo escenario A+", group = gOp)
bufTicks = input.int(2, "Colchón del stop (ticks)", minval = 0, group = gOp, tooltip = "Distancia extra más allá del VWAP o de la toma de liquidez.")
minStopATR = input.float(0.5, "Stop mínimo (en ATR)", minval = 0.0, step = 0.1, group = gOp, tooltip = "Descarta operaciones con stop absurdamente corto (inflarían los contratos). Si el stop del A+ queda más corto, se usa la toma de liquidez.")
maxQty = input.int(50, "Máximo de contratos por operación", minval = 1, group = gOp)
stopMode = input.string("El más cercano (VWAP o liquidez)", "Stop del escenario A+", options = ["El más cercano (VWAP o liquidez)", "Siempre la toma de liquidez", "ATR fijo (múltiplo)"], group = gOp, tooltip = "El más cercano: debajo (largo) o encima (corto) del VWAP o de la toma de liquidez, el que quede más cerca. Siempre la toma de liquidez: bajo el mínimo (o sobre el máximo) que hizo el barrido, como en el escenario A.")
atrStopMult = input.float(2.0, "Stop en ATR: múltiplo del ATR (solo si el stop es ATR fijo)", minval = 0.5, step = 0.25, group = gOp, tooltip = "Con el stop en ATR fijo, el stop queda a esta cantidad de ATR del precio de entrada y el objetivo sigue siendo 1:R.")
maxStopATR = input.float(0.0, "Distancia máxima del stop (en ATR, 0 = sin límite)", minval = 0.0, step = 0.5, group = gOp, tooltip = "Descarta la señal si el stop queda a más de esta distancia del precio de entrada. Sirve para no entrar cuando el precio está muy lejos del mínimo (o máximo) de la estructura.")
pdMode = input.string("Apagado", "Filtro premium / descuento", options = ["Apagado", "M15", "H1", "M15 y H1"], group = gOp, tooltip = "Compras solo por debajo del nivel de la estructura del marco elegido (50% = mitad); ventas solo por encima del nivel espejo. La estructura es el rango entre el último swing alto y el último swing bajo confirmados en ese marco.")
pdLevel = input.float(0.5, "Nivel premium / descuento (0.5 = mitad de la estructura)", minval = 0.1, maxval = 0.9, step = 0.05, group = gOp)
pdZZ = input.float(2.0, "Retroceso para confirmar un swing de M15 / H1 (en ATR)", minval = 0.5, step = 0.5, group = gOp)
flatBars = input.int(24, "Contexto: velas M5 para medir la pendiente del VWAP (24 = 2 horas)", minval = 3, group = gOp)
confirmN = input.int(0, "Esperar N velas M5 de confirmación antes de entrar (0 = entrar al cierre del A+)", minval = 0, maxval = 6, group = gOp, tooltip = "Entra N velas M5 después del A+ solo si el imbalance M5 del gatillo no se ha invertido ni el precio ha tocado el stop. El stop se mantiene donde estaba; el objetivo 1:R y el tamaño se recalculan con el precio real de entrada.")
biasMode = input.string("Manual por día (lista)", "Bias", options = ["Manual por día (lista)", "Sin bias (ambas direcciones)", "Solo compras", "Solo ventas"], group = gBi, tooltip = "Manual: usa tu lista por día (A compras, B ventas, N no opera). Sin bias: opera ambas direcciones.")
biasList = input.text_area("", "Bias por día (para el modo Manual)", group = gBi, tooltip = "Una línea por día: fecha AAAA-MM-DD y una letra. A = alcista (solo compras), B = bajista (solo ventas), N = sin bias (no opera). Ejemplo: 2026-09-02 A. Los días que no estén en la lista cuentan como N.")
useAsia = input.bool(true, "Sesión de Asia (domingo a jueves)", group = gBi)
asiaS = input.int(20, "Asia: desde la hora (NY)", minval = 0, maxval = 23, group = gBi)
asiaE = input.int(22, "Asia: hasta la hora (NY)", minval = 1, maxval = 24, group = gBi)
useLon = input.bool(true, "Sesión de Londres (lunes a viernes)", group = gBi)
lonS = input.int(2, "Londres: desde la hora (NY)", minval = 0, maxval = 23, group = gBi)
lonE = input.int(4, "Londres: hasta la hora (NY)", minval = 1, maxval = 24, group = gBi)
useNY = input.bool(true, "Sesión de Nueva York (lunes a viernes)", group = gBi)
nyS = input.int(9, "Nueva York: desde la hora (NY)", minval = 0, maxval = 23, group = gBi)
nyE = input.int(12, "Nueva York: hasta la hora (NY)", minval = 1, maxval = 24, group = gBi)
graceMin = input.int(60, "Cerrar la posición X minutos después de terminar su sesión", minval = 0, group = gBi, tooltip = "Si la operación no llegó a TP ni a SL, se cierra a mercado pasado ese tiempo.")
// ------------------------- LÓGICA DE DETECCIÓN -----------------------
// Devuelve [código, mínimo del barrido (compras), máximo del barrido (ventas), VWAP, ATR]
// código: +1 compra A+, +2 compra A, -1 venta A+, -2 venta A, 0 nada.
core() =>
float atrV = ta.atr(14)
// VWAP (con respaldo si el activo no tiene volumen: promedio del día)
float vwB = ta.vwap(hlc3)
var float sP = 0.0
var int sN = 0
if timeframe.change("D")
sP := 0.0
sN := 0
sP += hlc3
sN += 1
float vw = na(vwB) ? sP / sN : vwB
// Cruces del precio sobre el VWAP
bool crossUp = ta.crossover(close, vw)
bool crossDn = ta.crossunder(close, vw)
int sinceUp = nz(ta.barssince(crossUp), 9999)
int sinceDn = nz(ta.barssince(crossDn), 9999)
float lo3 = ta.lowest(low, 3)
float hi3 = ta.highest(high, 3)
// Swings por retroceso (zigzag en ATR)
float dev = zzATR * atrV
var int zzDir = 0
var float legHi = na
var float legLo = na
var float swHi = na
var float swLo = na
if na(legHi)
legHi := high
legLo := low
if zzDir >= 0
legHi := math.max(legHi, high)
if not na(dev) and low <= legHi - dev
swHi := legHi
zzDir := -1
legLo := low
else
legLo := math.min(legLo, low)
if not na(dev) and high >= legLo + dev
swLo := legLo
zzDir := 1
legHi := high
float tol = sweepTol * atrV
// ---------------- COMPRAS ----------------
var float lSweepLow = na
var int lSweepBar = na
var float lBosLvl = na
var int lBosBar = na
bool dropL = (not na(lSweepBar) and na(lBosBar) and bar_index - lSweepBar > maxBars) or (not na(lBosBar) and (bar_index - lBosBar > postBos or close < lSweepLow))
if dropL
lSweepBar := na
lSweepLow := na
lBosLvl := na
lBosBar := na
float refLow = na(lSweepLow) ? swLo : math.min(swLo, lSweepLow)
bool touchL = zzDir == -1 and not na(swLo) and not na(swHi) and low <= refLow + tol and swHi > close and na(lBosBar)
if touchL
lSweepLow := na(lSweepLow) ? low : math.min(lSweepLow, low)
lSweepBar := bar_index
lBosLvl := swHi
if not na(lSweepBar) and na(lBosBar) and bar_index > lSweepBar and close > lBosLvl
lBosBar := bar_index
bool fvgBull = low > high[2] and (low - high[2]) >= minGap * atrV
bool trigL = not na(lBosBar) and fvgBull and close > lBosLvl
// ---------------- VENTAS ----------------
var float sSweepHigh = na
var int sSweepBar = na
var float sBosLvl = na
var int sBosBar = na
bool dropS = (not na(sSweepBar) and na(sBosBar) and bar_index - sSweepBar > maxBars) or (not na(sBosBar) and (bar_index - sBosBar > postBos or close > sSweepHigh))
if dropS
sSweepBar := na
sSweepHigh := na
sBosLvl := na
sBosBar := na
float refHigh = na(sSweepHigh) ? swHi : math.max(swHi, sSweepHigh)
bool touchS = zzDir == 1 and not na(swHi) and not na(swLo) and high >= refHigh - tol and swLo < close and na(sBosBar)
if touchS
sSweepHigh := na(sSweepHigh) ? high : math.max(sSweepHigh, high)
sSweepBar := bar_index
sBosLvl := swLo
if not na(sSweepBar) and na(sBosBar) and bar_index > sSweepBar and close < sBosLvl
sBosBar := bar_index
bool fvgBear = high < low[2] and (low[2] - high) >= minGap * atrV
bool trigS = not na(sBosBar) and fvgBear and close < sBosLvl
// ---------------- Clasificación A+ / A ----------------
float vwD = nearATR * atrV
int codeL = 0
int codeS = 0
if trigL and not na(vw)
if (lo3 - vw) <= vwD and close >= vw and sinceUp <= flipWin
codeL := 1
else if close < vw and (vw - close) > vwD
codeL := 2
if trigS and not na(vw)
if (vw - hi3) <= vwD and close <= vw and sinceDn <= flipWin
codeS := -1
else if close > vw and (close - vw) > vwD
codeS := -2
// Se guardan los extremos del barrido ANTES de reiniciar el setup
float stL = lSweepLow
float stS = sSweepHigh
if codeL != 0
lSweepBar := na
lSweepLow := na
lBosLvl := na
lBosBar := na
if codeS != 0
sSweepBar := na
sSweepHigh := na
sBosLvl := na
sBosBar := na
int code = codeL != 0 ? codeL : codeS
[code, stL, stS, vw, atrV]
[code, stL, stS, vw, atrV] = core()
// ------------------------- CONTEXTO DE LA ESTRUCTURA -------------------------
// Rango del día del activo (desde el cambio de día), pendiente y distancia al VWAP, en ATR.
var float dHi = na
var float dLo = na
if timeframe.change("D") or na(dHi)
dHi := high
dLo := low
else
dHi := math.max(dHi, high)
dLo := math.min(dLo, low)
float dRng = dHi - dLo
float posDay = dRng > 0 ? (close - dLo) / dRng : 0.5
float vwSlope = (not na(vw) and not na(vw[flatBars]) and atrV > 0) ? math.abs(vw - vw[flatBars]) / atrV : na
float vwDist = (not na(vw) and atrV > 0) ? math.abs(close - vw) / atrV : na
// ------------------------- BIAS, HORARIO Y LÍMITES -------------------
// Bias manual por día: se lee la lista una sola vez
var map<string, string> biasMap = map.new<string, string>()
if barstate.isfirst
string[] lines = str.split(biasList, "\n")
for i = 0 to array.size(lines) - 1
string t = str.trim(array.get(lines, i))
string[] p = str.split(t, " ")
if array.size(p) >= 2
map.put(biasMap, array.get(p, 0), str.upper(str.trim(array.get(p, array.size(p) - 1))))
// ---- Sesión de la vela (hora de apertura de la vela, en Nueva York) ----
string tzNY = "America/New_York"
int hhB = hour(time, tzNY)
int dwB = dayofweek(time, tzNY)
bool isAsia = useAsia and dwB >= dayofweek.sunday and dwB <= dayofweek.thursday and hhB >= asiaS and hhB < asiaE
bool isLon = useLon and dwB >= dayofweek.monday and dwB <= dayofweek.friday and hhB >= lonS and hhB < lonE
bool isNYs = useNY and dwB >= dayofweek.monday and dwB <= dayofweek.friday and hhB >= nyS and hhB < nyE
bool inSess = isAsia or isLon or isNYs
string sessName = isAsia ? "Asia" : (isLon ? "Londres" : (isNYs ? "NY" : ""))
int sessEndH = isAsia ? asiaE : (isLon ? lonE : (isNYs ? nyE : 0))
// Asia (tarde/noche) usa el bias del día siguiente; Londres y NY, el de su propio día
int biasT = isAsia ? time + 86400000 : time
string dayKey = str.format_time(biasT, "yyyy-MM-dd", tzNY)
string manualBias = map.contains(biasMap, dayKey) ? map.get(biasMap, dayKey) : "N"
bool okLong = biasMode == "Sin bias (ambas direcciones)" or biasMode == "Solo compras" or (biasMode == "Manual por día (lista)" and manualBias == "A")
bool okShort = biasMode == "Sin bias (ambas direcciones)" or biasMode == "Solo ventas" or (biasMode == "Manual por día (lista)" and manualBias == "B")
// El contador de operaciones se reinicia al empezar el día de bias (Asia + Londres + NY de ese día suman juntas)
var string lastKey = ""
var int tradesToday = 0
var int closeAt = na
if inSess and dayKey != lastKey
lastKey := dayKey
tradesToday := 0
// ------------------------- POSICIÓN DENTRO DE LA ESTRUCTURA (M15 / H1) ------
// Devuelve 0 = en el último swing bajo, 1 = en el último swing alto, 0.5 = mitad. Fuera de rango: <0 o >1.
// Se lee con la última vela cerrada del marco (sin mirar al futuro).
rangePos() =>
float atrR = ta.atr(14)
var int zDir = 0
var float lHi = na
var float lLo = na
var float sHi = na
var float sLo = na
if na(lHi)
lHi := high
lLo := low
float dv = pdZZ * atrR
if zDir >= 0
lHi := math.max(lHi, high)
if not na(dv) and low <= lHi - dv
sHi := lHi
zDir := -1
lLo := low
else
lLo := math.min(lLo, low)
if not na(dv) and high >= lLo + dv
sLo := lLo
zDir := 1
lHi := high
float pos = (not na(sHi) and not na(sLo) and sHi > sLo) ? (close - sLo) / (sHi - sLo) : na
pos[1]
float posM15 = request.security(syminfo.tickerid, "15", rangePos(), lookahead = barmerge.lookahead_on)
float posH1 = request.security(syminfo.tickerid, "60", rangePos(), lookahead = barmerge.lookahead_on)
bool pdLm15 = na(posM15) ? true : posM15 < pdLevel
bool pdLh1 = na(posH1) ? true : posH1 < pdLevel
bool pdSm15 = na(posM15) ? true : posM15 > 1 - pdLevel
bool pdSh1 = na(posH1) ? true : posH1 > 1 - pdLevel
bool pdL = pdMode == "Apagado" or (pdMode == "M15" and pdLm15) or (pdMode == "H1" and pdLh1) or (pdMode == "M15 y H1" and pdLm15 and pdLh1)
bool pdS = pdMode == "Apagado" or (pdMode == "M15" and pdSm15) or (pdMode == "H1" and pdSh1) or (pdMode == "M15 y H1" and pdSm15 and pdSh1)
// ------------------------- OPERACIONES -------------------------------
bool canTrade = strategy.position_size == 0 and tradesToday < maxTrades and inSess
bool gradeOk = code != 0 and (math.abs(code) == 1 or not onlyAplus)
// Señal en espera de confirmación y operación en curso (para el diagnóstico)
var int pDir = 0
var int pCnt = 0
var float pStop = na
var float pTop = na
var float pBot = na
var string pGrade = ""
var string pSess = ""
var float pU = -1.0
var float pSl = -1.0
var float pDs = -1.0
var float[] invBars = array.new_float()
var float[] sdArr = array.new_float()
var float[] uArr = array.new_float()
var float[] slArr = array.new_float()
var float[] dsArr = array.new_float()
var float[] atArr = array.new_float()
var int curDir = 0
var float curTop = na
var float curBot = na
var int curEntryBar = na
bool stopNear = stopMode == "El más cercano (VWAP o liquidez)"
bool stopAtr = stopMode == "ATR fijo (múltiplo)"
float minStop = minStopATR * atrV
float buf = bufTicks * syminfo.mintick
// 1) Señal en espera: cada vela nueva se revisa si el imbalance se invirtió, el precio tocó el stop o ya toca entrar
if pDir != 0
pCnt += 1
bool invP = (pDir == 1 and close < pBot and close < open) or (pDir == -1 and close > pTop and close > open)
bool stopHitP = (pDir == 1 and close <= pStop) or (pDir == -1 and close >= pStop)
if invP or stopHitP or not inSess
pDir := 0
else if pCnt >= confirmN
if strategy.position_size == 0 and tradesToday < maxTrades
float distP = pDir == 1 ? close - pStop : pStop - close
if distP >= minStop and (maxStopATR == 0 or distP <= maxStopATR * atrV)
float qP = math.min(math.floor(riskUSD / (distP * syminfo.pointvalue)), maxQty)
if qP >= 1
if pDir == 1
strategy.entry("Compra", strategy.long, qty = qP, comment = pSess + " " + pGrade)
strategy.exit("Salida C", from_entry = "Compra", stop = pStop, limit = close + rr * distP)
else
strategy.entry("Venta", strategy.short, qty = qP, comment = pSess + " " + pGrade)
strategy.exit("Salida V", from_entry = "Venta", stop = pStop, limit = close - rr * distP)
tradesToday += 1
closeAt := timestamp(tzNY, year(time, tzNY), month(time, tzNY), dayofmonth(time, tzNY), 0, 0) + sessEndH * 3600000 + graceMin * 60000
array.push(invBars, -1.0)
array.push(sdArr, distP)
array.push(atArr, atrV)
array.push(uArr, pU)
array.push(slArr, pSl)
array.push(dsArr, pDs)
curDir := pDir
curTop := pTop
curBot := pBot
curEntryBar := bar_index
pDir := 0
// 2) Señal nueva
if canTrade and gradeOk and not na(vw) and not na(atrV)
bool sigL = false
bool sigS = false
float sStop = na
if code > 0 and okLong and pdL
float sa = math.max(vw, stL) - buf
float sb = stL - buf
sStop := (code == 1 and stopNear) ? ((close - sa) >= minStop ? sa : sb) : sb
if stopAtr
sStop := close - atrStopMult * atrV
sigL := (close - sStop) >= minStop and (maxStopATR == 0 or (close - sStop) <= maxStopATR * atrV)
if code < 0 and okShort and pdS
float sc = math.min(vw, stS) + buf
float sd = stS + buf
sStop := (code == -1 and stopNear) ? ((sc - close) >= minStop ? sc : sd) : sd
if stopAtr
sStop := close + atrStopMult * atrV
sigS := (sStop - close) >= minStop and (maxStopATR == 0 or (sStop - close) <= maxStopATR * atrV)
if sigL or sigS
float zT = sigL ? low : low[2]
float zB = sigL ? high[2] : high
string gr = math.abs(code) == 1 ? "A+" : "A"
// Contexto en la vela del setup: u = 0 es el lado favorable del rango del día (-1 = sin dato o rango pequeño)
float uCtx = (dRng >= 3 * atrV) ? (sigL ? posDay : 1 - posDay) : -1.0
float slCtx = na(vwSlope) ? -1.0 : vwSlope
float dsCtx = na(vwDist) ? -1.0 : vwDist
if confirmN == 0
float dist0 = sigL ? close - sStop : sStop - close
float q0 = math.min(math.floor(riskUSD / (dist0 * syminfo.pointvalue)), maxQty)
if q0 >= 1
if sigL
strategy.entry("Compra", strategy.long, qty = q0, comment = sessName + " " + gr)
strategy.exit("Salida C", from_entry = "Compra", stop = sStop, limit = close + rr * dist0)
else
strategy.entry("Venta", strategy.short, qty = q0, comment = sessName + " " + gr)
strategy.exit("Salida V", from_entry = "Venta", stop = sStop, limit = close - rr * dist0)
tradesToday += 1
closeAt := timestamp(tzNY, year(time, tzNY), month(time, tzNY), dayofmonth(time, tzNY), 0, 0) + sessEndH * 3600000 + graceMin * 60000
array.push(invBars, -1.0)
array.push(sdArr, dist0)
array.push(atArr, atrV)
array.push(uArr, uCtx)
array.push(slArr, slCtx)
array.push(dsArr, dsCtx)
curDir := sigL ? 1 : -1
curTop := zT
curBot := zB
curEntryBar := bar_index
else
pDir := sigL ? 1 : -1
pCnt := 0
pStop := sStop
pTop := zT
pBot := zB
pGrade := gr
pSess := sessName
pU := uCtx
pSl := slCtx
pDs := dsCtx
// 3) Diagnóstico: con la operación abierta, ¿el imbalance del gatillo se invirtió? (cierre con cuerpo al otro lado)
if strategy.position_size != 0 and curDir != 0 and bar_index > curEntryBar and array.size(invBars) > 0
int lastI = array.size(invBars) - 1
if array.get(invBars, lastI) < 0
bool invC = (curDir == 1 and close < curBot and close < open) or (curDir == -1 and close > curTop and close > open)
if invC
array.set(invBars, lastI, bar_index - curEntryBar)
if strategy.position_size != 0 and not na(closeAt) and time >= closeAt
strategy.close_all(comment = "Fin de sesión")
plot(vw, "VWAP", color = color.new(color.blue, 40), linewidth = 1)
// ------------------------- RESULTADO POR SESIÓN -------------------------
// Se calcula sobre las operaciones cerradas. El neto puede diferir un poco del Strategy Tester (comisiones).
var table stT = table.new(position.top_right, 6, 6, bgcolor = color.new(color.black, 20), border_width = 1, border_color = color.gray)
if barstate.islast
float[] nTr = array.new_float(3, 0.0)
float[] nWin = array.new_float(3, 0.0)
float[] gP = array.new_float(3, 0.0)
float[] gL = array.new_float(3, 0.0)
int nClosed = strategy.closedtrades
if nClosed > 0
for i = 0 to nClosed - 1
string cm = strategy.closedtrades.entry_comment(i)
int k = str.contains(cm, "Asia") ? 0 : (str.contains(cm, "Londres") ? 1 : 2)
float pf = strategy.closedtrades.profit(i)
array.set(nTr, k, array.get(nTr, k) + 1)
if pf > 0
array.set(nWin, k, array.get(nWin, k) + 1)
array.set(gP, k, array.get(gP, k) + pf)
else
array.set(gL, k, array.get(gL, k) - pf)
string[] hd = array.from("Sesión", "Ops", "Ganadas", "% acierto", "Neto USD", "PF")
for c = 0 to 5
table.cell(stT, c, 0, array.get(hd, c), text_color = color.white, text_size = size.small)
string[] nm = array.from("Asia", "Londres", "NY")
float tN = 0.0
float tW = 0.0
float tP = 0.0
float tL = 0.0
for k = 0 to 2
float n = array.get(nTr, k)
float w = array.get(nWin, k)
float p = array.get(gP, k)
float l = array.get(gL, k)
tN += n
tW += w
tP += p
tL += l
table.cell(stT, 0, k + 1, array.get(nm, k), text_color = color.white, text_size = size.small)
table.cell(stT, 1, k + 1, str.tostring(n, "#"), text_color = color.white, text_size = size.small)
table.cell(stT, 2, k + 1, str.tostring(w, "#"), text_color = color.white, text_size = size.small)
table.cell(stT, 3, k + 1, n > 0 ? str.tostring(w / n * 100, "#.#") + "%" : "-", text_color = color.white, text_size = size.small)
table.cell(stT, 4, k + 1, str.tostring(p - l, "#,###.##"), text_color = (p - l) >= 0 ? color.lime : color.red, text_size = size.small)
table.cell(stT, 5, k + 1, l > 0 ? str.tostring(p / l, "#.##") : "-", text_color = color.white, text_size = size.small)
table.cell(stT, 0, 4, "TOTAL", text_color = color.yellow, text_size = size.small)
table.cell(stT, 1, 4, str.tostring(tN, "#"), text_color = color.yellow, text_size = size.small)
table.cell(stT, 2, 4, str.tostring(tW, "#"), text_color = color.yellow, text_size = size.small)
table.cell(stT, 3, 4, tN > 0 ? str.tostring(tW / tN * 100, "#.#") + "%" : "-", text_color = color.yellow, text_size = size.small)
table.cell(stT, 4, 4, str.tostring(tP - tL, "#,###.##"), text_color = (tP - tL) >= 0 ? color.lime : color.red, text_size = size.small)
table.cell(stT, 5, 4, tL > 0 ? str.tostring(tP / tL, "#.##") : "-", text_color = color.yellow, text_size = size.small)
table.cell(stT, 0, 5, "Bias: " + biasMode + (onlyAplus ? " · solo A+" : " · A+ y A"), text_color = color.silver, text_size = size.small)
// ------------------------- DIAGNÓSTICO: ¿SE INVIRTIÓ EL IMBALANCE M5? -------------------------
var table dgT = table.new(position.bottom_right, 5, 3, bgcolor = color.new(color.black, 20), border_width = 1, border_color = color.gray)
if barstate.islast
float[] dn = array.new_float(2, 0.0)
float[] dw = array.new_float(2, 0.0)
float[] dl = array.new_float(2, 0.0)
float[] db = array.new_float(2, 0.0)
int nC = strategy.closedtrades
if nC > 0 and array.size(invBars) > 0
for i = 0 to nC - 1
if i < array.size(invBars)
float v = array.get(invBars, i)
int g = v >= 0 ? 0 : 1
array.set(dn, g, array.get(dn, g) + 1)
if strategy.closedtrades.profit(i) > 0
array.set(dw, g, array.get(dw, g) + 1)
else
array.set(dl, g, array.get(dl, g) + 1)
if v >= 0
array.set(db, 0, array.get(db, 0) + v)
string[] h2 = array.from("Imbalance M5", "Ops", "Ganadas", "Perdidas", "Velas M5 hasta invertir")
for c2 = 0 to 4
table.cell(dgT, c2, 0, array.get(h2, c2), text_color = color.white, text_size = size.small)
table.cell(dgT, 0, 1, "Se invirtió", text_color = color.orange, text_size = size.small)
table.cell(dgT, 1, 1, str.tostring(array.get(dn, 0), "#"), text_color = color.white, text_size = size.small)
table.cell(dgT, 2, 1, str.tostring(array.get(dw, 0), "#"), text_color = color.lime, text_size = size.small)
table.cell(dgT, 3, 1, str.tostring(array.get(dl, 0), "#"), text_color = color.red, text_size = size.small)
table.cell(dgT, 4, 1, array.get(dn, 0) > 0 ? str.tostring(array.get(db, 0) / array.get(dn, 0), "#.#") : "-", text_color = color.white, text_size = size.small)
table.cell(dgT, 0, 2, "No se invirtió", text_color = color.aqua, text_size = size.small)
table.cell(dgT, 1, 2, str.tostring(array.get(dn, 1), "#"), text_color = color.white, text_size = size.small)
table.cell(dgT, 2, 2, str.tostring(array.get(dw, 1), "#"), text_color = color.lime, text_size = size.small)
table.cell(dgT, 3, 2, str.tostring(array.get(dl, 1), "#"), text_color = color.red, text_size = size.small)
table.cell(dgT, 4, 2, "-", text_color = color.white, text_size = size.small)
// ------------------------- ESTADÍSTICA DE STOP Y TP -------------------------
// MAE = máximo retroceso en contra durante la operación; MFE = máximo avance a favor.
// R = 1 es la distancia del stop. Los valores vienen del Strategy Tester (intravela), son aproximados.
// "Puntos" = unidades de precio del activo (en MNQ, 1 punto = 4 ticks).
var table exT = table.new(position.bottom_left, 3, 11, bgcolor = color.new(color.black, 20), border_width = 1, border_color = color.gray)
if barstate.islast
float[] wMaeR = array.new_float()
float[] wMaeP = array.new_float()
float[] wMfeR = array.new_float()
float[] wMfeP = array.new_float()
float[] wStP = array.new_float()
float[] wStA = array.new_float()
float[] lMaeR = array.new_float()
float[] lMaeP = array.new_float()
float[] lMfeR = array.new_float()
float[] lMfeP = array.new_float()
float[] lStP = array.new_float()
float[] lStA = array.new_float()
int nT = strategy.closedtrades
if nT > 0
for i = 0 to nT - 1
if i < array.size(sdArr)
float sd = array.get(sdArr, i)
float at = array.get(atArr, i)
float szPv = math.abs(strategy.closedtrades.size(i)) * syminfo.pointvalue
if sd > 0 and szPv > 0 and at > 0
float maeP = math.abs(strategy.closedtrades.max_drawdown(i)) / szPv
float mfeP = math.abs(strategy.closedtrades.max_runup(i)) / szPv
if strategy.closedtrades.profit(i) > 0
array.push(wMaeR, maeP / sd)
array.push(wMaeP, maeP)
array.push(wMfeR, mfeP / sd)
array.push(wMfeP, mfeP)
array.push(wStP, sd)
array.push(wStA, sd / at)
else
array.push(lMaeR, maeP / sd)
array.push(lMaeP, maeP)
array.push(lMfeR, mfeP / sd)
array.push(lMfeP, mfeP)
array.push(lStP, sd)
array.push(lStA, sd / at)
int nW = array.size(wStP)
int nL = array.size(lStP)
int w1 = 0
int l1 = 0
if nW > 0
for j = 0 to nW - 1
if array.get(wMfeR, j) >= 1.0
w1 += 1
if nL > 0
for j = 0 to nL - 1
if array.get(lMfeR, j) >= 1.0
l1 += 1
string[] lab = array.from("Medida", "Retroceso en contra (MAE), mediana en R", "MAE: P75 / P90 en R", "MAE, mediana en puntos", "Avance a favor (MFE), mediana en R", "MFE: % que llegó a 1R o más", "MFE, mediana en puntos", "Stop mediano en puntos", "Stop mediano en ATR", "Operaciones")
table.cell(exT, 0, 0, "Medida", text_color = color.white, text_size = size.small)
table.cell(exT, 1, 0, "Ganadoras", text_color = color.lime, text_size = size.small)
table.cell(exT, 2, 0, "Perdedoras", text_color = color.red, text_size = size.small)
for r = 1 to 9
table.cell(exT, 0, r, array.get(lab, r), text_color = color.white, text_size = size.small)
table.cell(exT, 0, 10, "Con pocas operaciones es solo una pista", text_color = color.silver, text_size = size.small)
table.cell(exT, 1, 1, nW > 0 ? str.tostring(array.median(wMaeR), "#.##") : "-", text_color = color.white, text_size = size.small)
table.cell(exT, 2, 1, nL > 0 ? str.tostring(array.median(lMaeR), "#.##") : "-", text_color = color.white, text_size = size.small)
table.cell(exT, 1, 2, nW > 0 ? str.tostring(array.percentile_nearest_rank(wMaeR, 75), "#.##") + " / " + str.tostring(array.percentile_nearest_rank(wMaeR, 90), "#.##") : "-", text_color = color.white, text_size = size.small)
table.cell(exT, 2, 2, nL > 0 ? str.tostring(array.percentile_nearest_rank(lMaeR, 75), "#.##") + " / " + str.tostring(array.percentile_nearest_rank(lMaeR, 90), "#.##") : "-", text_color = color.white, text_size = size.small)
table.cell(exT, 1, 3, nW > 0 ? str.tostring(array.median(wMaeP), "#.#") : "-", text_color = color.white, text_size = size.small)
table.cell(exT, 2, 3, nL > 0 ? str.tostring(array.median(lMaeP), "#.#") : "-", text_color = color.white, text_size = size.small)
table.cell(exT, 1, 4, nW > 0 ? str.tostring(array.median(wMfeR), "#.##") : "-", text_color = color.white, text_size = size.small)
table.cell(exT, 2, 4, nL > 0 ? str.tostring(array.median(lMfeR), "#.##") : "-", text_color = color.white, text_size = size.small)
table.cell(exT, 1, 5, nW > 0 ? str.tostring(w1 / nW * 100, "#") + "%" : "-", text_color = color.white, text_size = size.small)
table.cell(exT, 2, 5, nL > 0 ? str.tostring(l1 / nL * 100, "#") + "%" : "-", text_color = color.white, text_size = size.small)
table.cell(exT, 1, 6, nW > 0 ? str.tostring(array.median(wMfeP), "#.#") : "-", text_color = color.white, text_size = size.small)
table.cell(exT, 2, 6, nL > 0 ? str.tostring(array.median(lMfeP), "#.#") : "-", text_color = color.white, text_size = size.small)
table.cell(exT, 1, 7, nW > 0 ? str.tostring(array.median(wStP), "#.#") : "-", text_color = color.white, text_size = size.small)
table.cell(exT, 2, 7, nL > 0 ? str.tostring(array.median(lStP), "#.#") : "-", text_color = color.white, text_size = size.small)
table.cell(exT, 1, 8, nW > 0 ? str.tostring(array.median(wStA), "#.##") : "-", text_color = color.white, text_size = size.small)
table.cell(exT, 2, 8, nL > 0 ? str.tostring(array.median(lStA), "#.##") : "-", text_color = color.white, text_size = size.small)
table.cell(exT, 1, 9, str.tostring(nW), text_color = color.white, text_size = size.small)
table.cell(exT, 2, 9, str.tostring(nL), text_color = color.white, text_size = size.small)
// ------------------------- CONTEXTO: RESULTADOS POR GRUPO -------------------------
// Ubicación: favorable = compras en el tercio bajo del rango del día (ventas en el tercio alto).
var table cxT = table.new(position.top_left, 5, 12, bgcolor = color.new(color.black, 20), border_width = 1, border_color = color.gray)
if barstate.islast
float[] cn = array.new_float(10, 0.0)
float[] cw = array.new_float(10, 0.0)
float[] cp = array.new_float(10, 0.0)
int nC2 = strategy.closedtrades
if nC2 > 0
for i = 0 to nC2 - 1
if i < array.size(uArr)
float pr = strategy.closedtrades.profit(i)
float uu = array.get(uArr, i)
float ss = array.get(slArr, i)
float d2 = array.get(dsArr, i)
int b1 = uu < 0 ? 3 : (uu < 0.33 ? 0 : (uu < 0.67 ? 1 : 2))
int b2 = ss < 0 ? -1 : (ss < 0.75 ? 4 : (ss < 1.5 ? 5 : 6))
int b3 = d2 < 0 ? -1 : (d2 < 1.0 ? 7 : (d2 < 2.0 ? 8 : 9))
for b in array.from(b1, b2, b3)
if b >= 0
array.set(cn, b, array.get(cn, b) + 1)
if pr > 0
array.set(cw, b, array.get(cw, b) + 1)
array.set(cp, b, array.get(cp, b) + pr)
string[] cl = array.from("Ubicación favorable (<33%)", "Ubicación media", "Ubicación desfavorable (>67%)", "Sin rango claro del día", "VWAP plano (<0.75 ATR)", "VWAP moderado", "VWAP inclinado (>1.5 ATR)", "Cerca del VWAP (<1 ATR)", "A 1 a 2 ATR del VWAP", "Lejos del VWAP (>2 ATR)")
string[] ch = array.from("Grupo", "Ops", "Ganadas", "% acierto", "Neto USD")
for c3 = 0 to 4
table.cell(cxT, c3, 0, array.get(ch, c3), text_color = color.white, text_size = size.small)
for r3 = 0 to 9
float n3 = array.get(cn, r3)
float w3 = array.get(cw, r3)
float p3 = array.get(cp, r3)
table.cell(cxT, 0, r3 + 1, array.get(cl, r3), text_color = color.white, text_size = size.small)
table.cell(cxT, 1, r3 + 1, str.tostring(n3, "#"), text_color = color.white, text_size = size.small)
table.cell(cxT, 2, r3 + 1, str.tostring(w3, "#"), text_color = color.white, text_size = size.small)
table.cell(cxT, 3, r3 + 1, n3 > 0 ? str.tostring(w3 / n3 * 100, "#") + "%" : "-", text_color = color.white, text_size = size.small)
table.cell(cxT, 4, r3 + 1, str.tostring(p3, "#,###"), text_color = p3 >= 0 ? color.lime : color.red, text_size = size.small)
table.cell(cxT, 0, 11, "Cada operación cuenta en 3 grupos (uno por bloque)", text_color = color.silver, text_size = size.small)