16 agents en ligneXAUUSD 4 552,4Session : London KillzoneProchaine analyse : 04:12
Indicateurs · Pine Script v6 · Gratis
● Gratuit · TradingView

Tetris ICT [Bolívar] : votre indicateur ICT pour TradingView

Le marché conserve une mémoire. Chaque Fair Value Gap, chaque Order Block non mitigé est une dette structurelle que le prix finira par combler. Tetris ICT [Bolívar] transforme ces dettes en pièces Tetris à l'écran pour vous apprendre à lire l'ordre caché du marché.

Créé par Xavier Mas, fondateur de Bolívar Bolsa, pour plus de 42 000 étudiants ICT/SMC en Amérique latine. Gratuit, sans restriction, écrit en Pine Script v6.

Ajouter sur TradingViewVoir le codeTélécharger .pine

Que fait-il ?

  • Fair Value Gap → pièce I. Détecte les déséquilibres de 3 bougies et les marque comme zones en attente de remplissage.
  • Order Block → pièce O. Identifie la dernière bougie opposée avant un mouvement expansif : l'origine institutionnelle de l'impulsion.
  • Balanced Price Range (BPR) → pièce S. Cartographie le chevauchement de deux FVG opposés — zone d'efficacité de prix maximale.
  • Breaker Block → pièce L. Détecte les Order Blocks ayant échoué et devenus une structure inverse.
  • Zones de liquidité → pièce T. Marque les accumulations de stops (hauts/bas égaux) où le prix tend à balayer avant de s'inverser.
  • HUD latéral de style arcade. Regroupe les pièces actives (non mitigées) avec un compteur de dette structurelle en temps réel.

Comment lire le score

Le HUD calcule combien de zones non mitigées restent actives sur le prix et affiche l'un des trois états :

ScoreÉtatLecture éducative
≥ 65BLOCKEDDensité élevée de dette structurelle. Le prix fait face à des zones en attente sur plusieurs niveaux.
30 – 64CAUTIONDensité modérée. Certaines zones non mitigées peuvent influencer le parcours.
< 30GREENTableau propre. Peu de dettes actives ; le prix évolue en espace ouvert.

L'aperçu de la pièce suivante (next-piece preview) anticipe quel type de zone apparaît le plus près du prix. Chaque pièce disparaît du tableau quand le prix la mitigue, comme une ligne complète dans Tetris.

Comment l'installer sur TradingView

  1. Ouvrez le Pine Editor. Dans TradingView, cliquez sur l'onglet Pine Editor (panneau inférieur).
  2. Collez le code. Copiez le code complet ci-dessous et remplacez le contenu de l'éditeur.
  3. Ajoutez au graphique. Cliquez sur « Add to chart » (en haut à droite de l'éditeur).
  4. Sauvegardez. Appuyez sur Ctrl/Cmd + S et donnez-lui un nom. Il sera enregistré dans votre bibliothèque TradingView.

Code Pine Script v6

Tetris-ICT [Bolívar] · Pine v6 · 1137 lignes · Gratuit.

Télécharger .pine
// ============================================================
// Tetris-ICT — bolivarbolsa.com — by Xavier Mas
// Indicador TradingView Pine Script v6
//
// Concepto: Las deudas estructurales del mercado ICT (FVG, OB,
// Breaker, BPR, Liquidez) se visualizan como piezas de Tetris
// en un tablero lateral con score HUD, next-piece preview y
// paleta de colores neón arcade. Cada zona no mitigada es una
// pieza activa que "bloquea" el precio hasta ser rellenada.
//
// Estructuras detectadas:
//   FVG  → pieza I (cyan / rojo)
//   OB   → pieza O (verde / naranja)
//   BPR  → pieza S (oro)
//   Breaker → pieza L (azul / violeta)
//   Liq  → pieza T (magenta / violeta oscuro)
//
// Scoring:
//   Debt Long  = presión bajista encima del precio (FVG bear, OB bear, etc.)
//   Debt Short = presión alcista debajo del precio
//   BLOCKED (≥65) / CAUTION (≥30) / GREEN (<30)
//
// Créditos: Xavier Mas — Fundador bolivarbolsa.com
//   42k estudiantes Forex/ICT/SMC LATAM
// ============================================================
//@version=6
indicator("Tetris-ICT [Bolívar]", shorttitle="TETRIS-ICT", overlay=true,
          max_boxes_count=500, max_lines_count=100, max_labels_count=150, max_bars_back=500)

// ============================================================
// PALETA DE COLORES NEÓN SYNTHWAVE
// ============================================================
var color C_FVG_BULL      = #00F5FF
var color C_FVG_BEAR      = #FF3131
var color C_OB_BULL       = #39FF14
var color C_OB_BEAR       = #FF7F00
var color C_BPR           = #FFD700
var color C_BREAKER_BULL  = #4D79FF
var color C_BREAKER_BEAR  = #CC44FF
var color C_LIQ_HIGH      = #FF00FF
var color C_LIQ_LOW       = #9D00FF
var color C_BG            = #0D0D0D
var color C_GRID          = #1A1A2E
var color C_TEXT          = #00FF41
var color C_BLOCKED       = #FF0044
var color C_GREEN         = #00FF41
var color C_CAUTION       = #FFFF00

// ============================================================
// INPUTS — GRUPO "Detection"
// ============================================================
string GRP_DET = "Detection"
i_fvg_atr_mult    = input.float(0.15, "FVG min gap (ATR × factor)", minval=0.0, step=0.05, group=GRP_DET,
                     tooltip="0 = sin filtro de tamaño mínimo")
i_ob_lookback     = input.int(10, "OB lookback (barras)", minval=3, maxval=10, group=GRP_DET,
                     tooltip="Lookback limité à 10 para rendimiento")
i_swing_len       = input.int(5, "Swing length (pivot left/right)", minval=2, maxval=20, group=GRP_DET)
i_bpr_enabled     = input.bool(true, "Detectar BPR (Balanced Price Range)", group=GRP_DET)
i_mitigation_mode = input.string("Wick", "Modo mitigación", options=["Wick", "50%", "Close"], group=GRP_DET)
i_max_per_type    = input.int(10, "Máx. estructuras activas por tipo", minval=3, maxval=20, group=GRP_DET)

// ============================================================
// INPUTS — GRUPO "Visuals"
// ============================================================
string GRP_VIS = "Visuals"
i_show_onchart = input.bool(true,  "Mostrar bloques sobre el precio", group=GRP_VIS)
i_show_board   = input.bool(true,  "Mostrar Game Board lateral", group=GRP_VIS)
i_board_pos    = input.string("Bottom Right", "Posición del Game Board", options=["Top Right", "Middle Right", "Bottom Right", "Top Left", "Middle Left", "Bottom Left"], group=GRP_VIS)
i_board_size   = input.string("Compact", "Tamaño Game Board", options=["Compact", "Normal", "Large"], group=GRP_VIS)
i_show_hud     = input.bool(true,  "Mostrar HUD (top-right)", group=GRP_VIS)
i_hud_pos      = input.string("Top Right", "Posición del HUD", options=["Top Right", "Top Center", "Top Left", "Bottom Right", "Bottom Center", "Bottom Left"], group=GRP_VIS)
i_show_next    = input.bool(true,  "Mostrar Next-Piece (top-left)", group=GRP_VIS)
i_next_pos     = input.string("Top Left", "Posición del Next-Piece", options=["Top Left", "Top Center", "Top Right", "Bottom Left", "Bottom Center", "Bottom Right"], group=GRP_VIS)
i_show_grid    = input.bool(true,  "Mostrar grilla horizontal ATR", group=GRP_VIS)
i_opacity      = input.int(70, "Opacidad bloques", minval=10, maxval=95, group=GRP_VIS)
i_beginner     = input.bool(false, "Modo principiante (labels extendidos)", group=GRP_VIS)

// ============================================================
// INPUTS — GRUPO "Scoring"
// ============================================================
string GRP_SCO = "Scoring"
i_w_fvg          = input.float(15.0, "Peso FVG",     minval=0.0, group=GRP_SCO)
i_w_ob           = input.float(20.0, "Peso OB",      minval=0.0, group=GRP_SCO)
i_w_breaker      = input.float(25.0, "Peso Breaker", minval=0.0, group=GRP_SCO)
i_w_bpr          = input.float(20.0, "Peso BPR",     minval=0.0, group=GRP_SCO)
i_w_liq          = input.float(22.0, "Peso Liq",     minval=0.0, group=GRP_SCO)
i_thresh_blocked = input.int(65, "Umbral BLOCKED", minval=1, maxval=100, group=GRP_SCO)
i_thresh_caution = input.int(30, "Umbral CAUTION", minval=1, maxval=100, group=GRP_SCO)
i_dist_cap_atr   = input.float(6.0, "Distancia máx. (ATR ×)", minval=1.0, group=GRP_SCO)
i_age_halflife   = input.int(500, "Semi-vida edad (barras)", minval=50, group=GRP_SCO)

// ============================================================
// INPUTS — GRUPO "Alerts"
// ============================================================
string GRP_ALT = "Alerts"
i_alert_blocked    = input.bool(true, "Alerta BLOCKED", group=GRP_ALT)
i_alert_green      = input.bool(true, "Alerta GREEN",   group=GRP_ALT)
i_alert_mitigation = input.bool(true, "Alerta Mitigación", group=GRP_ALT)

// ============================================================
// ARRAYS DE DATOS — FVG
// ============================================================
var array<float> fvg_top   = array.new<float>()
var array<float> fvg_bot   = array.new<float>()
var array<int>   fvg_dir   = array.new<int>()
var array<int>   fvg_bar   = array.new<int>()
var array<bool>  fvg_mitig = array.new<bool>()
var array<box>   fvg_box   = array.new<box>()

// ============================================================
// ARRAYS DE DATOS — OB (Order Block)
// ============================================================
var array<float> ob_top   = array.new<float>()
var array<float> ob_bot   = array.new<float>()
var array<int>   ob_dir   = array.new<int>()
var array<int>   ob_bar   = array.new<int>()
var array<bool>  ob_mitig = array.new<bool>()
var array<box>   ob_box   = array.new<box>()

// ============================================================
// ARRAYS DE DATOS — BREAKER
// ============================================================
var array<float> brk_top   = array.new<float>()
var array<float> brk_bot   = array.new<float>()
var array<int>   brk_dir   = array.new<int>()
var array<int>   brk_bar   = array.new<int>()
var array<bool>  brk_mitig = array.new<bool>()
var array<box>   brk_box   = array.new<box>()
var int          brk_max   = 8

// ============================================================
// ARRAYS DE DATOS — BPR (Balanced Price Range)
// ============================================================
var array<float> bpr_top   = array.new<float>()
var array<float> bpr_bot   = array.new<float>()
var array<int>   bpr_dir   = array.new<int>()
var array<int>   bpr_bar   = array.new<int>()
var array<bool>  bpr_mitig = array.new<bool>()
var array<box>   bpr_box   = array.new<box>()
var int          bpr_max   = 6

// ============================================================
// ARRAYS DE DATOS — LIQUIDEZ (BSL / SSL)
// ============================================================
var array<float> liq_top   = array.new<float>()
var array<float> liq_bot   = array.new<float>()
var array<int>   liq_dir   = array.new<int>()
var array<int>   liq_bar   = array.new<int>()
var array<bool>  liq_mitig = array.new<bool>()
var array<box>   liq_box   = array.new<box>()

// ============================================================
// VARIABLES DE ESTADO GLOBAL
// ============================================================
var int   lines_cleared    = 0
var int   blink_until      = 0
var bool  mitig_event      = false
var float mitig_price      = na
var string mitig_type      = ""

// Señales previas para alertas de transición
var string prev_signal_long  = "GREEN"
var string prev_signal_short = "GREEN"
var int    prev_lines        = 0

// ============================================================
// GRILLA HORIZONTAL (creada una sola vez)
// ============================================================
var array<line> grid_lines = array.new<line>()
if i_show_grid and array.size(grid_lines) == 0
    for _i = 0 to 19
        array.push(grid_lines, line.new(bar_index, close, bar_index + 1, close,
                   color=color.new(C_GRID, 80), style=line.style_dotted, width=1))

// ============================================================
// TABLAS (creadas una sola vez)
// ============================================================
var table tbl_hud  = na
var table tbl_next = na
var table tbl_board = na

// Helper: convertir string del input → position constant
pos_of(s) =>
    switch s
        "Top Right"     => position.top_right
        "Top Center"    => position.top_center
        "Top Left"      => position.top_left
        "Middle Right"  => position.middle_right
        "Middle Center" => position.middle_center
        "Middle Left"   => position.middle_left
        "Bottom Right"  => position.bottom_right
        "Bottom Center" => position.bottom_center
        "Bottom Left"   => position.bottom_left
        => position.bottom_right

// Dimensiones de cada celda del Game Board (% del chart)
board_cell_w = i_board_size == "Compact" ? 1.4 : i_board_size == "Normal" ? 2.2 : 3.0
board_cell_h = i_board_size == "Compact" ? 0.9 : i_board_size == "Normal" ? 1.4 : 1.9

if i_show_hud and na(tbl_hud)
    tbl_hud := table.new(pos_of(i_hud_pos), 2, 6,
                         bgcolor=C_BG, frame_color=C_TEXT, frame_width=2,
                         border_color=color.new(C_TEXT, 60), border_width=1)

if i_show_next and na(tbl_next)
    tbl_next := table.new(pos_of(i_next_pos), 6, 8,
                          bgcolor=C_BG, frame_color=C_LIQ_HIGH, frame_width=2,
                          border_color=color.new(C_LIQ_HIGH, 60), border_width=1)

if i_show_board and na(tbl_board)
    tbl_board := table.new(pos_of(i_board_pos), 12, 22,
                           bgcolor=C_BG, frame_color=C_TEXT, frame_width=3,
                           border_color=color.new(C_TEXT, 50), border_width=1)

// ============================================================
// ATR Y PARÁMETROS BASE
// ============================================================
atr14   = ta.atr(14)
fvg_min = atr14 * i_fvg_atr_mult

// ============================================================
// FUNCIÓN: color segun señal
// ============================================================
sig_color(sig) =>
    sig == "BLOCKED" ? C_BLOCKED : sig == "CAUTION" ? C_CAUTION : C_GREEN

// ============================================================
// FUNCIÓN: barra de score ASCII
// ============================================================
score_bar(v) =>
    filled = int(math.round(math.max(0.0, math.min(100.0, v)) / 10.0))
    str.repeat("▓", filled) + str.repeat("░", 10 - filled)

// ============================================================
// FUNCIÓN: verificar mitigación de una zona
// ============================================================
is_mitigated(top_val, bot_val) =>
    mid = (top_val + bot_val) / 2.0
    result = false
    if i_mitigation_mode == "Wick"
        result := low <= top_val and high >= bot_val
    else if i_mitigation_mode == "50%"
        result := low <= mid or high >= mid
    else
        result := close >= bot_val and close <= top_val
    result

// ============================================================
// FUNCIÓN: pruning de un array de estructuras
// ============================================================
prune_struct(tops, bots, dirs, bars, mitigs, boxes, max_count) =>
    // Primero eliminar mitigadas
    i = 0
    while i < array.size(mitigs)
        if array.get(mitigs, i)
            b = array.get(boxes, i)
            if not na(b)
                box.delete(b)
            array.remove(tops,   i)
            array.remove(bots,   i)
            array.remove(dirs,   i)
            array.remove(bars,   i)
            array.remove(mitigs, i)
            array.remove(boxes,  i)
        else
            i += 1
    // Luego FIFO si sigue sobre el límite
    while array.size(tops) >= max_count
        b = array.get(boxes, 0)
        if not na(b)
            box.delete(b)
        array.remove(tops,   0)
        array.remove(bots,   0)
        array.remove(dirs,   0)
        array.remove(bars,   0)
        array.remove(mitigs, 0)
        array.remove(boxes,  0)

// ============================================================
// DETECCIÓN FVG — solo en barras confirmadas
// ============================================================
if barstate.isconfirmed
    // FVG alcista: vela[0].low > vela[2].high → gap entre vela 2 y vela 0
    bull_fvg_size = low[1] - high[3]
    if low[1] > high[3] and bull_fvg_size >= fvg_min
        prune_struct(fvg_top, fvg_bot, fvg_dir, fvg_bar, fvg_mitig, fvg_box, i_max_per_type)
        new_box = box.new(bar_index[2], high[3], bar_index + 5, low[1],
                          border_color=C_FVG_BULL, border_width=2,
                          bgcolor=color.new(C_FVG_BULL, i_opacity))
        array.push(fvg_top,   low[1])
        array.push(fvg_bot,   high[3])
        array.push(fvg_dir,   1)
        array.push(fvg_bar,   bar_index[2])
        array.push(fvg_mitig, false)
        array.push(fvg_box,   new_box)

    // FVG bajista: vela[0].high < vela[2].low → gap inverso
    bear_fvg_size = low[3] - high[1]
    if high[1] < low[3] and bear_fvg_size >= fvg_min
        prune_struct(fvg_top, fvg_bot, fvg_dir, fvg_bar, fvg_mitig, fvg_box, i_max_per_type)
        new_box = box.new(bar_index[2], high[1], bar_index + 5, low[3],
                          border_color=C_FVG_BEAR, border_width=2,
                          bgcolor=color.new(C_FVG_BEAR, i_opacity))
        array.push(fvg_top,   low[3])
        array.push(fvg_bot,   high[1])
        array.push(fvg_dir,   -1)
        array.push(fvg_bar,   bar_index[2])
        array.push(fvg_mitig, false)
        array.push(fvg_box,   new_box)

// ============================================================
// DETECCIÓN OB (Order Block)
// ============================================================
if barstate.isconfirmed
    // OB alcista: última vela bajista antes de un desplazamiento alcista fuerte
    // Condición: close actual > high[3], y hay una vela bajista reciente
    displacement_bull = close > high[3] and (close - low[3]) >= atr14 * 0.5
    if displacement_bull
        best_bear_idx = 0
        best_bear_body = 0.0
        for k = 1 to math.min(i_ob_lookback, 10)
            body_k = open[k] - close[k]
            if body_k > best_bear_body and body_k >= atr14 * 0.5
                best_bear_body := body_k
                best_bear_idx  := k
        if best_bear_idx > 0 and best_bear_body > 0.0
            ob_t = high[best_bear_idx]
            ob_b = low[best_bear_idx]
            // Verificar que no existe ya un OB muy cercano
            already_exists = false
            if array.size(ob_top) > 0
                for m = 0 to array.size(ob_top) - 1
                    if math.abs(array.get(ob_top, m) - ob_t) < atr14 * 0.3
                        already_exists := true
                        break
            if not already_exists
                prune_struct(ob_top, ob_bot, ob_dir, ob_bar, ob_mitig, ob_box, i_max_per_type)
                new_box = box.new(bar_index[best_bear_idx], ob_b, bar_index + 5, ob_t,
                                  border_color=C_OB_BULL, border_width=2,
                                  bgcolor=color.new(C_OB_BULL, i_opacity))
                array.push(ob_top,   ob_t)
                array.push(ob_bot,   ob_b)
                array.push(ob_dir,   1)
                array.push(ob_bar,   bar_index[best_bear_idx])
                array.push(ob_mitig, false)
                array.push(ob_box,   new_box)

    // OB bajista: última vela alcista antes de un desplazamiento bajista fuerte
    displacement_bear = close < low[3] and (high[3] - close) >= atr14 * 0.5
    if displacement_bear
        best_bull_idx  = 0
        best_bull_body = 0.0
        for k = 1 to math.min(i_ob_lookback, 10)
            body_k = close[k] - open[k]
            if body_k > best_bull_body and body_k >= atr14 * 0.5
                best_bull_body := body_k
                best_bull_idx  := k
        if best_bull_idx > 0 and best_bull_body > 0.0
            ob_t = high[best_bull_idx]
            ob_b = low[best_bull_idx]
            already_exists = false
            if array.size(ob_top) > 0
                for m = 0 to array.size(ob_top) - 1
                    if math.abs(array.get(ob_top, m) - ob_t) < atr14 * 0.3
                        already_exists := true
                        break
            if not already_exists
                prune_struct(ob_top, ob_bot, ob_dir, ob_bar, ob_mitig, ob_box, i_max_per_type)
                new_box = box.new(bar_index[best_bull_idx], ob_b, bar_index + 5, ob_t,
                                  border_color=C_OB_BEAR, border_width=2,
                                  bgcolor=color.new(C_OB_BEAR, i_opacity))
                array.push(ob_top,   ob_t)
                array.push(ob_bot,   ob_b)
                array.push(ob_dir,   -1)
                array.push(ob_bar,   bar_index[best_bull_idx])
                array.push(ob_mitig, false)
                array.push(ob_box,   new_box)

// ============================================================
// DETECCIÓN BREAKER — OB invalidado (precio cierra más allá)
// ============================================================
if barstate.isconfirmed
    i = 0
    while i < array.size(ob_top)
        ob_t   = array.get(ob_top,  i)
        ob_b   = array.get(ob_bot,  i)
        ob_d   = array.get(ob_dir,  i)
        ob_br  = array.get(ob_bar,  i)
        // OB alcista invalidado → precio cierra BAJO el OB → se convierte en bearish breaker
        if ob_d == 1 and close < ob_b
            prune_struct(brk_top, brk_bot, brk_dir, brk_bar, brk_mitig, brk_box, brk_max)
            new_box = box.new(ob_br, ob_b, bar_index + 5, ob_t,
                              border_color=C_BREAKER_BEAR, border_width=2,
                              bgcolor=color.new(C_BREAKER_BEAR, i_opacity))
            array.push(brk_top,   ob_t)
            array.push(brk_bot,   ob_b)
            array.push(brk_dir,   -1)
            array.push(brk_bar,   ob_br)
            array.push(brk_mitig, false)
            array.push(brk_box,   new_box)
            // Eliminar del array OB
            old_box = array.get(ob_box, i)
            if not na(old_box)
                box.delete(old_box)
            array.remove(ob_top,   i)
            array.remove(ob_bot,   i)
            array.remove(ob_dir,   i)
            array.remove(ob_bar,   i)
            array.remove(ob_mitig, i)
            array.remove(ob_box,   i)
        // OB bajista invalidado → precio cierra SOBRE el OB → se convierte en bullish breaker
        else if ob_d == -1 and close > ob_t
            prune_struct(brk_top, brk_bot, brk_dir, brk_bar, brk_mitig, brk_box, brk_max)
            new_box = box.new(ob_br, ob_b, bar_index + 5, ob_t,
                              border_color=C_BREAKER_BULL, border_width=2,
                              bgcolor=color.new(C_BREAKER_BULL, i_opacity))
            array.push(brk_top,   ob_t)
            array.push(brk_bot,   ob_b)
            array.push(brk_dir,   1)
            array.push(brk_bar,   ob_br)
            array.push(brk_mitig, false)
            array.push(brk_box,   new_box)
            old_box = array.get(ob_box, i)
            if not na(old_box)
                box.delete(old_box)
            array.remove(ob_top,   i)
            array.remove(ob_bot,   i)
            array.remove(ob_dir,   i)
            array.remove(ob_bar,   i)
            array.remove(ob_mitig, i)
            array.remove(ob_box,   i)
        else
            i += 1

// ============================================================
// DETECCIÓN BPR (Balanced Price Range)
// ============================================================
if barstate.isconfirmed and i_bpr_enabled
    n_fvg = array.size(fvg_top)
    if n_fvg >= 2
        last_bull_idx = -1
        last_bear_idx = -1
        for k = n_fvg - 1 to 0
            d = array.get(fvg_dir, k)
            if d == 1 and last_bull_idx < 0
                last_bull_idx := k
            else if d == -1 and last_bear_idx < 0
                last_bear_idx := k
            if last_bull_idx >= 0 and last_bear_idx >= 0
                break
        if last_bull_idx >= 0 and last_bear_idx >= 0
            bar_bull = array.get(fvg_bar, last_bull_idx)
            bar_bear = array.get(fvg_bar, last_bear_idx)
            bar_diff = math.abs(bar_bull - bar_bear)
            if bar_diff <= 20
                // Verificar solapamiento
                bull_top = array.get(fvg_top, last_bull_idx)
                bull_bot = array.get(fvg_bot, last_bull_idx)
                bear_top = array.get(fvg_top, last_bear_idx)
                bear_bot = array.get(fvg_bot, last_bear_idx)
                overlap_top = math.min(bull_top, bear_top)
                overlap_bot = math.max(bull_bot, bear_bot)
                if overlap_top > overlap_bot
                    // Zona de solapamiento válida — verificar no duplicado
                    already_bpr = false
                    if array.size(bpr_top) > 0
                        for m = 0 to array.size(bpr_top) - 1
                            if math.abs(array.get(bpr_top, m) - overlap_top) < atr14 * 0.2
                                already_bpr := true
                                break
                    if not already_bpr
                        prune_struct(bpr_top, bpr_bot, bpr_dir, bpr_bar, bpr_mitig, bpr_box, bpr_max)
                        new_box = box.new(math.min(bar_bull, bar_bear), overlap_bot,
                                          bar_index + 5, overlap_top,
                                          border_color=C_BPR, border_width=2,
                                          bgcolor=color.new(C_BPR, i_opacity))
                        array.push(bpr_top,   overlap_top)
                        array.push(bpr_bot,   overlap_bot)
                        array.push(bpr_dir,   0)
                        array.push(bpr_bar,   bar_index)
                        array.push(bpr_mitig, false)
                        array.push(bpr_box,   new_box)

// ============================================================
// DETECCIÓN LIQUIDEZ (BSL / SSL via pivots)
// ============================================================
ph = ta.pivothigh(high, i_swing_len, i_swing_len)
pl = ta.pivotlow(low,   i_swing_len, i_swing_len)

if barstate.isconfirmed
    if not na(ph)
        already_liq = false
        if array.size(liq_top) > 0
            for m = 0 to array.size(liq_top) - 1
                if math.abs(array.get(liq_top, m) - ph) < atr14 * 0.3
                    already_liq := true
                    break
        if not already_liq
            prune_struct(liq_top, liq_bot, liq_dir, liq_bar, liq_mitig, liq_box, i_max_per_type)
            new_box = box.new(bar_index[i_swing_len], ph - atr14 * 0.05,
                              bar_index + 5, ph + atr14 * 0.05,
                              border_color=C_LIQ_HIGH, border_width=1,
                              bgcolor=color.new(C_LIQ_HIGH, 85))
            array.push(liq_top,   ph + atr14 * 0.05)
            array.push(liq_bot,   ph - atr14 * 0.05)
            array.push(liq_dir,   1)
            array.push(liq_bar,   bar_index[i_swing_len])
            array.push(liq_mitig, false)
            array.push(liq_box,   new_box)

    if not na(pl)
        already_liq = false
        if array.size(liq_top) > 0
            for m = 0 to array.size(liq_top) - 1
                if math.abs(array.get(liq_bot, m) - pl) < atr14 * 0.3
                    already_liq := true
                    break
        if not already_liq
            prune_struct(liq_top, liq_bot, liq_dir, liq_bar, liq_mitig, liq_box, i_max_per_type)
            new_box = box.new(bar_index[i_swing_len], pl - atr14 * 0.05,
                              bar_index + 5, pl + atr14 * 0.05,
                              border_color=C_LIQ_LOW, border_width=1,
                              bgcolor=color.new(C_LIQ_LOW, 85))
            array.push(liq_top,   pl + atr14 * 0.05)
            array.push(liq_bot,   pl - atr14 * 0.05)
            array.push(liq_dir,   -1)
            array.push(liq_bar,   bar_index[i_swing_len])
            array.push(liq_mitig, false)
            array.push(liq_box,   new_box)

// ============================================================
// CHECK MITIGACIÓN — actualizar estado de todas las estructuras
// ============================================================
mitig_event := false

if barstate.isconfirmed
    // FVG
    if array.size(fvg_top) > 0
        for i = 0 to array.size(fvg_top) - 1
            if not array.get(fvg_mitig, i)
                if is_mitigated(array.get(fvg_top, i), array.get(fvg_bot, i))
                    array.set(fvg_mitig, i, true)
                    b = array.get(fvg_box, i)
                    if not na(b)
                        box.set_bgcolor(b, color.new(color.gray, 90))
                        box.set_border_color(b, color.new(color.gray, 50))
                    mitig_event := true
                    mitig_type  := "FVG"
                    mitig_price := (array.get(fvg_top, i) + array.get(fvg_bot, i)) / 2.0

    // OB
    if array.size(ob_top) > 0
        for i = 0 to array.size(ob_top) - 1
            if not array.get(ob_mitig, i)
                if is_mitigated(array.get(ob_top, i), array.get(ob_bot, i))
                    array.set(ob_mitig, i, true)
                    b = array.get(ob_box, i)
                    if not na(b)
                        box.set_bgcolor(b, color.new(color.gray, 90))
                        box.set_border_color(b, color.new(color.gray, 50))
                    mitig_event := true
                    mitig_type  := "OB"
                    mitig_price := (array.get(ob_top, i) + array.get(ob_bot, i)) / 2.0

    // Breaker
    if array.size(brk_top) > 0
        for i = 0 to array.size(brk_top) - 1
            if not array.get(brk_mitig, i)
                if is_mitigated(array.get(brk_top, i), array.get(brk_bot, i))
                    array.set(brk_mitig, i, true)
                    b = array.get(brk_box, i)
                    if not na(b)
                        box.set_bgcolor(b, color.new(color.gray, 90))
                        box.set_border_color(b, color.new(color.gray, 50))
                    mitig_event := true
                    mitig_type  := "BRK"
                    mitig_price := (array.get(brk_top, i) + array.get(brk_bot, i)) / 2.0

    // BPR
    if array.size(bpr_top) > 0
        for i = 0 to array.size(bpr_top) - 1
            if not array.get(bpr_mitig, i)
                if is_mitigated(array.get(bpr_top, i), array.get(bpr_bot, i))
                    array.set(bpr_mitig, i, true)
                    b = array.get(bpr_box, i)
                    if not na(b)
                        box.set_bgcolor(b, color.new(color.gray, 90))
                        box.set_border_color(b, color.new(color.gray, 50))
                    mitig_event := true
                    mitig_type  := "BPR"
                    mitig_price := (array.get(bpr_top, i) + array.get(bpr_bot, i)) / 2.0

    // Liquidez — sweep check
    if array.size(liq_top) > 0
        for i = 0 to array.size(liq_top) - 1
            if not array.get(liq_mitig, i)
                liq_d = array.get(liq_dir, i)
                liq_t = array.get(liq_top, i)
                liq_b = array.get(liq_bot, i)
                swept = false
                if liq_d == 1 and high > liq_t + atr14 * 0.1
                    swept := true
                else if liq_d == -1 and low < liq_b - atr14 * 0.1
                    swept := true
                if swept
                    array.set(liq_mitig, i, true)
                    b = array.get(liq_box, i)
                    if not na(b)
                        box.set_bgcolor(b, color.new(color.gray, 90))
                        box.set_border_color(b, color.new(color.gray, 50))
                    mitig_event := true
                    mitig_type  := "LIQ"
                    mitig_price := (liq_t + liq_b) / 2.0

// ============================================================
// ACTUALIZAR box.set_right EN TODAS LAS ESTRUCTURAS ACTIVAS
// ============================================================
if (barstate.islast or barstate.isrealtime) and i_show_onchart
    if array.size(fvg_box) > 0
        for i = 0 to array.size(fvg_box) - 1
            b = array.get(fvg_box, i)
            if not na(b)
                box.set_right(b, bar_index + 5)
    if array.size(ob_box) > 0
        for i = 0 to array.size(ob_box) - 1
            b = array.get(ob_box, i)
            if not na(b)
                box.set_right(b, bar_index + 5)
    if array.size(brk_box) > 0
        for i = 0 to array.size(brk_box) - 1
            b = array.get(brk_box, i)
            if not na(b)
                box.set_right(b, bar_index + 5)
    if array.size(bpr_box) > 0
        for i = 0 to array.size(bpr_box) - 1
            b = array.get(bpr_box, i)
            if not na(b)
                box.set_right(b, bar_index + 5)
    if array.size(liq_box) > 0
        for i = 0 to array.size(liq_box) - 1
            b = array.get(liq_box, i)
            if not na(b)
                box.set_right(b, bar_index + 5)

// ============================================================
// CÁLCULO DEBT SCORE — fórmula ICT ponderada con distancia y edad
// ============================================================
debt_long_raw  = 0.0
debt_short_raw = 0.0

// Helper interno para acumular el score de una zona
calc_contrib(top_val, bot_val, dir_val, bar_val, weight, is_mitig) =>
    contrib_l = 0.0
    contrib_s = 0.0
    if not is_mitig
        mid      = (top_val + bot_val) / 2.0
        dist     = math.abs(close - mid) / math.max(atr14, 0.0001)
        if dist <= i_dist_cap_atr
            dist_factor = math.max(0.5, math.min(2.0, 1.0 / (1.0 + dist * 0.1)))
            age         = bar_index - bar_val
            age_factor  = math.exp(-float(age) / float(i_age_halflife))
            contrib     = weight * dist_factor * age_factor
            // dir_val: 1 = alcista (presiona deuda SHORT si está debajo, debería ser LONG driver)
            // Lógica: estructura alcista DEBAJO del precio → deuda SHORT (el precio la debe regresar)
            // Estructura bajista ENCIMA del precio → deuda LONG (el precio la debe cubrir)
            if dir_val == 1 and mid < close
                contrib_s := contrib
            else if dir_val == -1 and mid > close
                contrib_l := contrib
            else if dir_val == 0
                // BPR: cuenta en ambos lados según posición
                if mid > close
                    contrib_l := contrib * 0.5
                else
                    contrib_s := contrib * 0.5
    [contrib_l, contrib_s]

// Helper top-level para contar zonas activas en cada fila Tetris
count_cells_into(tops, bots, mitigs, target, row_min_val) =>
    if array.size(tops) > 0
        for i = 0 to array.size(tops) - 1
            if not array.get(mitigs, i)
                t   = array.get(tops, i)
                b   = array.get(bots, i)
                mid = (t + b) / 2.0
                row_idx = int(math.floor((mid - row_min_val) / (atr14 * 0.5)))
                if row_idx >= 0 and row_idx < 20
                    array.set(target, row_idx, array.get(target, row_idx) + 1)

// Acumular FVG
if array.size(fvg_top) > 0
    for i = 0 to array.size(fvg_top) - 1
        [cl, cs] = calc_contrib(array.get(fvg_top, i), array.get(fvg_bot, i),
                                array.get(fvg_dir, i), array.get(fvg_bar, i),
                                i_w_fvg, array.get(fvg_mitig, i))
        debt_long_raw  += cl
        debt_short_raw += cs

// Acumular OB
if array.size(ob_top) > 0
    for i = 0 to array.size(ob_top) - 1
        [cl, cs] = calc_contrib(array.get(ob_top, i), array.get(ob_bot, i),
                                array.get(ob_dir, i), array.get(ob_bar, i),
                                i_w_ob, array.get(ob_mitig, i))
        debt_long_raw  += cl
        debt_short_raw += cs

// Acumular Breaker
if array.size(brk_top) > 0
    for i = 0 to array.size(brk_top) - 1
        [cl, cs] = calc_contrib(array.get(brk_top, i), array.get(brk_bot, i),
                                array.get(brk_dir, i), array.get(brk_bar, i),
                                i_w_breaker, array.get(brk_mitig, i))
        debt_long_raw  += cl
        debt_short_raw += cs

// Acumular BPR
if array.size(bpr_top) > 0
    for i = 0 to array.size(bpr_top) - 1
        [cl, cs] = calc_contrib(array.get(bpr_top, i), array.get(bpr_bot, i),
                                array.get(bpr_dir, i), array.get(bpr_bar, i),
                                i_w_bpr, array.get(bpr_mitig, i))
        debt_long_raw  += cl
        debt_short_raw += cs

// Acumular Liquidez
// BSL (dir=1) encima = deuda LONG; SSL (dir=-1) debajo = deuda SHORT
if array.size(liq_top) > 0
    for i = 0 to array.size(liq_top) - 1
        liq_d   = array.get(liq_dir, i)
        liq_mid = (array.get(liq_top, i) + array.get(liq_bot, i)) / 2.0
        if not array.get(liq_mitig, i)
            dist = math.abs(close - liq_mid) / math.max(atr14, 0.0001)
            if dist <= i_dist_cap_atr
                dist_factor = math.max(0.5, math.min(2.0, 1.0 / (1.0 + dist * 0.1)))
                age_factor  = math.exp(-float(bar_index - array.get(liq_bar, i)) / float(i_age_halflife))
                contrib     = i_w_liq * dist_factor * age_factor
                // BSL encima bloquea longs (el precio lo irá a buscar pero hay riesgo de reversión)
                if liq_d == 1 and liq_mid > close
                    debt_long_raw += contrib
                // SSL debajo bloquea shorts
                else if liq_d == -1 and liq_mid < close
                    debt_short_raw += contrib

// Normalizar (cap empírico 120)
debt_long  = math.min(100.0, debt_long_raw  / 120.0 * 100.0)
debt_short = math.min(100.0, debt_short_raw / 120.0 * 100.0)

// ============================================================
// SEÑAL FINAL
// ============================================================
var string signal_long  = "GREEN"
var string signal_short = "GREEN"
signal_long  := debt_long  >= i_thresh_blocked ? "BLOCKED" : debt_long  >= i_thresh_caution ? "CAUTION" : "GREEN"
signal_short := debt_short >= i_thresh_blocked ? "BLOCKED" : debt_short >= i_thresh_caution ? "CAUTION" : "GREEN"

// ============================================================
// GRILLA HORIZONTAL — actualizar posiciones cada barra
// ============================================================
if i_show_grid and array.size(grid_lines) > 0
    for k = 0 to 19
        y_level = close + (k - 10) * atr14 * 0.5
        l = array.get(grid_lines, k)
        line.set_x1(l, bar_index - 50)
        line.set_x2(l, bar_index + 50)
        line.set_y1(l, y_level)
        line.set_y2(l, y_level)

// ============================================================
// BUSCAR ESTRUCTURA MITIGACIÓN MÁS CERCANA (para HUD + Next-piece)
// Pine v6: funciones no pueden mutar vars del scope padre vía :=,
// usamos arrays de 1 elemento (paso por referencia).
// ============================================================
var array<float>  nearest_dist_arr  = array.from(999999.0)
var array<float>  nearest_price_arr = array.from(float(na))
var array<string> nearest_name_arr  = array.from("---")
var array<color>  nearest_color_arr = array.from(C_TEXT)
var array<string> nearest_type_arr  = array.from("")

// Reset cada barra
array.set(nearest_dist_arr,  0, 999999.0)
array.set(nearest_price_arr, 0, na)
array.set(nearest_name_arr,  0, "---")
array.set(nearest_color_arr, 0, C_TEXT)
array.set(nearest_type_arr,  0, "")

check_nearest(tops, bots, mitigs, type_name, piece_color) =>
    if array.size(tops) > 0
        for i = 0 to array.size(tops) - 1
            if not array.get(mitigs, i)
                mid  = (array.get(tops, i) + array.get(bots, i)) / 2.0
                dist = math.abs(close - mid)
                if dist < array.get(nearest_dist_arr, 0)
                    array.set(nearest_dist_arr,  0, dist)
                    array.set(nearest_price_arr, 0, mid)
                    array.set(nearest_name_arr,  0, type_name)
                    array.set(nearest_color_arr, 0, piece_color)
                    array.set(nearest_type_arr,  0, type_name)

check_nearest(fvg_top, fvg_bot, fvg_mitig, "FVG",     C_FVG_BULL)
check_nearest(ob_top,  ob_bot,  ob_mitig,  "OB",      C_OB_BULL)
check_nearest(brk_top, brk_bot, brk_mitig, "BREAKER", C_BREAKER_BULL)
check_nearest(bpr_top, bpr_bot, bpr_mitig, "BPR",     C_BPR)
check_nearest(liq_top, liq_bot, liq_mitig, "LIQ",     C_LIQ_HIGH)

// Aliases locales para el resto del código
nearest_dist  = array.get(nearest_dist_arr,  0)
nearest_price = array.get(nearest_price_arr, 0)
nearest_name  = array.get(nearest_name_arr,  0)
nearest_color = array.get(nearest_color_arr, 0)
nearest_type  = array.get(nearest_type_arr,  0)

// ============================================================
// DETECCIÓN "LÍNEA COMPLETA" TETRIS — solo en barras confirmadas
// ============================================================
if barstate.isconfirmed
    row_min = close - 10.0 * atr14 * 0.5
    cells_per_row = array.new<int>(20, 0)

    count_cells_into(fvg_top, fvg_bot, fvg_mitig, cells_per_row, row_min)
    count_cells_into(ob_top,  ob_bot,  ob_mitig,  cells_per_row, row_min)
    count_cells_into(brk_top, brk_bot, brk_mitig, cells_per_row, row_min)
    count_cells_into(bpr_top, bpr_bot, bpr_mitig, cells_per_row, row_min)
    count_cells_into(liq_top, liq_bot, liq_mitig, cells_per_row, row_min)

    for row_idx = 0 to 19
        if array.get(cells_per_row, row_idx) >= 7
            lines_cleared += 1
            blink_until   := bar_index + 3

// ============================================================
// RENDER GAME BOARD LATERAL — solo en última barra (rendimiento)
// ============================================================
if barstate.islast and i_show_board and not na(tbl_board)
    row_min_board = close - 10.0 * atr14 * 0.5

    // Header
    table.cell(tbl_board, 0, 0, "TETRIS-ICT",
               text_color=C_TEXT, text_size=size.small,
               bgcolor=color.new(C_BG, 20), text_halign=text.align_center,
               width=board_cell_w, height=board_cell_h)
    for c = 1 to 11
        table.merge_cells(tbl_board, 0, 0, 11, 0)
        break

    // Filas 1-20: celdas de precio
    for row_idx = 0 to 19
        row_price = row_min_board + (19 - row_idx) * atr14 * 0.5
        price_str = str.tostring(math.round(row_price, 2))

        // Columna precio (col 0)
        table.cell(tbl_board, 0, row_idx + 1, price_str,
                   text_color=color.new(C_TEXT, 40), text_size=size.tiny,
                   bgcolor=C_BG, text_halign=text.align_right,
                   width=board_cell_w * 1.5, height=board_cell_h)

        // Columna leyenda (col 11)
        table.cell(tbl_board, 11, row_idx + 1, "",
                   bgcolor=C_BG, text_size=size.tiny,
                   width=board_cell_w * 0.3, height=board_cell_h)

        // Celdas de juego (cols 1-10)
        cell_color  = C_BG
        cell_text   = ""
        cell_tcolor = C_BG
        has_struct  = false

        // Verificar qué estructura ocupa esta fila
        if array.size(fvg_top) > 0
            for i = 0 to array.size(fvg_top) - 1
                if not array.get(fvg_mitig, i)
                    t = array.get(fvg_top, i)
                    b = array.get(fvg_bot, i)
                    if row_price <= t and row_price >= b
                        cell_color := array.get(fvg_dir, i) == 1 ? color.new(C_FVG_BULL, 50) : color.new(C_FVG_BEAR, 50)
                        cell_text  := "I"
                        cell_tcolor := color.white
                        has_struct := true
                        break

        if not has_struct and array.size(ob_top) > 0
            for i = 0 to array.size(ob_top) - 1
                if not array.get(ob_mitig, i)
                    t = array.get(ob_top, i)
                    b = array.get(ob_bot, i)
                    if row_price <= t and row_price >= b
                        cell_color := array.get(ob_dir, i) == 1 ? color.new(C_OB_BULL, 50) : color.new(C_OB_BEAR, 50)
                        cell_text  := "O"
                        cell_tcolor := color.white
                        has_struct := true
                        break

        if not has_struct and array.size(brk_top) > 0
            for i = 0 to array.size(brk_top) - 1
                if not array.get(brk_mitig, i)
                    t = array.get(brk_top, i)
                    b = array.get(brk_bot, i)
                    if row_price <= t and row_price >= b
                        cell_color := array.get(brk_dir, i) == 1 ? color.new(C_BREAKER_BULL, 50) : color.new(C_BREAKER_BEAR, 50)
                        cell_text  := "L"
                        cell_tcolor := color.white
                        has_struct := true
                        break

        if not has_struct and array.size(bpr_top) > 0
            for i = 0 to array.size(bpr_top) - 1
                if not array.get(bpr_mitig, i)
                    t = array.get(bpr_top, i)
                    b = array.get(bpr_bot, i)
                    if row_price <= t and row_price >= b
                        cell_color := color.new(C_BPR, 50)
                        cell_text  := "S"
                        cell_tcolor := color.white
                        has_struct := true
                        break

        if not has_struct and array.size(liq_top) > 0
            for i = 0 to array.size(liq_top) - 1
                if not array.get(liq_mitig, i)
                    t = array.get(liq_top, i)
                    b = array.get(liq_bot, i)
                    if row_price <= t and row_price >= b
                        cell_color := array.get(liq_dir, i) == 1 ? color.new(C_LIQ_HIGH, 50) : color.new(C_LIQ_LOW, 50)
                        cell_text  := "T"
                        cell_tcolor := color.white
                        has_struct := true
                        break

        // Efecto blink en líneas completas
        if blink_until >= bar_index and has_struct
            cell_color := bar_index % 2 == 0 ? color.white : color.new(color.white, 60)

        // Render celdas 1-10 con la misma info
        for col_idx = 1 to 10
            table.cell(tbl_board, col_idx, row_idx + 1, col_idx == 5 ? cell_text : "",
                       text_color=cell_tcolor, text_size=size.tiny,
                       bgcolor=has_struct ? cell_color : color.new(C_GRID, 80),
                       text_halign=text.align_center,
                       width=board_cell_w, height=board_cell_h)

    // Footer
    table.cell(tbl_board, 0, 21,
               "LINES: " + str.tostring(lines_cleared) + "  |  bolivarbolsa.com",
               text_color=C_TEXT, text_size=size.tiny,
               bgcolor=color.new(C_BG, 20), text_halign=text.align_center,
               width=board_cell_w, height=board_cell_h)
    for c = 1 to 11
        table.merge_cells(tbl_board, 0, 21, 11, 21)
        break

// ============================================================
// RENDER HUD — top-right
// ============================================================
if barstate.islast and i_show_hud and not na(tbl_hud)
    // Row 0: Título
    table.cell(tbl_hud, 0, 0, "TETRIS-ICT",
               text_color=C_TEXT, text_size=size.normal,
               bgcolor=color.new(C_BG, 10), text_halign=text.align_center)
    table.cell(tbl_hud, 1, 0, "bolivarbolsa.com",
               text_color=color.new(C_TEXT, 40), text_size=size.tiny,
               bgcolor=color.new(C_BG, 10), text_halign=text.align_center)

    // Row 1: Debt Long
    dl_str = str.tostring(math.round(debt_long)) + "%  " + score_bar(debt_long)
    table.cell(tbl_hud, 0, 1, "DEBT ↑ LONG",
               text_color=color.new(C_TEXT, 20), text_size=size.small,
               bgcolor=C_BG, text_halign=text.align_left)
    table.cell(tbl_hud, 1, 1, dl_str,
               text_color=sig_color(signal_long), text_size=size.small,
               bgcolor=C_BG, text_halign=text.align_right)

    // Row 2: Debt Short
    ds_str = str.tostring(math.round(debt_short)) + "%  " + score_bar(debt_short)
    table.cell(tbl_hud, 0, 2, "DEBT ↓ SHORT",
               text_color=color.new(C_TEXT, 20), text_size=size.small,
               bgcolor=C_BG, text_halign=text.align_left)
    table.cell(tbl_hud, 1, 2, ds_str,
               text_color=sig_color(signal_short), text_size=size.small,
               bgcolor=C_BG, text_halign=text.align_right)

    // Row 3: Signal Long / Short
    blink_blocked_l = signal_long  == "BLOCKED" and bar_index % 2 == 0
    blink_blocked_s = signal_short == "BLOCKED" and bar_index % 2 == 0
    sig_l_bg = blink_blocked_l ? C_BLOCKED : sig_color(signal_long)
    sig_s_bg = blink_blocked_s ? C_BLOCKED : sig_color(signal_short)
    table.cell(tbl_hud, 0, 3, "SIG ↑  " + signal_long,
               text_color=color.black, text_size=size.small,
               bgcolor=color.new(sig_l_bg, 20), text_halign=text.align_center)
    table.cell(tbl_hud, 1, 3, "SIG ↓  " + signal_short,
               text_color=color.black, text_size=size.small,
               bgcolor=color.new(sig_s_bg, 20), text_halign=text.align_center)

    // Row 4: Next mitigación
    next_str = na(nearest_price) ? "---" : nearest_name + " @ " + str.tostring(math.round(nearest_price, 2))
    table.cell(tbl_hud, 0, 4, "NEXT MITIG",
               text_color=color.new(C_TEXT, 20), text_size=size.small,
               bgcolor=C_BG, text_halign=text.align_left)
    table.cell(tbl_hud, 1, 4, next_str,
               text_color=na(nearest_price) ? color.new(C_TEXT, 50) : nearest_color,
               text_size=size.small, bgcolor=C_BG, text_halign=text.align_right)

    // Row 5: Lines cleared
    table.cell(tbl_hud, 0, 5, "LINES CLEARED",
               text_color=color.new(C_TEXT, 20), text_size=size.small,
               bgcolor=C_BG, text_halign=text.align_left)
    table.cell(tbl_hud, 1, 5, str.tostring(lines_cleared),
               text_color=C_TEXT, text_size=size.normal,
               bgcolor=C_BG, text_halign=text.align_right)

// ============================================================
// RENDER NEXT-PIECE PREVIEW — top-left
// ============================================================
// Formas 4×4 para cada tipo de estructura
get_piece_shape(type_str) =>
    // Devuelve array de 16 ints (4x4), 1=celda activa
    shape = array.new<int>(16, 0)
    if type_str == "FVG"
        // I-piece: fila completa
        array.set(shape, 0, 1)
        array.set(shape, 1, 1)
        array.set(shape, 2, 1)
        array.set(shape, 3, 1)
    else if type_str == "OB"
        // O-piece: cuadrado 2x2
        array.set(shape, 0, 1)
        array.set(shape, 1, 1)
        array.set(shape, 4, 1)
        array.set(shape, 5, 1)
    else if type_str == "BREAKER"
        // L-piece: columna + esquina
        array.set(shape, 0, 1)
        array.set(shape, 4, 1)
        array.set(shape, 8, 1)
        array.set(shape, 9, 1)
    else if type_str == "BPR"
        // S-piece
        array.set(shape, 1, 1)
        array.set(shape, 2, 1)
        array.set(shape, 4, 1)
        array.set(shape, 5, 1)
    else if type_str == "LIQ"
        // T-piece
        array.set(shape, 0, 1)
        array.set(shape, 1, 1)
        array.set(shape, 2, 1)
        array.set(shape, 5, 1)
    shape

if barstate.islast and i_show_next and not na(tbl_next)
    // Row 0: Header
    table.cell(tbl_next, 0, 0, "NEXT", text_color=C_LIQ_HIGH,
               text_size=size.small, bgcolor=color.new(C_BG, 10),
               text_halign=text.align_center)
    for c = 1 to 5
        table.merge_cells(tbl_next, 0, 0, 5, 0)
        break

    // Rows 1-4: grilla 4×4 de la pieza
    piece_shape = get_piece_shape(nearest_name)
    piece_col   = nearest_color

    for pr = 0 to 3
        for pc = 0 to 3
            cell_val = array.get(piece_shape, pr * 4 + pc)
            table.cell(tbl_next, pc + 1, pr + 1, "",
                       bgcolor=cell_val == 1 ? color.new(piece_col, 30) : color.new(C_BG, 80),
                       text_size=size.tiny)

    // Columna 0 (margen izq) y col 5 (margen der) — vacías
    for pr = 1 to 4
        table.cell(tbl_next, 0, pr, "", bgcolor=C_BG)
        table.cell(tbl_next, 5, pr, "", bgcolor=C_BG)

    // Row 5: Nombre estructura
    table.cell(tbl_next, 0, 5, nearest_name,
               text_color=nearest_color, text_size=size.small,
               bgcolor=C_BG, text_halign=text.align_center)
    for c = 1 to 5
        table.merge_cells(tbl_next, 0, 5, 5, 5)
        break

    // Row 6: Precio
    price_str_next = na(nearest_price) ? "---" : str.tostring(math.round(nearest_price, 2))
    table.cell(tbl_next, 0, 6, price_str_next,
               text_color=color.new(nearest_color, 20), text_size=size.small,
               bgcolor=C_BG, text_halign=text.align_center)
    for c = 1 to 5
        table.merge_cells(tbl_next, 0, 6, 5, 6)
        break

    // Row 7: vacía
    table.cell(tbl_next, 0, 7, "", bgcolor=C_BG)
    for c = 1 to 5
        table.merge_cells(tbl_next, 0, 7, 5, 7)
        break

// ============================================================
// PLOTS SILENCIOSOS — para alertas JSON vía {{plot()}}
// ============================================================
plot(debt_long,     "Debt Long",     display=display.none)
plot(debt_short,    "Debt Short",    display=display.none)
plot(lines_cleared, "Lines Cleared", display=display.none)

// ============================================================
// ALERTCONDITIONS — 5 condiciones de alerta
// ============================================================

// 1. Blocked Long — transición a BLOCKED
alertcondition(
     signal_long == "BLOCKED" and signal_long[1] != "BLOCKED",
     title="Blocked Long",
     message='{"event":"TETRIS_BLOCKED_LONG","pair":"{{ticker}}","tf":"{{interval}}","debt":{{plot("Debt Long")}},"source":"bolivarbolsa"}')

// 2. Green Long — transición a GREEN
alertcondition(
     signal_long == "GREEN" and signal_long[1] != "GREEN",
     title="Green Long",
     message='{"event":"TETRIS_GREEN_LONG","pair":"{{ticker}}","tf":"{{interval}}","debt":{{plot("Debt Long")}},"source":"bolivarbolsa"}')

// 3. Green Short — transición a GREEN (short)
alertcondition(
     signal_short == "GREEN" and signal_short[1] != "GREEN",
     title="Green Short",
     message='{"event":"TETRIS_GREEN_SHORT","pair":"{{ticker}}","tf":"{{interval}}","debt":{{plot("Debt Short")}},"source":"bolivarbolsa"}')

// 4. Mitigación detectada
alertcondition(
     mitig_event,
     title="Mitigación ICT",
     message='{"event":"TETRIS_MITIGATED","pair":"{{ticker}}","tf":"{{interval}}","source":"bolivarbolsa"}')

// 5. Línea completa — Level Up
alertcondition(
     lines_cleared > lines_cleared[1],
     title="Line Cleared (Level Up)",
     message='{"event":"TETRIS_LEVEL_UP","pair":"{{ticker}}","tf":"{{interval}}","lines":{{plot("Lines Cleared")}},"source":"bolivarbolsa"}')

Questions fréquentes

Est-ce gratuit ?

Oui, entièrement gratuit. Sans abonnement ni paiement. Il vous suffit d'un compte TradingView (le plan gratuit suffit).

Sur quelles unités de temps fonctionne-t-il le mieux ?

Sur toutes les unités de temps. La méthodologie ICT/SMC de Bolívar Bolsa utilise H4 pour le contexte et M15/M5 pour la structure de détail.

Fonctionne-t-il sur XAUUSD et le Forex ?

Oui. Il détecte les déséquilibres structurels sur n'importe quel instrument TradingView : XAUUSD, devises, indices et cryptomonnaies. La logique ICT est universelle.

Ai-je besoin d'un compte TradingView payant ?

Non. Le plan gratuit permet d'exécuter des indicateurs Pine Script v6 sans restriction.

Outil exclusivement éducatif destiné à l'étude des concepts ICT/SMC ; ne constitue pas un conseil financier et ne garantit pas les résultats. Le trading implique un risque de perte en capital.

Apprenez la méthodologie complète. Tetris ICT est un point d'entrée visuel, pas le système complet. Comprenez pourquoi le prix respecte chaque dette structurelle dans l'École de trading gratuite, ou rejoignez la communauté Telegram.