TQE_Trade_Quality_Engine.pine
Copy
//@version=5
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © AssetWhisper — https://assetwhisper.com/indicators/
// ============================================================================
// TQE — Trade Quality Engine
// Un solo indicador que cierra el ciclo completo de una operación:
// entrada → stop consciente de la cola → target de máxima esperanza →
// probabilidad de éxito → contexto (régimen, saltos, compresión, liquidez) →
// puntuación 0-100 → tamaño de posición calibrado con su propio historial.
//
// Combina la pieza mejor fundamentada de cada indicador del TOP10 y añade una
// idea propia: el DRIFT del modelo de barreras es CONDICIONAL AL RÉGIMEN.
// · Hurst R/S multiescala (AHM): en tendencia el drift es el medido, en
// reversión es la atracción hacia la media, en paseo aleatorio es cero.
// · Skew/curtosis + VaR de Cornish-Fisher (STRO): el stop nunca queda dentro
// del peor movimiento 1-de-100 barras esperado en contra.
// · Probabilidad exacta de tocar target antes que stop (SOPB): esperanza en R
// y ratio R óptimo para el drift, la volatilidad y el régimen actuales.
// · Cuota de saltos + test BNS (JRD): penaliza cuando la volatilidad viene de
// discontinuidades que el modelo difusivo no captura.
// · Compresión / ruptura limpia (CEP) y niveles de liquidez no barridos (LHS):
// detecta imanes a favor del target y stops colocados en zona de caza.
// · Kelly fraccional con cota de Wilson (KLDS): simula sus propias señales con
// el stop/target que propone y dimensiona según el track record real.
// Todas las salidas clave se exportan (display.none) para encadenar scripts.
// ============================================================================
indicator("TQE — Trade Quality Engine", shorttitle="TQE", overlay=true, max_lines_count=120, max_labels_count=120)
// === INPUTS ===
grpS = "Setup"
dirMode = input.string("Auto", "Direction", options=["Auto", "Long", "Short"], group=grpS)
stopATRk = input.float(1.5, "Base stop (ATR mult)", minval=0.3, maxval=10, step=0.1, group=grpS)
tailMult = input.float(1.5, "Tail protection (× VaR99 per bar)", minval=0.0, maxval=5.0, step=0.25, group=grpS)
autoR = input.bool(true, "Optimise target R for max expectancy", group=grpS)
fixedR = input.float(2.0, "Fixed target R (if not optimised)", minval=0.25, maxval=10, step=0.25, group=grpS)
signalTh = input.int(65, "Signal threshold (score 0-100)", minval=40, maxval=95, group=grpS)
grpM = "Modelo"
lookback = input.int(100, "Drift / vol lookback", minval=20, maxval=500, group=grpM)
hurstLen = input.int(128, "Hurst window N (uses N, N/2, N/4)", minval=32, maxval=512, group=grpM)
distLen = input.int(60, "Skew / kurtosis window", minval=20, maxval=250, group=grpM)
jumpLen = input.int(30, "Jump window", minval=10, maxval=200, group=grpM)
mrLen = input.int(20, "Mean-reversion anchor length", minval=10, maxval=100, group=grpM)
grpC = "Contexto"
pivotLen = input.int(10, "Pivot length", minval=3, maxval=50, group=grpC)
maxLevels = input.int(10, "Max liquidity levels per side", minval=3, maxval=30, group=grpC)
maxAge = input.int(400, "Level max age (bars)", minval=50, maxval=2000, group=grpC)
rankLen = input.int(100, "Compression percentile lookback", minval=30, maxval=500, group=grpC)
grpK = "Sizing (auto-calibrado)"
kFrac = input.float(0.5, "Kelly fraction", minval=0.1, maxval=1.0, step=0.05, group=grpK)
kCap = input.float(3.0, "Max risk per trade (%)", minval=0.25, maxval=20, step=0.25, group=grpK)
minN = input.int(15, "Min virtual trades for sizing", minval=5, maxval=100, group=grpK)
sampleN = input.int(40, "Results window (trades)", minval=10, maxval=200, group=grpK)
maxBars = input.int(30, "Virtual trade time exit (bars)", minval=3, maxval=300, group=grpK)
maxOpen = input.int(3, "Max simultaneous virtual trades", minval=1, maxval=10, group=grpK)
equity = input.float(10000, "Account equity", minval=100, group=grpK)
grpV = "Visual"
showTable = input.bool(true, "Show table", group=grpV)
showLevels = input.bool(true, "Show liquidity levels", group=grpV)
showRegime = input.bool(true, "Regime background", group=grpV)
// === BASE ===
lr = math.log(close / close[1])
atr = ta.atr(14)
trueRange = ta.tr(true)
muMeas = ta.sma(lr, lookback)
sigma = ta.stdev(lr, lookback)
clamp01(x) => math.max(0.0, math.min(1.0, x))
// ============================================================================
// CAPA 1 · RÉGIMEN: Hurst R/S en tres escalas con corrección Anis-Lloyd/Peters
// ============================================================================
rsStat(n) =>
m = 0.0
for i = 0 to n - 1
m += nz(lr[i])
m /= n
cum = 0.0
mx = 0.0
mn = 0.0
ss = 0.0
for i = n - 1 to 0
dd = nz(lr[i]) - m
cum += dd
mx := math.max(mx, cum)
mn := math.min(mn, cum)
ss += dd * dd
s = math.sqrt(ss / n)
s > 0 ? (mx - mn) / s : na
expectedRS(n) =>
acc = 0.0
for i = 1 to n - 1
acc += math.sqrt((n - i) / i)
(n - 0.5) / n * math.pow(n * math.pi / 2, -0.5) * acc
slope3(x1, y1, x2, y2, x3, y3) =>
xm = (x1 + x2 + x3) / 3
ym = (y1 + y2 + y3) / 3
num = (x1 - xm) * (y1 - ym) + (x2 - xm) * (y2 - ym) + (x3 - xm) * (y3 - ym)
den = (x1 - xm) * (x1 - xm) + (x2 - xm) * (x2 - xm) + (x3 - xm) * (x3 - xm)
den > 0 ? num / den : na
n1 = hurstLen
n2 = math.max(16, math.floor(hurstLen / 2))
n3 = math.max(8, math.floor(hurstLen / 4))
var float ers1 = expectedRS(n1)
var float ers2 = expectedRS(n2)
var float ers3 = expectedRS(n3)
lx1 = math.log(n1)
lx2 = math.log(n2)
lx3 = math.log(n3)
hRaw = slope3(lx1, math.log(rsStat(n1)), lx2, math.log(rsStat(n2)), lx3, math.log(rsStat(n3)))
hRw = slope3(lx1, math.log(ers1), lx2, math.log(ers2), lx3, math.log(ers3))
H = ta.ema(math.max(0.0, math.min(1.0, nz(0.5 + hRaw - hRw, 0.5))), 5)
var int regime = 0 // 1 tendencia, -1 reversión, 0 aleatorio (con histéresis)
if regime != 1 and H > 0.55
regime := 1
else if regime == 1 and H < 0.53
regime := 0
if regime != -1 and H < 0.45
regime := -1
else if regime == -1 and H > 0.47
regime := 0
wTrend = clamp01((H - 0.5) / 0.15)
wMR = clamp01((0.5 - H) / 0.15)
regimeLabel = regime == 1 ? "TREND" : regime == -1 ? "MEAN-REV" : "RANDOM"
regimeColor = regime == 1 ? color.green : regime == -1 ? color.red : color.gray
// ============================================================================
// CAPA 2 · DISTRIBUCIÓN: skew, curtosis y cuantiles de Cornish-Fisher
// ============================================================================
meanR = ta.sma(lr, distLen)
sdR = ta.stdev(lr, distLen)
s3 = 0.0
s4 = 0.0
if sdR > 0
for i = 0 to distLen - 1
zi = (nz(lr[i]) - meanR) / sdR
s3 += zi * zi * zi
s4 += zi * zi * zi * zi
S = ta.ema(s3 / distLen, 5)
K = ta.ema(s4 / distLen - 3, 5)
heavy = K > 2
cfQ(zq, sk, ku) =>
zq + (zq * zq - 1) * sk / 6 + (zq * zq * zq - 3 * zq) * ku / 24 - (2 * zq * zq * zq - 5 * zq) * sk * sk / 36
Sc = math.max(-3.0, math.min(3.0, S))
Kc = math.max(-2.0, math.min(10.0, K))
var99cf = -(meanR + sdR * cfQ(-2.326, Sc, Kc)) * 100 // peor caída % esperada 1 de cada 100 barras
up99cf = (meanR + sdR * cfQ(2.326, Sc, Kc)) * 100 // peor subida % (adversa para cortos)
// ============================================================================
// CAPA 3 · SALTOS: RV vs bipower variation + estadístico BNS
// ============================================================================
ar = math.abs(lr)
rv = ta.sum(lr * lr, jumpLen)
bv = (math.pi / 2) * ta.sum(ar * ar[1], jumpLen)
tq = jumpLen * math.pow(0.8309, -3) * ta.sum(math.pow(ar, 4.0 / 3) * math.pow(ar[1], 4.0 / 3) * math.pow(ar[2], 4.0 / 3), jumpLen)
jumpShare = ta.ema(rv > 0 ? math.max(0.0, 1 - bv / rv) : 0.0, 3)
theta = math.pi * math.pi / 4 + math.pi - 5
bnsZ = rv > 0 and bv > 0 ? (1 - bv / rv) / math.sqrt(theta * math.max(1.0, tq / (bv * bv)) / jumpLen) : 0.0
bnsSig = bnsZ >= 1.96
// ============================================================================
// CAPA 4 · DRIFT CONDICIONAL AL RÉGIMEN
// ============================================================================
basis = ta.sma(close, mrLen)
sdP = ta.stdev(close, mrLen)
z = sdP > 0 ? (close - basis) / sdP : 0.0
muMr = close > 0 ? -(close - basis) / close / mrLen : 0.0 // fracción del gap a la media que se cierra por barra
muEff = wTrend * muMeas + wMR * muMr // H≈0.5 → drift ≈ 0
// ============================================================================
// CAPA 5 · ESTRUCTURA: compresión, ruptura y niveles de liquidez
// ============================================================================
atrRank = ta.percentrank(atr, rankLen)
[bbB, bbU, bbL] = ta.bb(close, 20, 2)
bbw = bbB > 0 ? (bbU - bbL) / bbB : 0.0
bbwRank = ta.percentrank(bbw, rankLen)
nr7 = trueRange <= ta.lowest(trueRange, 7)
comp = ta.ema((2 * (100 - atrRank) + 2 * (100 - bbwRank) + (nr7 ? 100 : 0)) / 5, 3)
compPrior = ta.highest(comp[1], 5)
dcH = ta.highest(high, 20)
dcL = ta.lowest(low, 20)
volMA = ta.sma(volume, 20)
volOk = volMA > 0 ? volume / volMA >= 1.2 : true
breakoutUp = close > dcH[1] and trueRange > atr and volOk
breakoutDn = close < dcL[1] and trueRange > atr and volOk
cleanUp = breakoutUp and compPrior >= 55
cleanDn = breakoutDn and compPrior >= 55
// Niveles de liquidez: pivotes aún no tocados por el precio
var array<float> hiLvl = array.new<float>()
var array<int> hiBar = array.new<int>()
var array<line> hiLine = array.new<line>()
var array<float> loLvl = array.new<float>()
var array<int> loBar = array.new<int>()
var array<line> loLine = array.new<line>()
deleteLine(ln) =>
if not na(ln)
line.delete(ln)
pushLevel(lvls, bars, lns, price, bIdx, col) =>
array.push(lvls, price)
array.push(bars, bIdx)
line ln = na
if showLevels
ln := line.new(bIdx, price, bar_index, price, color=col, style=line.style_dotted, width=1)
array.push(lns, ln)
if array.size(lvls) > maxLevels
array.shift(lvls)
array.shift(bars)
deleteLine(array.shift(lns))
removeLevel(lvls, bars, lns, i) =>
array.remove(lvls, i)
array.remove(bars, i)
deleteLine(array.remove(lns, i))
purgeOld(lvls, bars, lns) =>
n = array.size(lvls)
if n > 0
for i = n - 1 to 0
if bar_index - array.get(bars, i) > maxAge
removeLevel(lvls, bars, lns, i)
extendLines(lns) =>
n = array.size(lns)
if n > 0
for i = 0 to n - 1
ln = array.get(lns, i)
if not na(ln)
line.set_x2(ln, bar_index)
ph = ta.pivothigh(high, pivotLen, pivotLen)
pl = ta.pivotlow(low, pivotLen, pivotLen)
if not na(ph)
pushLevel(hiLvl, hiBar, hiLine, ph, bar_index - pivotLen, color.new(color.red, 55))
if not na(pl)
pushLevel(loLvl, loBar, loLine, pl, bar_index - pivotLen, color.new(color.green, 55))
// Un nivel tocado deja de ser liquidez pendiente
nLo = array.size(loLvl)
if nLo > 0
for i = nLo - 1 to 0
if low < array.get(loLvl, i)
removeLevel(loLvl, loBar, loLine, i)
nHi = array.size(hiLvl)
if nHi > 0
for i = nHi - 1 to 0
if high > array.get(hiLvl, i)
removeLevel(hiLvl, hiBar, hiLine, i)
purgeOld(hiLvl, hiBar, hiLine)
purgeOld(loLvl, loBar, loLine)
extendLines(hiLine)
extendLines(loLine)
// ============================================================================
// CAPA 6 · MODELO DE BARRERAS (browniano con drift, dos barreras)
// ============================================================================
// a = distancia log al stop, b = distancia log al target, k = 2μ/σ²
// P(target antes que stop) = (1 - e^{k·a}) / (e^{-k·b} - e^{k·a}); a/(a+b) si μ = 0
hitProb(aa, bb, m, s) =>
result = 0.5
if aa > 0 and bb > 0
if s <= 0 or math.abs(m) < 1e-9
result := aa / (aa + bb)
else
k = 2 * m / (s * s)
ea = math.exp(math.max(-50.0, math.min(50.0, k * aa)))
eb = math.exp(math.max(-50.0, math.min(50.0, -k * bb)))
denom = eb - ea
result := math.abs(denom) < 1e-12 ? aa / (aa + bb) : (1 - ea) / denom
math.max(0.0, math.min(1.0, result))
// ============================================================================
// MOTOR · evalúa un setup en la dirección d (+1 largo, -1 corto)
// ============================================================================
evalSetup(d) =>
// Stop: el mayor entre k·ATR y la protección de cola (VaR99 CF adverso × tailMult)
adverse = d > 0 ? math.max(0.0, var99cf) : math.max(0.0, up99cf)
stopAbs = math.max(stopATRk * atr, tailMult * adverse / 100 * close)
stopPx = close - d * stopAbs
a = stopPx > 0 ? (d > 0 ? math.log(close / stopPx) : math.log(stopPx / close)) : na
muD = d > 0 ? muEff : -muEff
// Target: R óptimo (máxima esperanza) o fijo
bestR = fixedR
bestEV = -1e9
if autoR and not na(a)
for kk = 1 to 16
rT = kk * 0.5
tT = close + d * stopAbs * rT
if tT > 0
bT = d > 0 ? math.log(tT / close) : math.log(close / tT)
pT_ = hitProb(a, bT, muD, sigma)
ev_ = pT_ * rT - (1 - pT_)
if ev_ > bestEV
bestEV := ev_
bestR := rT
tgtPx = close + d * stopAbs * bestR
b = tgtPx > 0 and not na(a) ? (d > 0 ? math.log(tgtPx / close) : math.log(close / tgtPx)) : na
pT = na(a) or na(b) ? 0.5 : hitProb(a, b, muD, sigma)
evR = pT * bestR - (1 - pT)
breakEven = 1 / (1 + bestR)
expBars = na(a) or na(b) or sigma <= 0 ? na : math.abs(muD) < 1e-9 ? a * b / (sigma * sigma) : math.max(0.0, (b * pT - a * (1 - pT)) / muD)
// Liquidez: imán antes del target (+) / stop en zona de caza (-)
magnet = false
stopHunt = false
nH = array.size(hiLvl)
nL = array.size(loLvl)
if d > 0
if nH > 0
for i = 0 to nH - 1
lvl = array.get(hiLvl, i)
if lvl > close and lvl <= tgtPx
magnet := true
if nL > 0
for i = 0 to nL - 1
lvl = array.get(loLvl, i)
if lvl < close and lvl >= stopPx and lvl - stopPx < 0.5 * atr
stopHunt := true
else
if nL > 0
for i = 0 to nL - 1
lvl = array.get(loLvl, i)
if lvl < close and lvl >= tgtPx
magnet := true
if nH > 0
for i = 0 to nH - 1
lvl = array.get(hiLvl, i)
if lvl > close and lvl <= stopPx and stopPx - lvl < 0.5 * atr
stopHunt := true
// Puntuación 0-100
sEV = 40 * clamp01((evR + 0.5) / 1.5) // EV de -0.5R → 0, +1R → 40
sProb = 10 * clamp01((pT - breakEven) / 0.25) // 25 pts sobre el break-even → máximo
sReg = regime == 1 ? (muD > 0 ? 15.0 : 0.0) : regime == -1 ? ((d > 0 ? z < -1 : z > 1) ? 15.0 : (d > 0 ? z < 0 : z > 0) ? 8.0 : 0.0) : 5.0
sJump = math.max(0.0, 10 * (1 - jumpShare) - (bnsSig ? 5 : 0))
sDir = d > 0 ? S : -S
sTail = math.max(0.0, math.min(10.0, 5 + sDir * 5 - (heavy ? 3 : 0)))
sStr = (d > 0 ? cleanUp : cleanDn) ? 10.0 : (d > 0 ? breakoutUp : breakoutDn) ? 5.0 : comp >= 55 ? 3.0 : 0.0
sStr := math.max(0.0, math.min(15.0, sStr + (magnet ? 5 : 0) - (stopHunt ? 5 : 0)))
score = sEV + sProb + sReg + sJump + sTail + sStr
[score, pT, evR, bestR, stopPx, tgtPx, expBars, stopHunt, magnet, stopAbs]
[scL, pL, evL, rL, slL, tpL, ebL, shL, mgL, saL] = evalSetup(1)
[scS, pS, evS, rS, slS, tpS, ebS, shS, mgS, saS] = evalSetup(-1)
dirSel = dirMode == "Long" ? 1 : dirMode == "Short" ? -1 : scL >= scS ? 1 : -1
score = dirSel > 0 ? scL : scS
pT = dirSel > 0 ? pL : pS
evR = dirSel > 0 ? evL : evS
rSel = dirSel > 0 ? rL : rS
stopPx = dirSel > 0 ? slL : slS
tgtPx = dirSel > 0 ? tpL : tpS
expBars = dirSel > 0 ? ebL : ebS
stopHunt = dirSel > 0 ? shL : shS
magnet = dirSel > 0 ? mgL : mgS
stopAbs = dirSel > 0 ? saL : saS
sigLong = dirSel > 0 and scL >= signalTh and scL[1] < signalTh
sigShort = dirSel < 0 and scS >= signalTh and scS[1] < signalTh
// ============================================================================
// CAPA 7 · AUTO-CALIBRACIÓN: simula sus propias señales y dimensiona con Kelly
// ============================================================================
var array<int> oDir = array.new<int>()
var array<float> oEntry = array.new<float>()
var array<float> oStop = array.new<float>()
var array<float> oTgt = array.new<float>()
var array<float> oR = array.new<float>()
var array<int> oBar = array.new<int>()
var array<float> results = array.new<float>()
nO = array.size(oDir)
if nO > 0
for i = nO - 1 to 0
d = array.get(oDir, i)
e = array.get(oEntry, i)
s = array.get(oStop, i)
tg = array.get(oTgt, i)
rr = array.get(oR, i)
b0 = array.get(oBar, i)
if bar_index > b0
riskAbs = math.abs(e - s)
hitStop = d > 0 ? low <= s : high >= s
hitTgt = d > 0 ? high >= tg : low <= tg
float res = na
if hitStop
res := -1.0
else if hitTgt
res := rr
else if bar_index - b0 >= maxBars
res := riskAbs > 0 ? (close - e) * d / riskAbs : 0.0
if not na(res)
array.push(results, res)
if array.size(results) > sampleN
array.shift(results)
array.remove(oDir, i)
array.remove(oEntry, i)
array.remove(oStop, i)
array.remove(oTgt, i)
array.remove(oR, i)
array.remove(oBar, i)
openVirtual(d, sl, tp, rr) =>
if array.size(oDir) < maxOpen
array.push(oDir, d)
array.push(oEntry, close)
array.push(oStop, sl)
array.push(oTgt, tp)
array.push(oR, rr)
array.push(oBar, bar_index)
if sigLong
openVirtual(1, slL, tpL, rL)
if sigShort
openVirtual(-1, slS, tpS, rS)
nR = array.size(results)
wins = 0
sumW = 0.0
sumL = 0.0
sumR = 0.0
sumR2 = 0.0
if nR > 0
for i = 0 to nR - 1
v = array.get(results, i)
sumR += v
sumR2 += v * v
if v > 0
wins += 1
sumW += v
else
sumL += -v
losses = nR - wins
pWin = nR > 0 ? wins / nR : 0.0
avgW = wins > 0 ? sumW / wins : 0.0
avgL = losses > 0 ? sumL / losses : 0.0
payoff = avgL > 0 ? avgW / avgL : 0.0
expectancyR = nR > 0 ? sumR / nR : 0.0
zW = 1.96
pLB = nR > 0 ? (pWin + zW * zW / (2 * nR) - zW * math.sqrt(pWin * (1 - pWin) / nR + zW * zW / (4 * nR * nR))) / (1 + zW * zW / nR) : 0.0
kellyBin = payoff > 0 ? pLB - (1 - pLB) / payoff : 0.0
kellyCont = nR > 0 and sumR2 > 0 ? (sumR / nR) / (sumR2 / nR) : 0.0
kellyUse = math.min(kellyBin, kellyCont)
riskPct = nR >= minN ? math.max(0.0, math.min(kCap, kellyUse * kFrac * 100)) : 0.0
riskAmt = equity * riskPct / 100
units = stopAbs > 0 ? riskAmt / stopAbs : 0.0
// ============================================================================
// VISUAL
// ============================================================================
dirLabel = dirSel > 0 ? "LONG" : "SHORT"
scoreColor = score >= signalTh ? color.lime : score >= 50 ? color.orange : color.red
stopPct = stopAbs / close * 100
tgtPct = stopPct * rSel
var line slLine = na
var line tpLine = na
var label slLbl = na
var label tpLbl = na
var label enLbl = na
if barstate.islast
if na(slLine)
slLine := line.new(bar_index, stopPx, bar_index + 12, stopPx, color=color.red, width=2)
tpLine := line.new(bar_index, tgtPx, bar_index + 12, tgtPx, color=color.green, width=2)
slLbl := label.new(bar_index + 12, stopPx, "", style=label.style_label_left, color=color.new(color.red, 20), textcolor=color.white, size=size.small)
tpLbl := label.new(bar_index + 12, tgtPx, "", style=label.style_label_left, color=color.new(color.green, 20), textcolor=color.white, size=size.small)
enLbl := label.new(bar_index + 12, close, "", style=label.style_label_left, color=color.new(color.gray, 20), textcolor=color.white, size=size.small)
line.set_xy1(slLine, bar_index, stopPx)
line.set_xy2(slLine, bar_index + 12, stopPx)
line.set_xy1(tpLine, bar_index, tgtPx)
line.set_xy2(tpLine, bar_index + 12, tgtPx)
label.set_xy(slLbl, bar_index + 12, stopPx)
label.set_text(slLbl, "SL -" + str.tostring(stopPct, "#.##") + "% P " + str.tostring((1 - pT) * 100, "#") + "%" + (stopHunt ? " ⚠ hunt zone" : ""))
label.set_xy(tpLbl, bar_index + 12, tgtPx)
label.set_text(tpLbl, "TP +" + str.tostring(tgtPct, "#.##") + "% (" + str.tostring(rSel, "#.#") + "R) P " + str.tostring(pT * 100, "#") + "% EV " + str.tostring(evR, "#.##") + "R" + (magnet ? " ◎ magnet" : ""))
label.set_xy(enLbl, bar_index + 12, close)
label.set_text(enLbl, dirLabel + " score " + str.tostring(score, "#") + "/100 · " + regimeLabel)
plotshape(sigLong, "TQE long signal", style=shape.triangleup, location=location.belowbar, color=color.lime, size=size.small, text="TQE")
plotshape(sigShort, "TQE short signal", style=shape.triangledown, location=location.abovebar, color=color.red, size=size.small, text="TQE")
bgcolor(showRegime ? color.new(regimeColor, 95) : na, title="Regime")
// Exportables
plot(scL, "TQE long score (export)", display=display.none)
plot(scS, "TQE short score (export)", display=display.none)
plot(evR, "TQE expectancy R (export)", display=display.none)
plot(regime, "TQE regime (export)", display=display.none)
plot(riskPct, "TQE risk % (export)", display=display.none)
if showTable
var table t = table.new(position.top_right, 2, 19, bgcolor=color.new(color.black, 55))
gauge = ""
for i = 0 to 9
gauge += i < math.round(score / 10) ? "█" : "░"
table.cell(t, 0, 0, "TQE " + dirLabel, text_color=color.white, text_size=size.small)
table.cell(t, 1, 0, gauge + " " + str.tostring(score, "#"), bgcolor=color.new(scoreColor, 60), text_color=color.white, text_size=size.small)
table.cell(t, 0, 1, "Long / Short score", text_color=color.white, text_size=size.small)
table.cell(t, 1, 1, str.tostring(scL, "#") + " / " + str.tostring(scS, "#"), text_color=color.gray, text_size=size.small)
table.cell(t, 0, 2, "P(target) / break-even", text_color=color.white, text_size=size.small)
table.cell(t, 1, 2, str.tostring(pT * 100, "#") + "% / " + str.tostring(100 / (1 + rSel), "#") + "%", text_color=pT > 1 / (1 + rSel) ? color.lime : color.red, text_size=size.small)
table.cell(t, 0, 3, "Expectancy", text_color=color.white, text_size=size.small)
table.cell(t, 1, 3, str.tostring(evR, "#.##") + " R", text_color=evR > 0 ? color.lime : color.red, text_size=size.small)
table.cell(t, 0, 4, "Stop", text_color=color.white, text_size=size.small)
table.cell(t, 1, 4, str.tostring(stopPx, format.mintick) + " (" + str.tostring(stopPct, "#.##") + "%, " + str.tostring(stopAbs / atr, "#.#") + " ATR)", text_color=color.red, text_size=size.small)
table.cell(t, 0, 5, "Target", text_color=color.white, text_size=size.small)
table.cell(t, 1, 5, str.tostring(tgtPx, format.mintick) + " (" + str.tostring(rSel, "#.#") + "R" + (autoR ? " opt" : "") + ")", text_color=color.green, text_size=size.small)
table.cell(t, 0, 6, "Expected bars", text_color=color.white, text_size=size.small)
table.cell(t, 1, 6, na(expBars) ? "n/a" : str.tostring(expBars, "#"), text_color=color.white, text_size=size.small)
table.cell(t, 0, 7, "Regime (H)", text_color=color.white, text_size=size.small)
table.cell(t, 1, 7, regimeLabel + " (" + str.tostring(H, "#.##") + ")", text_color=regimeColor, text_size=size.small)
table.cell(t, 0, 8, "Drift eff / σ per bar", text_color=color.white, text_size=size.small)
table.cell(t, 1, 8, str.tostring(muEff * 100, "#.###") + "% / " + str.tostring(sigma * 100, "#.##") + "%", text_color=color.gray, text_size=size.small)
table.cell(t, 0, 9, "MR z-score", text_color=color.white, text_size=size.small)
table.cell(t, 1, 9, str.tostring(z, "#.##"), text_color=z < -1 ? color.lime : z > 1 ? color.red : color.gray, text_size=size.small)
table.cell(t, 0, 10, "Jump share / BNS", text_color=color.white, text_size=size.small)
table.cell(t, 1, 10, str.tostring(jumpShare * 100, "#") + "% / z " + str.tostring(bnsZ, "#.#") + (bnsSig ? " ✱" : ""), text_color=jumpShare > 0.5 or bnsSig ? color.red : color.gray, text_size=size.small)
table.cell(t, 0, 11, "Skew / ex.kurt", text_color=color.white, text_size=size.small)
table.cell(t, 1, 11, str.tostring(S, "#.##") + " / " + str.tostring(K, "#.##"), text_color=heavy ? color.orange : color.gray, text_size=size.small)
table.cell(t, 0, 12, "VaR99 CF (down / up)", text_color=color.white, text_size=size.small)
table.cell(t, 1, 12, str.tostring(var99cf, "#.##") + "% / " + str.tostring(up99cf, "#.##") + "%", text_color=color.orange, text_size=size.small)
table.cell(t, 0, 13, "Compression", text_color=color.white, text_size=size.small)
table.cell(t, 1, 13, str.tostring(comp, "#") + (cleanUp ? " clean break ↑" : cleanDn ? " clean break ↓" : breakoutUp ? " break ↑" : breakoutDn ? " break ↓" : comp >= 55 ? " coiled" : ""), text_color=comp >= 55 ? color.aqua : color.gray, text_size=size.small)
table.cell(t, 0, 14, "Liquidity", text_color=color.white, text_size=size.small)
table.cell(t, 1, 14, (magnet ? "magnet before target" : "no magnet") + (stopHunt ? " · STOP IN HUNT ZONE" : ""), text_color=stopHunt ? color.red : magnet ? color.lime : color.gray, text_size=size.small)
table.cell(t, 0, 15, "Risk / trade (Kelly)", text_color=color.white, text_size=size.small)
table.cell(t, 1, 15, nR >= minN ? str.tostring(riskPct, "#.##") + "% (" + str.tostring(riskAmt, "#") + ")" : "calibrating " + str.tostring(nR) + "/" + str.tostring(minN), text_color=riskPct > 0 ? color.lime : color.gray, text_size=size.small)
table.cell(t, 0, 16, "Units", text_color=color.white, text_size=size.small)
table.cell(t, 1, 16, str.tostring(units, "#.##"), text_color=color.white, text_size=size.small)
table.cell(t, 0, 17, "Track record", text_color=color.white, text_size=size.small)
table.cell(t, 1, 17, str.tostring(nR) + " trades · win " + str.tostring(pWin * 100, "#") + "% · payoff " + str.tostring(payoff, "#.##") + " · exp " + str.tostring(expectancyR, "#.##") + "R", text_color=expectancyR > 0 ? color.lime : color.gray, text_size=size.small)
table.cell(t, 0, 18, "Open virtual", text_color=color.white, text_size=size.small)
table.cell(t, 1, 18, str.tostring(array.size(oDir)) + " / " + str.tostring(maxOpen), text_color=color.gray, text_size=size.small)
// === ALERTAS ===
alertcondition(sigLong, "TQE Long", "TQE: long setup crossed the score threshold")
alertcondition(sigShort, "TQE Short", "TQE: short setup crossed the score threshold")
alertcondition(stopHunt and not stopHunt[1], "TQE Stop in hunt zone", "TQE: suggested stop sits just under/over a liquidity pool")
if sigLong or sigShort
alert("TQE " + dirLabel + " " + syminfo.ticker + " score " + str.tostring(score, "#") + " | SL " + str.tostring(stopPx, format.mintick) + " TP " + str.tostring(tgtPx, format.mintick) + " (" + str.tostring(rSel, "#.#") + "R) | P " + str.tostring(pT * 100, "#") + "% EV " + str.tostring(evR, "#.##") + "R | risk " + str.tostring(riskPct, "#.##") + "%", alert.freq_once_per_bar_close)
Show all 542 lines