Indicator · Pine Script v5 · On the chart

TQE Trade Quality Engine

by AssetWhisper

An overlay indicator that combines several statistical modules into a single trade planning panel.

View on GitHub

GitHub stars1on the repository
Pine versionv5runs as is
Lines of code542
Settings25inputs
LicenceMPL-2.0
Last update7 weeks ago10 Sep 2026

Add It to Your TradingView Chart

  1. 1Copy the scriptPress Copy script above, or the copy button on the code below.
  2. 2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
  3. 3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
  4. 4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.

Source Code

542 lines of Pine Script v5 from indicators/TQE_Trade_Quality_Engine.pine, published under MPL-2.0. Keep the author's credit and licence lines when you reuse it.

TQE_Trade_Quality_Engine.pine
//@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)

Overview

For a long or short direction, chosen automatically or manually, it proposes an ATR-based stop widened for tail risk, a target R multiple optimised for expectancy, a quality score from 0 to 100, and a position size based on simulated results. Inputs include Hurst regime, skew, jump, compression and liquidity level settings. It plans trades but does not place orders.

Details

Published by

AssetWhisper on GitHub

Type

Indicator, Pine Script v5

Draws

On the price chart

Category

Volatility & bands, Support & resistance

Markets

Any symbol

Timeframes

Any timeframe

Risk Notes

No repainting, lookahead or stacked entries were found in its code or README. That says nothing about how well it works: test it first.

Settings

The inputs you can change from the gear icon on the chart. Defaults are the author's.

SettingTypeDefaultVariable
DirectionstringAutodirMode
Base stop (ATR mult)float1.5stopATRk
Tail protection (× VaR99 per bar)float1.5tailMult
Optimise target R for max expectancybooltrueautoR
Fixed target R (if not optimised)float2.0fixedR
Signal threshold (score 0-100)int65signalTh
Drift / vol lookbackint100lookback
Hurst window N (uses N, N/2, N/4)int128hurstLen
Skew / kurtosis windowint60distLen
Jump windowint30jumpLen
Mean-reversion anchor lengthint20mrLen
Pivot lengthint10pivotLen
Show all 25 settings
SettingTypeDefaultVariable
Max liquidity levels per sideint10maxLevels
Level max age (bars)int400maxAge
Compression percentile lookbackint100rankLen
Kelly fractionfloat0.5kFrac
Max risk per trade (%)float3.0kCap
Min virtual trades for sizingint15minN
Results window (trades)int40sampleN
Virtual trade time exit (bars)int30maxBars
Max simultaneous virtual tradesint3maxOpen
Account equityfloat10000equity
Show tablebooltrueshowTable
Show liquidity levelsbooltrueshowLevels
Regime backgroundbooltrueshowRegime
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.

Similar Pine Scripts

ICT Liquidity Void Fill

by ArunKBhaskar
575 stars

This overlay indicator marks ICT liquidity void fills on the price chart and can show long and short signals. It places a Fibonacci level inside a displacement candle, defaulting to the midpoint, and draws lines in a chosen style. Optional filters limit the fi

Indicator v6 SMC / ICT / price actionSupport & resistance
Updated 1 year ago · MPL-2.0 GitHub repository of ICT Liquidity Void Fill

[RS]Donchian Fans

by ricardosantos79
261 stars

This indicator plots Donchian Channel boundary lines along with dynamic fan projections directly on the price chart. It calculates the highest high, lowest low, and median price over a customizable lookback period. Whenever price establishes a new channel high

Indicator v4 Support & resistanceTrend
Updated 4 years ago · None stated GitHub repository of [RS]Donchian Fans

Lux Trailing

by dearvn
257 stars

Plots kernel-smoothed price zones on the price chart using a Rational Quadratic kernel from an imported library. It builds an inner and an outer high-low zone from 15-minute highs and lows, plus a smoothed close and a midline, and marks bullish or bearish bars

Indicator v5 Support & resistanceVolatility & bands Repaints
Updated 6 weeks ago · None stated GitHub repository of Lux Trailing

Candlestick Patterns

by harryguiacorn
219 stars

This indicator plots optional candlestick pattern signals on the price chart. It detects engulfing bars, inside bars, HOLP and LOHP, kicker patterns, body-mass extremes, higher highs and lower lows, ATR warnings, and four-bar fractals. Optional pivot marks use

Indicator v5 Candlestick patternsVolatility & bands
Updated 1 year ago · None stated GitHub repository of Candlestick Patterns

Dynamic Linear Regression

by TradersPost
218 stars

Dynamic Linear Regression calculates and plots linear regression trendlines and standard deviation channels directly on the price chart over a user-selected time period. Traders can configure static channels locked at the end time, dynamic channels that contin

Indicator v6 TrendSupport & resistance
Updated 3 months ago · None stated GitHub repository of Dynamic Linear Regression

More indicators

Trade Your Charts With a Regulated Broker

Compare brokers that connect to TradingView, with spreads, minimum deposits and leverage side by side.

Compare brokers