//@version=6
// ════════════════════════════════════════════════════════════════════════════════
// Smart Zone Retest — Rejection & CHoCH Confirmation – v2
// Versi Bahasa Indonesia
// Implementasi orisinal. Membangun zona berbasis struktur dan mencari
// retest terkonfirmasi (candle rejection dan/atau CHoCH) sebelum sinyal entry.
//
// Alur
// ────
// 1. Struktur market dari swing pivot terkonfirmasi (Pivot Period Zone).
// 2. Break struktur + spike impulsif (min. candle searah berurutan + momentum vs ATR)
// membuat zona: distal = Origin Pivot, proximal = tepi Break Candle.
// 3. Sinyal = harga KEMBALI ke zona, plus konfirmasi yang diaktifkan:
// • Candle rejection (Pin Bar / Engulfing) pada bar sentuh atau setelahnya
// • CHoCH searah trade pada timeframe pilihan user, setelah sentuhan
// 4. SL = distal ± threshold. Hingga 3 tahap take-profit dengan partial close dan
// opsi break-even setelah TP1.
// 5. Setiap level menampilkan harga, % jarak, R-multiple dan P/L hipotetis berdasarkan
// input balance akun dan risk %.
//
// NON-REPAINTING:
// • Semua logika sinyal/zona berjalan hanya pada bar chart TERKONFIRMASI.
// • CHoCH pada TF lebih rendah dibaca dari intrabar yang sudah tertutup penuh (security_lower_tf).
// • CHoCH pada TF lebih tinggi menggunakan bar HTF SEBELUMNYA yang SUDAH SELESAI (lookahead + [1]).
// ════════════════════════════════════════════════════════════════════════════════
indicator("GoodSmart Retest", shorttitle = "GoodSmart", overlay = true, max_boxes_count = 500, max_lines_count = 500, max_labels_count = 500, max_bars_back = 500)
// ═══════════════════════════════════ INPUT ══════════════════════════════════════
const string G0 = "PRESET INSTRUMEN"
const string G1 = "FILTER SPIKE | PERGERAKAN"
const string G2 = "MANAJEMEN POSISI"
const string GT = "TAKE PROFIT | MULTI-TAHAP"
const string GR = "RISIKO & BALANCE (P/L hipotetis)"
const string GRJ = "KONFIRMASI ENTRY | REJECTION"
const string GCH = "KONFIRMASI ENTRY | CHOCH"
const string G3 = "PENGATURAN TAMPILAN"
const string G4 = "ALERT"
const string G5 = "STYLE"
const string G6 = "LANJUTAN (extra – tidak ada di UI asli)"
// ── Preset instrumen
string presetIn = input.string("Manual (gunakan input di bawah)", "Preset Instrumen", options = ["Manual (gunakan input di bawah)", "Gold (XAUUSD)", "Forex Majors", "JPY Pairs", "Indices", "Crypto (BTC/ETH majors)"], group = G0, tooltip = "Mengisi otomatis Minimum Spike Bars, Movement Power, Pivot Period Zone, Stop-Loss Threshold (dipaksa ke ATR Multiple), Risk-Reward/TP1-3, Wide Zone Filter, Zone Max Age dan Max Impulse Length untuk kategori di bawah, disesuaikan dengan timeframe chart saat ini (bucket: <=5m, <=15m, <=1h, <=4h, >4h).\nMulti-Stage Take Profit Close % dibagi dan setiap input di RISIKO & BALANCE, KONFIRMASI ENTRY dan TAMPILAN tetap seperti yang Anda atur.\nPilih \"Manual\" untuk mengontrol semua input sendiri.")
// ── Filter spike | Pergerakan
int minSpike = input.int(4, "Minimum Candle Spike", minval = 1, maxval = 30, group = G1, tooltip = "Jumlah minimum candle searah BERURUTAN di dalam leg impulsif (origin pivot → break).")
bool useMove = input.bool(true, "Kekuatan Pergerakan", inline = "mp", group = G1)
float movePower = input.float(4.5, "", minval = 0.1, step = 0.1, inline = "mp", group = G1, tooltip = "Filter momentum. Range impulsif (origin extreme → leg extreme) harus >= nilai ini x ATR.")
int pivotLen = input.int(5, "Periode Pivot Zona", minval = 1, maxval = 50, group = G1, tooltip = "Bar kiri/kanan yang digunakan untuk konfirmasi Swing High / Swing Low (struktur market).")
// ── Manajemen posisi
bool useSL = input.bool(true, "Threshold Stop-Loss", inline = "sl", group = G2)
float slThr = input.float(0.2, "", minval = 0.0, step = 0.05, inline = "sl", group = G2, tooltip = "Buffer ditambahkan di luar tepi distal. Unit dipilih di grup LANJUTAN. Jika dimatikan, SL = tepi distal persis.")
float rr = input.float(2.0, "Risk-Reward Ratio (TP tunggal)", minval = 0.1, step = 0.1, group = G2, tooltip = "Hanya digunakan saat Multi-Tahap Take Profit OFF.")
bool useWide = input.bool(true, "Filter Zona Lebar", inline = "wz", group = G2)
float maxZoneAtr = input.float(2.5, "", minval = 0.1, step = 0.1, inline = "wz", group = G2, tooltip = "Zona yang lebih tinggi dari sekian ATR akan diabaikan.")
// ── Take profit | multi-tahap
bool useMulti = input.bool(true, "Multi-Tahap Take Profit", group = GT, tooltip = "ON: tiga target dengan partial close. OFF: target tunggal pada Risk-Reward Ratio di atas.\nTahap dengan Close % = 0 dinonaktifkan.")
float tp1R = input.float(1.0, "TP1 Reward (R)", minval = 0.1, step = 0.1, inline = "t1", group = GT)
float tp1P = input.float(40.0, "Close %", minval = 0.0, maxval = 100.0, step = 5.0, inline = "t1", group = GT)
float tp2R = input.float(2.0, "TP2 Reward (R)", minval = 0.1, step = 0.1, inline = "t2", group = GT)
float tp2P = input.float(30.0, "Close %", minval = 0.0, maxval = 100.0, step = 5.0, inline = "t2", group = GT)
float tp3R = input.float(3.0, "TP3 Reward (R)", minval = 0.1, step = 0.1, inline = "t3", group = GT)
float tp3P = input.float(30.0, "Close %", minval = 0.0, maxval = 100.0, step = 5.0, inline = "t3", group = GT, tooltip = "Nilai Close % dinormalisasi, jadi tidak harus berjumlah 100.")
bool beAfterTP1 = input.bool(false, "Pindahkan SL ke Break-even setelah TP1", group = GT, tooltip = "OFF (default): stop tetap di level aslinya selama trade.\nON: setelah TP1 stop pindah ke harga entry; garis SL ASLI tetap di tempatnya dan garis SL→BE putus-putus baru dimulai dari bar tersebut.")
// ── Risiko & balance
float acctBal = input.float(10000.0, "Balance Akun", minval = 1.0, step = 100.0, group = GR)
float riskPct = input.float(1.0, "Risk % per Trade", minval = 0.01, maxval = 100.0, step = 0.1, group = GR, tooltip = "P/L hipotetis pada setiap level = Balance x Risk% x R-multiple x porsi posisi yang ditutup.")
// ── Konfirmasi entry | Rejection
bool useReject = input.bool(true, "Konfirmasi Rejection", group = GRJ, tooltip = "Candle sinyal harus menyentuh zona, close di luar tepi proximal DAN merupakan candle rejection (tipe di bawah).")
bool usePin = input.bool(true, "Pin Bar", inline = "rj", group = GRJ)
bool useEngulf = input.bool(true, "Engulfing", inline = "rj", group = GRJ, tooltip = "Jika kedua pola dimatikan, candle apapun diterima.")
float pinRatio = input.float(2.0, "Pin Bar: wick >= N x body", minval = 0.5, step = 0.25, group = GRJ, tooltip = "Wick rejection harus minimal N kali body dan minimal 50% dari range candle.")
// ── Konfirmasi entry | CHoCH
bool useChoch = input.bool(false, "Konfirmasi CHoCH", group = GCH, tooltip = "Setelah harga masuk zona, CHoCH searah trade harus terjadi pada timeframe di bawah sebelum sinyal diizinkan.")
string chochTF = input.timeframe("15", "Timeframe CHoCH", group = GCH, tooltip = "TF lebih rendah (mis. 1/5/15) = konfirmasi LTF klasik. TF sama atau lebih tinggi juga didukung.")
int chLen = input.int(3, "Periode Pivot CHoCH", minval = 1, maxval = 20, group = GCH, tooltip = "Bar kiri/kanan untuk deteksi swing pada timeframe CHoCH.")
bool autoChoch = input.bool(true, "Auto Timeframe CHoCH (dari preset)", group = GCH, tooltip = "ON (default): saat preset di atas aktif, Timeframe CHoCH dipilih otomatis dari preset (kira-kira 1/4-1/5 dari timeframe chart).\nOFF: selalu gunakan field Timeframe CHoCH secara manual, meski preset dipilih.")
// ── Tampilan
string dispMode = input.string("Signal", "Mode Tampilan", options = ["Signal", "Setup"], group = G3, tooltip = "Signal: zona + sinyal Long/Short setelah retest-dan-exit.\nSetup: zona + rencana Entry/SL/TP (limit order di tepi proximal) begitu zona dibuat.")
bool showEntry = input.bool(true, "Tampilkan Level Entry", group = G3)
bool onlyLast = input.bool(false, "Hanya Tampilkan Posisi Terakhir", group = G3)
int drawW = input.int(2, "Lebar Gambar Setup", minval = 1, maxval = 5, group = G3, tooltip = "Lebar garis untuk border zona dan level Entry / SL / TP.")
string lblSizeIn = input.string("Large", "Ukuran Label Level", options = ["Small", "Normal", "Large", "Huge"], group = G3, tooltip = "Ukuran teks label info Entry / SL / TP dan label hasil trade.")
string lblFontIn = input.string("Monospace", "Font Label Level", options = ["Monospace", "Default"], group = G3, tooltip = "Monospace menjaga harga dan persentase tetap sejajar dan lebih mudah dibaca.")
bool lblBold = input.bool(true, "Label Level Tebal", group = G3)
// ── Alert
bool useAlert = input.bool(true, "Alert", group = G4, tooltip = "Kirim pesan alert() pada sinyal baru (dan setup saat Mode Tampilan = Setup). Buat alert dengan kondisi: TRH → Any alert() function call.")
// ── Style
color bullClr = input.color(color.new(#4caf50, 78), "Long / Zona Demand", group = G5)
color bearClr = input.color(color.new(#f23645, 78), "Short / Zona Supply", group = G5)
color sigBull = input.color(#1b8a2f, "Sinyal Long", group = G5)
color sigBear = input.color(#a3151a, "Sinyal Short", group = G5)
// ── Lanjutan (extra)
int atrLen = input.int(14, "Panjang ATR", minval = 1, group = G6)
int maxImp = input.int(15, "Max Panjang Impuls (bar)", minval = 3, maxval = 100, group = G6, tooltip = "Jumlah maksimum bar antara origin pivot dan akhir spike.")
int maxAge = input.int(300, "Max Umur Zona (bar)", minval = 10, group = G6, tooltip = "Zona yang tidak pernah di-retest akan dibuang setelah sekian bar.")
int maxZones = input.int(30, "Max Zona Aktif", minval = 1, maxval = 100, group = G6)
string slUnit = input.string("% of Price", "Unit Threshold Stop-Loss", options = ["% of Price", "ATR Multiple", "Zone Height Multiple"], group = G6)
bool showStats = input.bool(false, "Tampilkan Tabel Statistik", group = G6)
color cEntry = color.new(#787b86, 0)
color cSL = color.new(#f23645, 0)
color cTP = color.new(#089981, 0)
// Bucket timeframe chart untuk memilih baris dari tabel preset.
f_bucket() =>
int sec = timeframe.in_seconds()
string b = sec <= 300 ? "M5" : sec <= 900 ? "M15" : sec <= 3600 ? "H1" : sec <= 14400 ? "H4" : "D1"
b
// Tabel lookup preset: [Minimum Candle Spike, Kekuatan Pergerakan, Periode Pivot Zona, Threshold SL (x ATR),
// Risk-Reward/TP1 (R), Filter Zona Lebar (x ATR), Max Umur Zona, Max Panjang Impuls, Timeframe CHoCH]
f_preset(string cat, string b) =>
int pMinSpike = minSpike
float pPower = movePower
int pPivot = pivotLen
float pSlAtr = 0.3
float pRR = rr
float pWide = maxZoneAtr
int pAge = maxAge
int pImp = maxImp
string pChoch = chochTF
if cat == "Gold (XAUUSD)"
switch b
"M5" =>
pMinSpike := 3
pPower := 3.5
pPivot := 10
pSlAtr := 0.3
pRR := 1.3
pWide := 1.8
pAge := 60
pImp := 8
pChoch := "1"
"M15" =>
pMinSpike := 4
pPower := 4.0
pPivot := 7
pSlAtr := 0.3
pRR := 1.5
pWide := 2.0
pAge := 100
pImp := 10
pChoch := "5"
"H1" =>
pMinSpike := 4
pPower := 4.5
pPivot := 5
pSlAtr := 0.3
pRR := 1.0
pWide := 2.2
pAge := 150
pImp := 12
pChoch := "15"
"H4" =>
pMinSpike := 5
pPower := 5.5
pPivot := 4
pSlAtr := 0.35
pRR := 1.0
pWide := 2.5
pAge := 200
pImp := 15
pChoch := "60"
=>
pMinSpike := 5
pPower := 6.0
pPivot := 3
pSlAtr := 0.4
pRR := 1.0
pWide := 3.0
pAge := 250
pImp := 18
pChoch := "240"
else if cat == "Forex Majors"
switch b
"M5" =>
pMinSpike := 4
pPower := 3.0
pPivot := 10
pSlAtr := 0.25
pRR := 1.5
pWide := 1.5
pAge := 80
pImp := 10
pChoch := "1"
"M15" =>
pMinSpike := 4
pPower := 3.5
pPivot := 8
pSlAtr := 0.25
pRR := 1.5
pWide := 1.8
pAge := 120
pImp := 12
pChoch := "5"
"H1" =>
pMinSpike := 5
pPower := 4.0
pPivot := 6
pSlAtr := 0.3
pRR := 1.5
pWide := 2.0
pAge := 180
pImp := 15
pChoch := "15"
"H4" =>
pMinSpike := 5
pPower := 4.5
pPivot := 4
pSlAtr := 0.3
pRR := 1.0
pWide := 2.2
pAge := 220
pImp := 18
pChoch := "60"
=>
pMinSpike := 5
pPower := 5.0
pPivot := 3
pSlAtr := 0.35
pRR := 1.0
pWide := 2.5
pAge := 260
pImp := 20
pChoch := "240"
else if cat == "JPY Pairs"
switch b
"M5" =>
pMinSpike := 4
pPower := 3.5
pPivot := 9
pSlAtr := 0.25
pRR := 1.3
pWide := 1.8
pAge := 70
pImp := 9
pChoch := "1"
"M15" =>
pMinSpike := 4
pPower := 4.0
pPivot := 7
pSlAtr := 0.3
pRR := 1.5
pWide := 2.0
pAge := 110
pImp := 11
pChoch := "5"
"H1" =>
pMinSpike := 4
pPower := 4.5
pPivot := 5
pSlAtr := 0.3
pRR := 1.5
pWide := 2.2
pAge := 160
pImp := 14
pChoch := "15"
"H4" =>
pMinSpike := 5
pPower := 5.0
pPivot := 4
pSlAtr := 0.35
pRR := 1.0
pWide := 2.5
pAge := 200
pImp := 16
pChoch := "60"
=>
pMinSpike := 5
pPower := 5.5
pPivot := 3
pSlAtr := 0.4
pRR := 1.0
pWide := 2.8
pAge := 250
pImp := 18
pChoch := "240"
else if cat == "Indices"
switch b
"M5" =>
pMinSpike := 3
pPower := 4.0
pPivot := 10
pSlAtr := 0.3
pRR := 1.3
pWide := 2.0
pAge := 50
pImp := 8
pChoch := "1"
"M15" =>
pMinSpike := 4
pPower := 4.5
pPivot := 7
pSlAtr := 0.35
pRR := 1.5
pWide := 2.2
pAge := 80
pImp := 10
pChoch := "5"
"H1" =>
pMinSpike := 4
pPower := 5.0
pPivot := 5
pSlAtr := 0.35
pRR := 1.5
pWide := 2.5
pAge := 130
pImp := 12
pChoch := "15"
"H4" =>
pMinSpike := 5
pPower := 5.5
pPivot := 4
pSlAtr := 0.4
pRR := 1.0
pWide := 3.0
pAge := 180
pImp := 15
pChoch := "60"
=>
pMinSpike := 5
pPower := 6.0
pPivot := 3
pSlAtr := 0.45
pRR := 1.0
pWide := 3.5
pAge := 220
pImp := 18
pChoch := "240"
else if cat == "Crypto (BTC/ETH majors)"
switch b
"M5" =>
pMinSpike := 3
pPower := 4.5
pPivot := 10
pSlAtr := 0.4
pRR := 1.3
pWide := 2.2
pAge := 50
pImp := 8
pChoch := "1"
"M15" =>
pMinSpike := 4
pPower := 5.0
pPivot := 7
pSlAtr := 0.4
pRR := 1.5
pWide := 2.5
pAge := 90
pImp := 10
pChoch := "5"
"H1" =>
pMinSpike := 4
pPower := 5.5
pPivot := 5
pSlAtr := 0.45
pRR := 1.5
pWide := 2.8
pAge := 140
pImp := 12
pChoch := "15"
"H4" =>
pMinSpike := 5
pPower := 6.0
pPivot := 4
pSlAtr := 0.5
pRR := 1.0
pWide := 3.2
pAge := 190
pImp := 15
pChoch := "60"
=>
pMinSpike := 5
pPower := 6.5
pPivot := 3
pSlAtr := 0.55
pRR := 1.0
pWide := 3.8
pAge := 240
pImp := 18
pChoch := "240"
[pMinSpike, pPower, pPivot, pSlAtr, pRR, pWide, pAge, pImp, pChoch]
// ── Parameter efektif (sadar-preset) ───────────────────────────────────────────
bool usePreset = presetIn != "Manual (gunakan input di bawah)"
string bucket = f_bucket()
[pMinSpike, pPower, pPivot, pSlAtr, pRR, pWide, pAge, pImp, pChoch] = f_preset(presetIn, bucket)
int effMinSpike = usePreset ? pMinSpike : minSpike
float effMovePower = usePreset ? pPower : movePower
int effPivotLen = usePreset ? pPivot : pivotLen
float effSlThr = usePreset ? pSlAtr : slThr
string effSlUnit = usePreset ? "ATR Multiple" : slUnit
float effRR = usePreset ? pRR : rr
float effTp1R = usePreset ? pRR : tp1R
float effTp2R = usePreset ? pRR + 1.0 : tp2R
float effTp3R = usePreset ? pRR + 2.0 : tp3R
float effMaxZoneAtr = usePreset ? pWide : maxZoneAtr
int effMaxAge = usePreset ? pAge : maxAge
int effMaxImp = usePreset ? pImp : maxImp
string effChochTF = (usePreset and autoChoch) ? pChoch : chochTF
// ── Konfigurasi take-profit turunan (bobot dinormalisasi)
float pSum = tp1P + tp2P + tp3P
float w1 = useMulti ? (pSum > 0 ? tp1P / pSum : 1.0) : 1.0
float w2 = useMulti ? (pSum > 0 ? tp2P / pSum : 0.0) : 0.0
float w3 = useMulti ? (pSum > 0 ? tp3P / pSum : 0.0) : 0.0
float r1 = useMulti ? effTp1R : effRR
float r2 = effTp2R
float r3 = effTp3R
float riskAmt = acctBal * riskPct / 100.0
// ── Tampilan label
lblSize = lblSizeIn == "Small" ? size.small : (lblSizeIn == "Normal" ? size.normal : (lblSizeIn == "Large" ? size.large : size.huge))
lblFont = lblFontIn == "Monospace" ? font.family_monospace : font.family_default
// ═════════════════════════════════════ TIPE ═════════════════════════════════════
type Cand
int orig
float distal
float oOpp
float brkEdge
bool brkFound
int run
int maxRun
float ext
type Zone
int dir
float distal
float prox
float slp
int x0
int born
bool entered
bool choch
bool hidden
box bx
array<line> lns
array<label> lbs
type Trade
int dir
float entry
float sl
float slCur
float risk
float t1
float t2
float t3
bool h1
bool h2
bool h3
float rem
float rR
int start
bool active
bool hidden
array<line> lns
array<label> lbs
// ═════════════════════════════════════ STATE ════════════════════════════════════
var array<Zone> zones = array.new<Zone>()
var array<Zone> fin = array.new<Zone>()
var array<Trade> trades = array.new<Trade>()
var Cand cBull = na
var Cand cBear = na
var float swH = na
var int swHIdx = na
var bool swHUsed = true
var float swL = na
var int swLIdx = na
var bool swLUsed = true
var int nSig = 0
var int nWin = 0
var int nLoss = 0
var int nBE = 0
var float netR = 0.0
// ════════════════════════════════════ FUNGSI ════════════════════════════════════
f_buffer(float distal, float height, float atrV) =>
float b = 0.0
if useSL
if effSlUnit == "% of Price"
b := distal * effSlThr / 100.0
else if effSlUnit == "ATR Multiple"
b := atrV * effSlThr
else
b := height * effSlThr
b
f_sgn(float v) =>
(v >= 0 ? "+" : "") + str.tostring(v, "#.##")
f_money(float v) =>
f_sgn(v) + " " + syminfo.currency
f_px(float v) =>
str.tostring(v, format.mintick)
// Satu level = garis + label ditempatkan DI ATAS garis (body label duduk di atas harga)
f_addLvl(array<line> lns, array<label> lbs, int i, float y, string txt, color c, bool dashed, bool setup, bool above) =>
int x = bar_index
array.set(lns, i, line.new(x, y, x + 1, y, extend = setup ? extend.right : extend.none, color = c, style = dashed ? line.style_dashed : line.style_solid, width = drawW))
array.set(lbs, i, label.new(x + 1, y, txt, style = above ? label.style_label_lower_left : label.style_label_upper_left, color = color.new(color.white, 100), textcolor = c, size = lblSize, text_font_family = lblFont, text_formatting = lblBold ? text.format_bold : text.format_none))
true
f_tpTxt(string name, int dir, float entry, float price, float r, float w) =>
float pct = dir * (price - entry) / entry * 100.0
name + " " + f_px(price) + " | " + f_sgn(pct) + "% | " + str.tostring(r, "#.##") + "R | " + f_money(w * r * riskAmt) + (useMulti ? " (" + str.tostring(w * 100.0, "#") + "%)" : "")
// Membangun level Entry / SL / TP1-3. Slot: 0 entry, 1 SL, 2 TP1, 3 TP2, 4 TP3
f_mkLevels(int dir, float entry, float sl, bool setup) =>
float rk = math.abs(entry - sl)
array<line> lns = array.new<line>(5, na)
array<label> lbs = array.new<label>(5, na)
f_addLvl(lns, lbs, 0, entry, "Entry " + f_px(entry) + " | Risiko " + str.tostring(riskAmt, "#.##") + " " + syminfo.currency + " (" + str.tostring(riskPct, "#.##") + "%)", cEntry, true, setup, dir == 1)
float slPct = dir * (sl - entry) / entry * 100.0
f_addLvl(lns, lbs, 1, sl, "SL " + f_px(sl) + " | " + f_sgn(slPct) + "% | " + f_money(-riskAmt), cSL, false, setup, dir == -1)
if w1 > 0
float p1 = dir == 1 ? entry + r1 * rk : entry - r1 * rk
f_addLvl(lns, lbs, 2, p1, f_tpTxt(useMulti ? "TP1" : "TP", dir, entry, p1, r1, w1), cTP, false, setup, dir == 1)
if w2 > 0
float p2 = dir == 1 ? entry + r2 * rk : entry - r2 * rk
f_addLvl(lns, lbs, 3, p2, f_tpTxt("TP2", dir, entry, p2, r2, w2), cTP, false, setup, dir == 1)
if w3 > 0
float p3 = dir == 1 ? entry + r3 * rk : entry - r3 * rk
f_addLvl(lns, lbs, 4, p3, f_tpTxt("TP3", dir, entry, p3, r3, w3), cTP, false, setup, dir == 1)
[lns, lbs]
f_delArrays(array<line> lns, array<label> lbs) =>
if not na(lns)
for l in lns
if not na(l)
line.delete(l)
if not na(lbs)
for b in lbs
if not na(b)
label.delete(b)
true
f_dropLevels(Zone z) =>
f_delArrays(z.lns, z.lbs)
z.lns := na
z.lbs := na
true
f_delZone(Zone z) =>
if not z.hidden
if not na(z.bx)
box.delete(z.bx)
f_dropLevels(z)
z.hidden := true
true
f_delTrade(Trade t) =>
if not t.hidden
f_delArrays(t.lns, t.lbs)
t.lns := na
t.lbs := na
t.hidden := true
true
f_clearFinished() =>
for z in fin
f_delZone(z)
array.clear(fin)
for t in trades
f_delTrade(t)
true
f_addZone(int dir, float dist, float prox, int x0, float atrV) =>
float h = math.abs(prox - dist)
float buf = f_buffer(dist, h, atrV)
float sl = dir == 1 ? dist - buf : dist + buf
color base = dir == 1 ? bullClr : bearClr
Zone z = Zone.new(dir = dir, distal = dist, prox = prox, slp = sl, x0 = x0, born = bar_index, entered = false, choch = false, hidden = false)
z.bx := box.new(x0, math.max(dist, prox), bar_index, math.min(dist, prox), border_color = color.new(base, 70), border_width = drawW, bgcolor = base)
if dispMode == "Setup" and showEntry
[ln, lb] = f_mkLevels(dir, prox, sl, true)
z.lns := ln
z.lbs := lb
z
f_addTrade(int dir, float entry, float sl) =>
float rk = math.abs(entry - sl)
Trade t = Trade.new(dir = dir, entry = entry, sl = sl, slCur = sl, risk = rk, t1 = dir == 1 ? entry + r1 * rk : entry - r1 * rk, t2 = dir == 1 ? entry + r2 * rk : entry - r2 * rk, t3 = dir == 1 ? entry + r3 * rk : entry - r3 * rk, h1 = false, h2 = false, h3 = false, rem = 1.0, rR = 0.0, start = bar_index, active = true, hidden = false)
if showEntry
[ln, lb] = f_mkLevels(dir, entry, sl, false)
t.lns := ln
t.lbs := lb
t
// Pindahkan garis/label SL trade ke break-even
f_moveSLtoBE(Trade t) =>
t.slCur := t.entry
if not t.hidden and not na(t.lns)
line oldL = array.get(t.lns, 1)
label oldB = array.get(t.lbs, 1)
if not na(oldL)
// biarkan garis/label stop asli tidak tersentuh (dipindah ke slot "arsip" 5+),
// lalu mulai garis stop break-even putus-putus baru dari bar ini
array.push(t.lns, oldL)
array.push(t.lbs, oldB)
array.set(t.lns, 1, line.new(bar_index, t.entry, bar_index + 1, t.entry, color = cSL, style = line.style_dotted, width = drawW))
array.set(t.lbs, 1, label.new(bar_index + 1, t.entry, "SL → BE " + f_px(t.entry) + " | 0% | " + f_money(0.0), style = t.dir == -1 ? label.style_label_lower_left : label.style_label_upper_left, color = color.new(color.white, 100), textcolor = cSL, size = lblSize, text_font_family = lblFont, text_formatting = lblBold ? text.format_bold : text.format_none))
true
// Setiap level (SL=slot1, TP1=slot2, TP2=slot3, TP3=slot4) punya tepat SATU
// label persisten, dibuat sekali saat trade dibuka. Saat hit, kita perbarui
// label YANG SAMA teks/warnanya di tempat alih-alih membuat label kedua
// untuk garis yang sama — ini yang sebelumnya menyebabkan label duplikat.
f_updateLevelLabel(Trade t, int i, string txt, bool done) =>
if showEntry and not t.hidden and not na(t.lbs)
label lb = array.get(t.lbs, i)
if not na(lb)
label.set_text(lb, txt)
if done
label.set_textcolor(lb, t.rR > 0.05 ? cTP : (t.rR < -0.05 ? cSL : cEntry))
true
// Masukkan satu candle ke kandidat spike. Mengembalikan true selama spike masih berjalan.
f_step(Cand c, int dir, float o, float h, float l, float cl) =>
bool dirCandle = dir * (cl - o) > 0
c.run := dirCandle ? c.run + 1 : 0
c.maxRun := math.max(c.maxRun, c.run)
c.ext := dir == 1 ? math.max(c.ext, h) : math.min(c.ext, l)
if not c.brkFound and dir * (cl - c.oOpp) > 0
c.brkFound := true
c.brkEdge := dir == 1 ? l : h
dirCandle
// Validasi spike yang sudah selesai dan buat zona TRH jika semua filter lolos.
f_tryZone(Cand c, int dir, float atrV) =>
bool ok = false
if c.maxRun >= effMinSpike
float edge = c.brkFound ? c.brkEdge : c.oOpp
float prox = dir == 1 ? math.max(c.oOpp, edge) : math.min(c.oOpp, edge)
float zh = math.abs(prox - c.distal)
bool powerOK = not useMove or math.abs(c.ext - c.distal) >= effMovePower * atrV
bool wideOK = not useWide or zh <= effMaxZoneAtr * atrV
if powerOK and wideOK and zh > syminfo.mintick
if onlyLast and dispMode == "Setup"
for z in zones
f_delZone(z)
f_clearFinished()
if array.size(zones) >= maxZones
f_delZone(array.shift(zones))
array.push(zones, f_addZone(dir, c.distal, prox, c.orig, atrV))
ok := true
ok
// Detektor CHoCH pada timeframe apapun saat dievaluasi. Mengembalikan [bullish, bearish] sebagai 1.0 / 0.0.
// Bullish CHoCH = close menembus swing high terakhir saat tren sedang bearish (dan sebaliknya).
f_choch(int len) =>
float ph = ta.pivothigh(high, len, len)
float pl = ta.pivotlow(low, len, len)
var float sh = na
var float sl = na
var bool shUsed = true
var bool slUsed = true
var int trend = 0
if not na(ph)
sh := ph
shUsed := false
if not na(pl)
sl := pl
slUsed := false
float bull = 0.0
float bear = 0.0
if not na(sh) and not shUsed and close > sh
shUsed := true
if trend == -1
bull := 1.0
trend := 1
if not na(sl) and not slUsed and close < sl
slUsed := true
if trend == 1
bear := 1.0
trend := -1
[bull, bear]
// Sama, tapi tertunda satu bar → digunakan dengan lookahead_on pada timeframe LEBIH TINGGI (tanpa repaint)
f_chochLag(int len) =>
[b, s] = f_choch(len)
[b[1], s[1]]
// ══════════════════════════════════ KALKULASI ═══════════════════════════════════
float atrV = ta.atr(atrLen)
float ph = ta.pivothigh(high, effPivotLen, effPivotLen)
float pl = ta.pivotlow(low, effPivotLen, effPivotLen)
// ── Pola candle rejection (dievaluasi pada candle tertutup)
float rng = high - low
float body = math.abs(close - open)
float loWick = math.min(open, close) - low
float upWick = high - math.max(open, close)
bool bullPin = rng > 0 and loWick >= pinRatio * math.max(body, syminfo.mintick) and loWick >= 0.5 * rng
bool bearPin = rng > 0 and upWick >= pinRatio * math.max(body, syminfo.mintick) and upWick >= 0.5 * rng
bool bullEng = close[1] < open[1] and close > open and close >= open[1] and open <= close[1]
bool bearEng = close[1] > open[1] and close < open and close <= open[1] and open >= close[1]
bool anyPat = not usePin and not useEngulf
bool rejBull = not useReject or anyPat or (usePin and bullPin) or (useEngulf and bullEng)
bool rejBear = not useReject or anyPat or (usePin and bearPin) or (useEngulf and bearEng)
// ── CHoCH pada timeframe terpilih
int curSec = timeframe.in_seconds()
int chSec = timeframe.in_seconds(effChochTF)
bool chLow = chSec < curSec
bool chHigh = chSec > curSec
bool htfNew = timeframe.change(effChochTF)
[bChart, sChart] = f_choch(chLen)
float bullChoch = 0.0
float bearChoch = 0.0
if useChoch
if chLow
[bArr, sArr] = request.security_lower_tf(syminfo.tickerid, effChochTF, f_choch(chLen))
bullChoch := array.size(bArr) > 0 ? array.max(bArr) : 0.0
bearChoch := array.size(sArr) > 0 ? array.max(sArr) : 0.0
else if chHigh
[bH, sH] = request.security(syminfo.tickerid, effChochTF, f_chochLag(chLen), lookahead = barmerge.lookahead_on)
bullChoch := htfNew ? nz(bH) : 0.0
bearChoch := htfNew ? nz(sH) : 0.0
else
bullChoch := bChart
bearChoch := sChart
// ── Output
bool longSig = false
bool shortSig = false
bool bullSetup = false
bool bearSetup = false
float sigEntry = na
float sigSL = na
float sigTP1 = na
float sigTP2 = na
float sigTP3 = na
string sigConf = ""
if barstate.isconfirmed
// ── 1) Struktur market: swing pivot terkonfirmasi ──────────────────────────
if not na(ph)
swH := ph
swHIdx := bar_index - effPivotLen
swHUsed := false
if not na(pl)
swL := pl
swLIdx := bar_index - effPivotLen
swLUsed := false
// ── 2) Kelola trade terbuka: SL / tahap TP / break-even / statistik ────────
for t in trades
if t.active and bar_index > t.start
bool isL = t.dir == 1
bool closedNow = false
if not t.hidden and not na(t.lns)
for i = 0 to 4
line lx = array.get(t.lns, i)
if not na(lx)
line.set_x2(lx, bar_index)
bool stopHit = isL ? low <= t.slCur : high >= t.slCur
if stopHit
// konservatif: jika SL dan TP keduanya di dalam bar yang sama, SL diasumsikan lebih dulu
float rs = (isL ? t.slCur - t.entry : t.entry - t.slCur) / t.risk
float leg = t.rem * rs
t.rR := t.rR + leg
t.rem := 0.0
closedNow := true
string txt1 = (rs < -0.01 ? "SL ✘ " : "BE ➜ ") + f_money(leg * riskAmt) + " | Total " + f_sgn(t.rR) + "R " + f_money(t.rR * riskAmt)
f_updateLevelLabel(t, 1, txt1, true)
else
if w1 > 0 and not t.h1 and (isL ? high >= t.t1 : low <= t.t1)
t.h1 := true
t.rem := t.rem - w1
t.rR := t.rR + w1 * r1
bool done1 = t.rem <= 0.0001
if done1
closedNow := true
string txt2 = (useMulti ? "TP1 ✔ " : "TP ✔ ") + f_money(w1 * r1 * riskAmt) + (done1 ? " | Total " + f_sgn(t.rR) + "R " + f_money(t.rR * riskAmt) : "")
f_updateLevelLabel(t, 2, txt2, done1)
if useMulti and beAfterTP1
f_moveSLtoBE(t)
if w2 > 0 and not t.h2 and (isL ? high >= t.t2 : low <= t.t2)
t.h2 := true
t.rem := t.rem - w2
t.rR := t.rR + w2 * r2
bool done2 = t.rem <= 0.0001
if done2
closedNow := true
string txt3 = "TP2 ✔ " + f_money(w2 * r2 * riskAmt) + (done2 ? " | Total " + f_sgn(t.rR) + "R " + f_money(t.rR * riskAmt) : "")
f_updateLevelLabel(t, 3, txt3, done2)
if w3 > 0 and not t.h3 and (isL ? high >= t.t3 : low <= t.t3)
t.h3 := true
t.rem := t.rem - w3
t.rR := t.rR + w3 * r3
bool done3 = t.rem <= 0.0001
if done3
closedNow := true
string txt4 = "TP3 ✔ " + f_money(w3 * r3 * riskAmt) + (done3 ? " | Total " + f_sgn(t.rR) + "R " + f_money(t.rR * riskAmt) : "")
f_updateLevelLabel(t, 4, txt4, done3)
if closedNow
t.active := false
netR += t.rR
if t.rR > 0.05
nWin += 1
else if t.rR < -0.05
nLoss += 1
else
nBE += 1
// ── 3) Kelola zona pending: retest → konfirmasi → sinyal ───────────────────
int zi = array.size(zones) - 1
while zi >= 0
Zone z = array.get(zones, zi)
bool isLong = z.dir == 1
int res = 0 // 0 pertahankan | 1 trigger | 2 matikan
if (isLong and close < z.slp) or (not isLong and close > z.slp)
res := 2 // tidak valid (close melewati level SL)
else if bar_index - z.born > effMaxAge
res := 2 // kedaluwarsa
else
bool touchNow = isLong ? low <= z.prox : high >= z.prox
if touchNow
z.entered := true // harga kembali ke zona
if z.entered and (isLong ? bullChoch > 0 : bearChoch > 0)
z.choch := true // CHoCH terlihat setelah sentuhan
bool exitOK = isLong ? close > z.prox : close < z.prox
bool rejOK = isLong ? rejBull : rejBear
bool chOK = not useChoch or z.choch
bool touchOK = not useReject or touchNow
if z.entered and exitOK and rejOK and chOK and touchOK
res := 1 // exit + konfirmasi → sinyal
if res == 0
if not z.hidden
box.set_right(z.bx, bar_index)
else if res == 2
f_delZone(z)
array.remove(zones, zi)
else
if onlyLast
f_clearFinished()
float entry = close
if not z.hidden
box.set_right(z.bx, bar_index)
f_dropLevels(z)
Trade tr = f_addTrade(z.dir, entry, z.slp)
array.push(trades, tr)
array.push(fin, z)
array.remove(zones, zi)
nSig += 1
string cf = ""
if useReject
if isLong
cf := cf + (bullPin ? "PinBar " : "") + (bullEng ? "Engulfing " : "")
else
cf := cf + (bearPin ? "PinBar " : "") + (bearEng ? "Engulfing " : "")
if useChoch
cf := cf + "CHoCH(" + effChochTF + ")"
if isLong
longSig := true
else
shortSig := true
sigEntry := entry
sigSL := z.slp
sigTP1 := tr.t1
sigTP2 := tr.t2
sigTP3 := tr.t3
sigConf := cf
zi -= 1
if array.size(fin) > 200
array.shift(fin)
if array.size(trades) > 200
array.shift(trades)
// ── 4) Lanjutkan spike berjalan; finalisasi saat impuls berakhir ───────────
if not na(cBull)
bool cont = f_step(cBull, 1, open, high, low, close)
if not cont or bar_index - cBull.orig >= effMaxImp
if f_tryZone(cBull, 1, atrV)
bullSetup := true
cBull := na
if not na(cBear)
bool cont = f_step(cBear, -1, open, high, low, close)
if not cont or bar_index - cBear.orig >= effMaxImp
if f_tryZone(cBear, -1, atrV)
bearSetup := true
cBear := na
// ── 5) Break struktur bullish → mulai kandidat spike (demand) ──────────────
if not na(swH) and not swHUsed and close > swH
swHUsed := true
if na(cBull) and close > open
int n = math.min(bar_index - swHIdx - 1, 490)
float oLow = low
int oOff = 0
if n >= 1
for k = 1 to n
if low[k] < oLow
oLow := low[k]
oOff := k
if oOff >= 1 and oOff <= effMaxImp
Cand c = Cand.new(orig = bar_index - oOff, distal = oLow, oOpp = high[oOff], brkEdge = na, brkFound = false, run = 0, maxRun = 0, ext = high[oOff])
for k = oOff - 1 to 0
f_step(c, 1, open[k], high[k], low[k], close[k])
cBull := c
// ── 6) Break struktur bearish → mulai kandidat spike (supply) ──────────────
if not na(swL) and not swLUsed and close < swL
swLUsed := true
if na(cBear) and close < open
int n = math.min(bar_index - swLIdx - 1, 490)
float oHigh = high
int oOff = 0
if n >= 1
for k = 1 to n
if high[k] > oHigh
oHigh := high[k]
oOff := k
if oOff >= 1 and oOff <= effMaxImp
Cand c = Cand.new(orig = bar_index - oOff, distal = oHigh, oOpp = low[oOff], brkEdge = na, brkFound = false, run = 0, maxRun = 0, ext = low[oOff])
for k = oOff - 1 to 0
f_step(c, -1, open[k], high[k], low[k], close[k])
cBear := c
// ═════════════════════════════════════ PLOT ═════════════════════════════════════
bool showSig = dispMode == "Signal"
plotshape(showSig and longSig, "Sinyal Long", shape.triangleup, location.belowbar, sigBull, text = "Sinyal Long", textcolor = sigBull, size = size.tiny)
plotshape(showSig and shortSig, "Sinyal Short", shape.triangledown, location.abovebar, sigBear, text = "Sinyal Short", textcolor = sigBear, size = size.tiny)
// ═════════════════════════════════════ ALERT ════════════════════════════════════
alertcondition(longSig, "TRH Sinyal Long", "TRH Sinyal Long di {{ticker}} ({{interval}})")
alertcondition(shortSig, "TRH Sinyal Short", "TRH Sinyal Short di {{ticker}} ({{interval}})")
alertcondition(bullSetup, "TRH Setup Long", "TRH Setup Long (zona demand) di {{ticker}} ({{interval}})")
alertcondition(bearSetup, "TRH Setup Short", "TRH Setup Short (zona supply) di {{ticker}} ({{interval}})")
if useAlert and barstate.isconfirmed
string lv = " | Entry " + str.tostring(sigEntry, format.mintick) + " | SL " + str.tostring(sigSL, format.mintick) + " | " + (useMulti ? "TP1 " : "TP ") + str.tostring(sigTP1, format.mintick) + (w2 > 0 ? " | TP2 " + str.tostring(sigTP2, format.mintick) : "") + (w3 > 0 ? " | TP3 " + str.tostring(sigTP3, format.mintick) : "") + (sigConf != "" ? " | " + sigConf : "")
if longSig
alert("TRH SINYAL LONG | " + syminfo.ticker + " | " + timeframe.period + lv, alert.freq_once_per_bar_close)
if shortSig
alert("TRH SINYAL SHORT | " + syminfo.ticker + " | " + timeframe.period + lv, alert.freq_once_per_bar_close)
if dispMode == "Setup"
if bullSetup
alert("TRH SETUP LONG (zona demand) | " + syminfo.ticker + " | " + timeframe.period, alert.freq_once_per_bar_close)
if bearSetup
alert("TRH SETUP SHORT (zona supply) | " + syminfo.ticker + " | " + timeframe.period, alert.freq_once_per_bar_close)
// ═════════════════════════════════════ STATISTIK ════════════════════════════════
if showStats and barstate.islast
var table tb = table.new(position.top_right, 2, 8, bgcolor = color.new(chart.fg_color, 90), frame_color = color.gray, frame_width = 1, border_width = 1)
int closed = nWin + nLoss + nBE
float wr = closed > 0 ? nWin * 100.0 / closed : 0.0
table.cell(tb, 0, 0, "Statistik TRH", text_color = chart.fg_color, text_size = size.small)
table.cell(tb, 1, 0, "", text_color = chart.fg_color)
table.cell(tb, 0, 1, "Sinyal", text_color = chart.fg_color, text_size = size.small)
table.cell(tb, 1, 1, str.tostring(nSig), text_color = chart.fg_color, text_size = size.small)
table.cell(tb, 0, 2, "Menang", text_color = chart.fg_color, text_size = size.small)
table.cell(tb, 1, 2, str.tostring(nWin), text_color = cTP, text_size = size.small)
table.cell(tb, 0, 3, "Break-even", text_color = chart.fg_color, text_size = size.small)
table.cell(tb, 1, 3, str.tostring(nBE), text_color = chart.fg_color, text_size = size.small)
table.cell(tb, 0, 4, "Kalah", text_color = chart.fg_color, text_size = size.small)
table.cell(tb, 1, 4, str.tostring(nLoss), text_color = cSL, text_size = size.small)
table.cell(tb, 0, 5, "Win rate", text_color = chart.fg_color, text_size = size.small)
table.cell(tb, 1, 5, str.tostring(wr, "#.##") + "%", text_color = chart.fg_color, text_size = size.small)
table.cell(tb, 0, 6, "Net R", text_color = chart.fg_color, text_size = size.small)
table.cell(tb, 1, 6, f_sgn(netR), text_color = netR >= 0 ? cTP : cSL, text_size = size.small)
table.cell(tb, 0, 7, "Net P/L", text_color = chart.fg_color, text_size = size.small)
table.cell(tb, 1, 7, f_money(netR * riskAmt), text_color = netR >= 0 ? cTP : cSL, text_size = size.small)
Change History
| Date |
Author |
Description of Change |
| 20-07-2026 |
Admin |
Release Full Script |
|
|
|
|
|
|
|
|
|
|
|
|