Skip to content
Learn

Advanced Pine: Data Structures, MTF, dan Order Flow

Advanced · 4.5 jam

Prasyarat:

  • Modul 1-9: dasar Pine Script v5/v6 (execution model per-bar, tipe data, var/varip, := vs =)
  • Paham fungsi ta.* (ta.sma, ta.ema, ta.rsi, ta.crossover)
  • Bisa bikin indicator() dan plot() dasar
  • Paham series vs simple secara umum
  • Punya akun TradingView (minimal Essential untuk Volume Profile, Premium untuk footprint)

Tujuan Pembelajaran

  • Pakai array dan matrix buat nyimpen riwayat sinyal/level dengan method syntax (push/get/size/avg/sort)
  • Bikin dashboard pakai map (key-value state) dan table yang nempel di pojok chart
  • Mendesain User-Defined Types (UDT): type, .new(), reference semantics, method, dan overloading/polymorphism via komposisi + enum
  • Tarik data multi-timeframe lewat request.security TANPA repainting (barmerge.lookahead_off + disiplin bar terkonfirmasi [1])
  • Pakai request.financial dan request.economic (EPS, GDP, IDX:COMPOSITE) dan paham limit ~40 request per script
  • Hafal limitasi Pine: max_bars_back ~244, cap ~500 drawing object, batas waktu eksekusi, simple vs series
  • Baca Volume Profile (POC, VAH/VAL, HVN/LVN) dan anchored VWAP + std-dev band
  • Jujur soal order flow di TradingView: footprint, DOM, Time & Sales, dan agregasi ~1 detik (bukan tick asli)
  • Debug pakai Pine Logs (log.info/warning/error) dan kenali error compiler/runtime umum
  • Bangun dashboard MTF (Daily/Weekly/Monthly) buat array simbol IDX pakai UDT untuk state tiap simbol

0. Pengantar: Naik Kelas dari "Bikin Indikator" ke "Bikin Sistem"

Oke, sampai modul 9 lo udah bisa bikin indikator yang lumayan: tarik garis MA, kasih sinyal panah, warnain background. Itu semua main di satu nilai per bar. Mulai modul ini, kita naik kelas.

Bayangin gini. Sampai sekarang lo kayak pedagang yang cuma punya satu laci buat naro duit. Tiap candle baru, laci di-reset (kecuali pakai var). Di modul ini lo bakal punya:

  • Array & matrix = rak penyimpanan. Bisa nyimpen 50 level support sekaligus, riwayat sinyal, antrian harga.
  • Map = buku besar dengan label. "BBCA harganya berapa? UNVR RSI-nya berapa?" tinggal cari pakai nama.
  • Table = papan tulis yang nempel di pojok chart, gak ikut geser sama candle.
  • UDT (tipe bikinan sendiri) = lo bikin "barang" sendiri. Misalnya satu objek Saham yang nyimpen ticker, trend, RSI, sinyal — semua dalam satu bungkus.
  • MTF (multi-timeframe) = liat Daily, Weekly, Monthly sekaligus dari satu chart.

Di akhir modul, lo bakal bangun dashboard MTF yang nampilin trend + RSI buat sekumpulan saham IDX (BBCA, BBRI, TLKM, ASII) di tiga timeframe sekaligus, rapi dalam satu tabel di pojok chart. Itu tools yang dipakai trader serius buat scan market tiap pagi.

Satu tema yang bakal gue ulang terus sepanjang modul ini: kejujuran. Banyak indikator di TradingView keliatan jago di chart historis tapi bohong (repainting). Banyak tools order flow yang dijual seolah-olah tick-level padahal cuma agregasi 1 detik. Gue bakal kasih tau mana yang beneran, mana yang ilusi.

Versi: kita pakai Pine v6 sebagai default (rilis terbaru). Beberapa fitur kayak dynamic request.* dan enum cuma ada di v6. Gue tandain kalau ada yang khusus v6.

Latihan

  1. Buka Pine Editor (panel bawah TradingView), bikin script kosong dengan //@version=6 dan indicator("Modul 10 Sandbox", overlay=true). Save. Kita pakai file ini sepanjang modul.

Pro Tips

  • Mulai modul ini, deklarasi pakai //@version=6 biar dapet semua fitur baru (dynamic request, enum, footprint access).
  • Kalau lo masih bingung sama series vs satu angka, ulang dulu modul 2. Konsep itu fondasi semua yang di sini.

1. Array: Rak Penyimpanan Per-Bar

Array itu daftar nilai yang bisa lo isi, baca, dan ubah. Beda sama variabel biasa yang cuma nampung satu nilai, array nampung banyak.

Bikin array

//@version=6
indicator("Belajar Array", overlay=true)

var harga = array.new_float(0)   // array float kosong, size awal 0

Perhatiin dua hal:

  • var itu wajib di sini. Tanpa var, array-nya di-reset jadi kosong tiap bar — sia-sia.
  • array.new_float(0) artinya: bikin array isi float, dengan 0 elemen awal.

Operasi dasar

array.push(harga, close)        // tambah close ke ujung array
sz   = array.size(harga)        // berapa banyak isinya
awal = array.get(harga, 0)      // ambil elemen index 0 (paling pertama)
rata = array.avg(harga)         // rata-rata semua isi
array.sort(harga, order.ascending)  // urutkan dari kecil ke besar

Method syntax (lebih enak dibaca)

Pine v5/v6 ngebolehin nulis pakai titik, kayak objek.fungsi():

harga.push(close)               // sama persis dengan array.push(harga, close)
sz   = harga.size()
awal = harga.get(0)
rata = harga.avg()

Dua-duanya valid. Gue saranin method syntax karena lebih kebaca: "harga, push close".

Contoh nyata: simpen 20 close terakhir dan hitung rata-ratanya manual

//@version=6
indicator("Rolling Avg pakai Array", overlay=true)

var buf = array.new_float(0)

buf.push(close)                 // masukin close bar ini
if buf.size() > 20              // kalau udah lebih dari 20...
    buf.shift()                 // buang yang paling lama (index 0)

rata = buf.avg()
plot(rata, "Rolling 20", color.orange, 2)
plot(ta.sma(close, 20), "SMA20 bawaan", color.blue, 1)

Dua garis ini bakal nempel hampir sama persis. Bedanya, yang pakai array lo kontrol penuh logikanya. buf.shift() itu buang elemen paling depan (FIFO, kayak antrian). Ada juga buf.pop() buang elemen paling belakang.

Kenapa array berguna buat trader

  • Nyimpen level support/resistance yang kedeteksi, terus cari yang paling deket harga sekarang.
  • Nyimpen riwayat sinyal buat hitung win-rate di dalam script.
  • Nyimpen harga swing high/low terakhir buat ngegambar zona.

Fungsi array yang sering kepakai

FungsiGuna
push / poptambah/buang di ujung belakang
unshift / shifttambah/buang di ujung depan
get / setbaca/tulis di index tertentu
sizejumlah elemen
avg / sum / min / max / median / stdevstatistik
sort / reverseurutkan
indexof / includescari elemen
clearkosongkan

Latihan

  1. Bikin array yang nyimpen 10 nilai volume terakhir, terus plot rata-ratanya. Bandingin sama ta.sma(volume, 10).
  2. Modif: pakai array.max() buat plot volume tertinggi dari 10 bar terakhir sebagai garis. Bandingin sama ta.highest(volume, 10).

Pro Tips

  • array.new_float(0) bikin array kosong; array.new_float(5, 0.0) bikin 5 elemen yang semua isinya 0.0. Hati-hati beda-nya.
  • Gabungan push + shift dengan batas size = bikin 'rolling window' / antrian geser. Pola ini paling sering dipakai.
  • array.avg() error kalau array-nya kosong. Selalu cek size() > 0 dulu kalau ragu.

2. Matrix: Tabel Dua Dimensi buat Riwayat Sinyal/Level

Kalau array itu satu baris (1 dimensi), matrix itu tabel: punya baris DAN kolom (2 dimensi). Kebayang kayak spreadsheet.

Kapan butuh matrix? Kalau tiap "item" punya beberapa atribut. Contoh: nyimpen riwayat sinyal di mana tiap sinyal punya (harga, waktu, arah). Tapi jujur — buat kasus kayak gini, UDT (bagian 4) biasanya lebih enak dibaca. Matrix paling pas buat data numerik murni yang emang berbentuk grid, kayak matriks korelasi antar saham.

Bikin dan isi matrix

//@version=6
indicator("Belajar Matrix")

// matrix 3 baris x 3 kolom, semua isi 0.0
var m = matrix.new<float>(3, 3, 0.0)

matrix.set(m, 0, 0, close)      // baris 0, kolom 0 = close
x = matrix.get(m, 0, 0)         // baca balik

rows = matrix.rows(m)           // jumlah baris
cols = matrix.columns(m)        // jumlah kolom

Contoh: matriks korelasi mini antar 3 saham

Ini use-case matrix yang paling masuk akal. Misal lo mau simpen seberapa nyambung pergerakan BBCA, BBRI, BMRI (tiga bank besar). Tiap sel [i][j] = korelasi saham i dengan saham j.

//@version=6
indicator("Korelasi Bank IDX")

bbca = request.security("IDX:BBCA", timeframe.period, close)
bbri = request.security("IDX:BBRI", timeframe.period, close)
bmri = request.security("IDX:BMRI", timeframe.period, close)

var m = matrix.new<float>(3, 3, na)
len = 50

matrix.set(m, 0, 1, ta.correlation(bbca, bbri, len))
matrix.set(m, 0, 2, ta.correlation(bbca, bmri, len))
matrix.set(m, 1, 2, ta.correlation(bbri, bmri, len))

// nanti di bagian Table, kita render matrix ini jadi tabel visual
plot(matrix.get(m, 0, 1), "Korelasi BBCA-BBRI")

Operasi matrix yang berguna

FungsiGuna
matrix.new<type>(rows, cols, init)bikin matrix
matrix.get / setbaca/tulis sel
matrix.rows / columnsdimensi
matrix.add_row / add_coltambah baris/kolom dinamis
matrix.row / colambil satu baris/kolom sebagai array
matrix.avg / sumstatistik

Kapan PAKAI, kapan JANGAN

  • Pakai matrix: data grid numerik (korelasi, heatmap, lookup table fixed).
  • Jangan pakai matrix: data per-item dengan tipe campur (string + float + bool). Itu kerjaan UDT. Maksa pakai matrix = kode susah dibaca dan gak bisa nyimpen string/bool dalam satu matrix float.

Latihan

  1. Lengkapi matriks korelasi 3 bank jadi simetris (sel [1][0] = [0][1], dst) pakai loop. Hint: for i, for j, set(m,j,i, get(m,i,j)).

Pro Tips

  • matrix.new<float> pakai kurung siku <> karena dia tipe generik — bisa <float>, <int>, <bool>, <string>, bahkan <UDT>.
  • Buat 90% kasus trading, lo gak butuh matrix. Kalau bingung milih antara matrix dan UDT-dalam-array, pilih UDT. Lebih kebaca.
  • matrix.get error kalau index di luar batas. Pine gak ngecek otomatis, jadi pastiin row/col valid.

3. Map: Buku Besar dengan Label (Key-Value State)

Array pakai index angka (0, 1, 2...). Map pakai label/key apa aja. Ini powerful banget buat nyimpen state per-simbol.

Analogi: array itu loker bernomor. Map itu loker bernama — "BBCA", "TLKM", "UNVR".

Bikin dan pakai map

//@version=6
indicator("Belajar Map")

var m = map.new<string, float>()   // key string, value float

map.put(m, "BBCA", 9500.0)         // simpan
map.put(m, "TLKM", 3200.0)

harga = map.get(m, "BBCA")         // ambil -> 9500.0
ada   = map.contains(m, "UNVR")    // false
jml   = map.size(m)                // 2

Method syntax

m.put("BBCA", 9500.0)
h = m.get("BBCA")

Operasi map

FungsiGuna
put(key, val)simpan/update
get(key)ambil (na kalau gak ada)
contains(key)cek ada/gak
remove(key)hapus
keys() / values()ambil semua key/value sebagai array
size()jumlah

Contoh nyata: hitung berapa kali tiap saham kasih sinyal

Misal lo lagi mantau beberapa saham dan mau hitung frekuensi golden cross tiap saham dalam satu session:

//@version=6
indicator("Counter Sinyal per Simbol")

var counter = map.new<string, int>()
sym = syminfo.ticker

goldenCross = ta.crossover(ta.ema(close, 12), ta.ema(close, 26))

if goldenCross
    prev = counter.contains(sym) ? counter.get(sym) : 0
    counter.put(sym, prev + 1)

if barstate.islast
    log.info("Total golden cross {0}: {1}", sym, counter.get(sym))

Catatan penting soal urutan

Map tidak menjamin urutan key. Kalau lo butuh urutan tetap (misal mau render tabel rapi), simpan daftar key di array terpisah, atau pakai array of UDT. Ini jebakan umum.

Kapan pakai map vs array vs UDT

  • Map: lo butuh lookup cepat pakai nama. "State RSI buat simbol X."
  • Array: urutan penting, akses pakai index, iterasi berurutan.
  • UDT-dalam-array: tiap item kompleks (banyak field) DAN urutan penting. Ini yang bakal kita pakai di proyek akhir.

Latihan

  1. Bikin map<string, float> yang nyimpen harga close terakhir tiap kali bar tutup, key-nya syminfo.ticker. Log isinya di bar terakhir.
  2. Pikirin: kenapa map gak cocok buat nyimpen 'urutan 5 sinyal terakhir'? Tulis alasannya 1 kalimat.

Pro Tips

  • map.get ke key yang gak ada balikin na, bukan error. Tapi kalau value-nya int dan na, bisa bikin bug diam-diam. Selalu contains() dulu kalau ragu.
  • map.keys() balikin array, tapi urutannya gak dijamin. Jangan andelin urutan map buat render UI.
  • Map paling sinar di v6 buat nyimpen state multi-simbol. Cocok dipadu dynamic request.security.

4. User-Defined Types (UDT): Bikin "Barang" Sendiri

Ini bagian terpenting di modul ini. UDT = lo bikin tipe data sendiri yang ngebungkus beberapa field jadi satu objek. Ini fondasi proyek akhir.

Kenapa butuh UDT?

Misal lo mau nyimpen state satu saham: ticker, trend, nilai RSI, dan apakah lagi kasih sinyal. Tanpa UDT, lo butuh 4 array paralel:

// CARA JELEK - rawan bug, susah dibaca
var tickers = array.new_string(0)
var trends  = array.new_string(0)
var rsis    = array.new_float(0)
var signals = array.new_bool(0)
// index 3 di tiap array harus selalu sinkron... gampang kacau

Dengan UDT, semua jadi satu bungkus:

type SahamState
    string ticker
    string trend
    float  rsi
    bool   signal

Bikin objek pakai .new()

s = SahamState.new("BBCA", "UP", 65.4, true)

// atau isi field belakangan
s2 = SahamState.new()
s2.ticker := "TLKM"
s2.rsi := 48.0

Akses field pakai titik: s.ticker, s.rsi. Ubah pakai :=.

Default value field

type SahamState
    string ticker = ""
    string trend  = "FLAT"
    float  rsi    = na
    bool   signal = false

Kalau lo panggil SahamState.new() tanpa argumen, field-nya ambil default ini.

REFERENCE SEMANTICS — JEBAKAN PALING BAHAYA

Objek UDT itu reference (rujukan), bukan kopian. Artinya kalau lo assign objek ke variabel lain, dua-duanya nunjuk ke barang yang SAMA:

REFERENCE vs COPY — ASSIGN = SHARING, COPY = KLONINGb = aabSahamStatersi: 99.0SATU objekubah b.rsi → a.rsi ikut 99b = a.copy()abobjek arsi: 60.0objek brsi: 99.0DUA objek terpisahubah b.rsi → a.rsi tetap 60
Reference vs copy: assign = sharing, copy = kloning
a = SahamState.new("BBCA", "UP", 60.0, true)
b = a                  // b dan a nunjuk objek yang SAMA
b.rsi := 99.0
// sekarang a.rsi JUGA 99.0! karena a dan b satu objek

Kalau lo beneran mau salinan terpisah, pakai .copy():

b = a.copy()           // b objek baru, terpisah
b.rsi := 99.0          // a.rsi tetap 60.0

Ini sumber bug yang bikin pusing pemula. Inget: assign = sharing, copy = kloning.

Method: fungsi yang nempel ke tipe

Lo bisa bikin fungsi khusus buat tipe lo pakai keyword method:

type Trade
    float entry
    float stop
    int   dir       // 1 = long, -1 = short

method risk(Trade this, float px) =>
    math.abs(px - this.stop) * this.dir

method isWinning(Trade this) =>
    this.dir == 1 ? close > this.entry : close < this.entry

Panggil pakai titik, persis kayak method bawaan:

t = Trade.new(9000.0, 8800.0, 1)
r = t.risk(close)        // 'this' otomatis keisi 't'
menang = t.isWinning()

Parameter pertama (this) otomatis keisi sama objek yang manggil. Itu konvensi.

Polymorphism: method overloading (TANPA inheritance)

Pine gak punya class inheritance (gak ada "class anak mewarisi class induk"). Tapi Pine punya method overloading — method dengan nama sama tapi tipe/jumlah parameter beda:

method describe(float x) => "angka: " + str.tostring(x)
method describe(string x) => "teks: " + x
method describe(Trade t) => "trade @ " + str.tostring(t.entry)

// Pine pilih versi yang cocok berdasarkan tipe argumen:
x = (5.0).describe()       // "angka: 5"
y = "halo".describe()      // "teks: halo"

Ini "polymorphism" ala Pine: dispatch berdasarkan tipe penerima.

Pengganti inheritance: KOMPOSISI + ENUM

Karena gak ada inheritance, cara idiomatik di Pine adalah:

  1. Komposisi — UDT nyimpen UDT lain sebagai field.
  2. Enum (v6) — buat "varian/state bertipe" tanpa string ajaib.
//@version=6
indicator("Enum + Komposisi")

enum Trend
    up
    down
    flat

type Indikator
    float rsi
    float ema

type Saham
    string    ticker
    Trend     trend       // pakai enum, bukan string "UP"/"DOWN"
    Indikator ind         // komposisi: Saham PUNYA Indikator

s = Saham.new("BBCA", Trend.up, Indikator.new(65.0, 9000.0))

teks = switch s.trend
    Trend.up   => "Naik"
    Trend.down => "Turun"
    Trend.flat => "Datar"

Kenapa enum lebih bagus dari string "UP"? Karena typo "Up" vs "UP" gak ketahuan sampai runtime, sedangkan Trend.up salah ketik langsung error pas compile. Lebih aman.

Array of UDT — pola paling kepakai

var watchlist = array.new<Saham>()
watchlist.push(Saham.new("BBCA", Trend.up, Indikator.new(65.0, 9000.0)))
watchlist.push(Saham.new("TLKM", Trend.down, Indikator.new(42.0, 3200.0)))

for s in watchlist
    log.info("{0} RSI {1}", s.ticker, s.ind.rsi)

Ini persis kerangka proyek akhir kita.

Latihan

  1. Bikin UDT 'Level' dengan field price (float) dan kind (enum: support/resistance). Bikin array<Level>, isi 3 level, log semuanya.
  2. Tambahin method 'distance(Level this) => math.abs(close - this.price)' lalu cari level terdekat dari harga sekarang.
  3. Buktiin reference semantics: bikin objek a, set b=a, ubah b, log a. Lalu ulang pakai b=a.copy() dan bandingin hasilnya.

Pro Tips

  • Reference semantics: ingat 'b = a' itu sharing, bukan copy. Pakai .copy() kalau mau objek terpisah. Ini bug nomor satu pemula UDT.
  • Enum (v6) gantiin string ajaib kayak "UP"/"DOWN". Typo enum ketahuan pas compile, typo string ketahuan pas udah rugi.
  • Method parameter pertama biasanya dinamain 'this' — itu objek yang manggil. Cuma konvensi, boleh nama lain, tapi ikutin standar.
  • Pine GAK punya inheritance. Berhenti nyari 'extends'. Pakai komposisi (UDT dalam UDT) + enum.

5. Table: Dashboard yang Nempel di Pojok Chart

Beda sama plot() yang gambar data per-bar dan ikut geser, table itu papan info yang nempel di pojok chart dan gak ikut scroll. Ini cara bikin dashboard.

Anatomi table

//@version=6
indicator("Dashboard Sederhana", overlay=true)

var t = table.new(position.top_right, columns=2, rows=3,
                  bgcolor=color.new(color.black, 20),
                  border_width=1)
  • var wajib — biar table dibikin sekali, bukan tiap bar.
  • position.top_right — pojok kanan atas. Pilihan lain: top_left, bottom_left, bottom_right, middle_center, dst.
  • columns & rows — ukuran grid.

Isi sel — HANYA di bar terakhir

Penting: isi table cuma pas barstate.islast. Kalau lo isi tiap bar, boros eksekusi dan gak ada gunanya (table cuma muncul sekali).

rsi = ta.rsi(close, 14)
trendUp = close > ta.ema(close, 50)

if barstate.islast
    // header
    table.cell(t, 0, 0, "Metrik", text_color=color.white, bgcolor=color.new(color.gray, 50))
    table.cell(t, 1, 0, "Nilai",  text_color=color.white, bgcolor=color.new(color.gray, 50))
    // baris harga
    table.cell(t, 0, 1, "Harga", text_color=color.white)
    table.cell(t, 1, 1, str.tostring(close, format.mintick), text_color=color.white)
    // baris RSI
    table.cell(t, 0, 2, "RSI", text_color=color.white)
    table.cell(t, 1, 2, str.tostring(rsi, "#.##"),
               text_color = rsi > 70 ? color.red : rsi < 30 ? color.green : color.white)

Koordinat sel itu (column, row) — kolom dulu baru baris. Banyak yang kebalik dan bingung kenapa layout-nya ngaco.

Panel sinyal berwarna

Trik umum: bikin sel yang warnanya berubah sesuai kondisi — ijo kalau bullish, merah kalau bearish.

sinyal = trendUp ? "BULLISH" : "BEARISH"
warna  = trendUp ? color.new(color.green, 0) : color.new(color.red, 0)

if barstate.islast
    table.cell(t, 0, 0, "Sinyal", text_color=color.white)
    table.cell(t, 1, 0, sinyal, text_color=color.white, bgcolor=warna)

Tips visual

  • text_size = size.tiny / small / normal / large / huge.
  • text_halign / text_valign buat rata kiri/tengah/kanan.
  • Pakai table.cell_set_text() dan table.cell_set_bgcolor() kalau mau update sel yang udah ada tanpa bikin ulang.

Table bakal kita pakai sebagai output utama dashboard MTF di proyek akhir.

Latihan

  1. Bikin dashboard 2 kolom x 4 baris: Harga, RSI, EMA50, dan Sinyal (BULLISH/BEARISH berwarna).
  2. Pindahin posisi table ke bottom_left dan kecilin text jadi size.small.
  3. Tambah baris 'Volume vs Avg': hijau kalau volume > ta.sma(volume,20), abu kalau enggak.

Pro Tips

  • Koordinat table = (kolom, baris), bukan (baris, kolom). Salah satu typo paling sering.
  • Bungkus pengisian table dalam 'if barstate.islast'. Ngisi tiap bar buang-buang waktu eksekusi.
  • var table.new() sekali, lalu table.cell tiap render. Jangan table.new() di dalam if barstate.islast — nanti kena reset.
  • str.tostring(close, format.mintick) bikin harga ngikutin presisi tick simbol. Lebih rapi dari hardcode '#.##'.

6. request.security: Multi-Timeframe Tanpa Bohong (Repainting)

Ini bagian di mana banyak indikator gratisan di TradingView bohong. Gue bakal ajarin cara yang jujur.

Konsep dasar

request.security narik data dari timeframe atau simbol lain ke chart lo:

// data Daily dari simbol yang lagi dibuka
closeHarian = request.security(syminfo.tickerid, "D", close)

// data dari simbol lain, timeframe sama
ihsg = request.security("IDX:COMPOSITE", timeframe.period, close)

Argumen: request.security(symbol, timeframe, expression).

REPAINTING — kenapa indikator bisa bohong

Misal lo di chart 1 jam, narik RSI Daily. Bar Daily yang lagi jalan belum tutup. RSI-nya masih berubah-ubah sampai jam pasar tutup.

REPAINT — BAR HTF BERJALAN GOYANG, BAR [1] TERKONFIRMASI ENGGAKDaily [1] — udah TUTUPDaily [0] — masih JALAN?1H berikutbelum kejadiannilai FINAL, gak berubahnilai goyang sampai tutuppakai expr[1] = ambil bar initelat satu bar HTF, tapi JUJUR (gak repaint)baca bar [0] = REPAINTkeliatan jago di historis, bohong di live
Kenapa pakai [1]: bar HTF berjalan masih goyang, bar terkonfirmasi enggak

Masalahnya: di chart historis, Pine tau nilai FINAL bar Daily itu (karena udah lewat). Jadi indikator keliatan selalu bener di masa lalu. Tapi di real-time, nilainya masih goyang. Inilah repainting — indikator yang "ngecat ulang" sejarah biar keliatan jago padahal di live gak bisa diandelin.

Analogi: kayak ngaku bisa tebak skor bola, tapi nebaknya pas pertandingan udah kelar. Gampang.

DUA aturan disiplin biar jujur

Aturan 1: lookahead_off (ini default, tapi tegasin)

rsiHarian = request.security(syminfo.tickerid, "D", ta.rsi(close, 14),
                             lookahead = barmerge.lookahead_off)

barmerge.lookahead_off = jangan ngintip masa depan. Ini default di Pine, tapi gue saranin tulis eksplisit biar jelas niatnya. JANGAN PERNAH pakai lookahead_on kecuali lo bener-bener paham (99% kasus = bug).

Aturan 2: pakai bar terkonfirmasi [1]

Meski lookahead off, bar HTF yang lagi jalan masih bisa goyang. Solusi paling aman: ambil nilai bar HTF yang udah tutup pakai [1]:

rsiHarianAman = request.security(syminfo.tickerid, "D",
                                 ta.rsi(close, 14)[1],
                                 lookahead = barmerge.lookahead_off)

[1] di dalam ekspresi = ambil nilai bar Daily yang udah konfirmasi (kemarin), bukan yang masih jalan hari ini. Trade-off: lo dapet data "telat satu bar HTF", tapi datanya gak akan berubah. Buat sinyal yang dipakai entry beneran, ini wajib.

Pola standar yang aman (hafalin)

f_htf(tf, expr) =>
    request.security(syminfo.tickerid, tf, expr[1],
                     lookahead = barmerge.lookahead_off)

rsiW = f_htf("W", ta.rsi(close, 14))   // RSI Weekly terkonfirmasi

Dynamic request (v6)

Di v6, argumen simbol/timeframe boleh series (dihitung runtime). Ini buka pintu scan multi-simbol dalam satu loop:

//@version=6
simbol = array.from("IDX:BBCA", "IDX:BBRI", "IDX:TLKM")
for s in simbol
    c = request.security(s, "D", close[1], lookahead = barmerge.lookahead_off)
    log.info("{0} close D: {1}", s, c)

Di v5 ini gak bisa (simbol harus konstan). v6 ngebolehin. Tapi inget limit ~40 panggilan (bagian 8).

Cek repainting indikator orang

Kalau lo download indikator MTF dari komunitas, cek: apakah dia pakai [1] dan lookahead_off? Kalau enggak, hati-hati — backtest-nya mungkin ilusi.

Latihan

  1. Bikin indikator yang plot RSI Daily di chart 1H, pakai pola aman (expr[1] + lookahead_off).
  2. Bikin dua plot: satu pakai [1] satu enggak. Refresh chart beberapa kali, amati mana yang sinyalnya geser (repaint).
  3. Pakai array.from + loop (v6) buat log close Daily 3 saham bank IDX.

Pro Tips

  • lookahead_off itu default, tapi tulis eksplisit. lookahead_on hampir selalu = bug yang ngintip masa depan.
  • Buat sinyal entry beneran, SELALU pakai expr[1] di dalam request.security. Telat satu bar HTF tapi jujur.
  • Bungkus pola request.security lo dalam satu fungsi helper. Sekali bener, kepakai di mana-mana, gak gampang lupa [1].
  • Test repainting: jalanin indikator, screenshot, refresh chart. Kalau sinyal historis pindah posisi = repainting.

7. request.financial & request.economic: Fundamental & Makro

Selain harga, Pine bisa narik data fundamental (laporan keuangan) dan data ekonomi makro.

request.financial — data laporan keuangan

// EPS (laba per saham) BBCA, periode kuartalan
eps = request.financial("IDX:BBCA", "EARNINGS_PER_SHARE_BASIC", "FQ")

// Pendapatan total, tahunan
rev = request.financial("IDX:BBCA", "TOTAL_REVENUE", "FY")

Argumen: request.financial(symbol, financial_id, period).

  • period: "FQ" (fiscal quarter / kuartalan), "FY" (fiscal year / tahunan), "TTM" (trailing twelve months / 12 bulan terakhir).
  • financial_id contoh: EARNINGS_PER_SHARE_BASIC, TOTAL_REVENUE, NET_INCOME, GROSS_MARGIN, MARKET_CAP, PRICE_EARNINGS.

Contoh plot EPS BBCA biar keliatan tren laba:

//@version=6
indicator("EPS BBCA")
eps = request.financial("IDX:BBCA", "EARNINGS_PER_SHARE_BASIC", "FQ")
plot(eps, "EPS", color.green, 2, plot.style_stepline)

request.economic — data makro

// GDP Indonesia
gdp = request.economic("ID", "GDP")

// inflasi (kalau tersedia)
inf = request.economic("ID", "CPI")

Argumen: request.economic(country_code, field). "ID" = Indonesia.

Bandingin saham vs IHSG (relative strength)

Use-case favorit: liat apakah saham lo outperform atau underperform IHSG.

//@version=6
indicator("BBCA vs IHSG (Relative Strength)")

saham = close
ihsg  = request.security("IDX:COMPOSITE", timeframe.period, close[1],
                         lookahead = barmerge.lookahead_off)

rs = saham / ihsg          // naik = saham lebih kuat dari index
plot(rs, "RS BBCA/IHSG", color.orange, 2)

Kalau garis RS naik, BBCA lagi lebih kuat dari pasar. Turun = lagi kalah sama pasar. Trader pakai ini buat milih saham yang "mimpin".

LIMIT yang wajib lo tau

  • Maksimal ~40 panggilan request.* per script (gabungan security + financial + economic). Lewat itu, error. Jadi kalau lo scan 10 simbol x 3 timeframe x 2 metrik = 60, langsung mentok. Hitung dulu sebelum desain.
  • Data fundamental update lambat (per kuartal/tahun), wajar — itu sifat datanya.
  • Beberapa metrik gak tersedia buat semua simbol IDX. Cek hasilnya na atau enggak.
  • Data ekonomi tergantung ketersediaan TradingView per negara. Gak semua field ada buat "ID".

Strategi hemat panggilan

Kalau lo butuh banyak data dari satu simbol+timeframe, gabungin jadi satu tuple dalam SATU request:

[c, r, e] = request.security(syminfo.tickerid, "D",
               [close[1], ta.rsi(close,14)[1], ta.ema(close,50)[1]],
               lookahead = barmerge.lookahead_off)

Satu panggilan, tiga nilai. Ini ngirit kuota dari 3 jadi 1. Trik penting buat dashboard MTF.

Latihan

  1. Plot EPS dan Net Income BBRI dalam satu indikator (period FY).
  2. Bikin RS ratio TLKM vs IHSG. Tambah EMA20 dari ratio-nya buat smoothing.
  3. Hitung di kertas: kalau dashboard lo butuh 5 simbol x 3 timeframe, dan tiap kombinasi narik [close, rsi, ema] sebagai tuple — berapa total panggilan request? Aman gak dari limit 40?

Pro Tips

  • Gabungin banyak nilai jadi tuple dalam SATU request.security buat hemat kuota ~40 panggilan. Wajib buat dashboard multi-simbol.
  • Hitung total request.* sebelum desain: simbol x timeframe x metrik. Gampang nembus 40 tanpa sadar.
  • Cek hasil request.financial apakah na — gak semua metrik ada buat semua saham IDX.
  • Plot fundamental pakai plot.style_stepline biar keliatan 'tangga' sesuai periode laporan, bukan garis miring nyesatin.

8. Limitasi Pine yang Wajib Dihafal

Pine itu sandbox dengan batas keras. Kalau lo gak tau batasnya, script lo error atau lambat misterius. Ini daftar yang WAJIB lo inget.

1. max_bars_back (~244 default)

Pine cuma "inget" sekitar 244 bar historis per variabel series secara default. Kalau lo akses close[300], bisa error: "Pine cannot determine the referencing length of a series."

Solusi:

indicator("X", max_bars_back = 500)

Tapi makin gede makin boros memori. Naikin secukupnya aja.

2. Cap drawing object (~500 each)

Maksimal sekitar 500 label, 500 line, 500 box per script (atur via max_labels_count, dst., maksimum 500). Kalau lo gambar label tiap bar tanpa hapus, yang lama bakal otomatis ilang begitu tembus 500.

indicator("X", max_labels_count = 500, max_lines_count = 500)

Kalau butuh banyak, hapus yang lama manual: label.delete(oldLabel).

3. Batas waktu eksekusi

Tiap script ada batas waktu compile dan eksekusi per-bar. Loop berat → error "Loop takes too long." Hindari loop dalam loop yang gede, atau iterasi ribuan elemen tiap bar.

4. Limit request.* (~40 panggilan)

Udah dibahas bagian 7. Maksimal sekitar 40 request.security/financial/economic per script. Gabungin pakai tuple buat hemat.

5. simple vs series — error tersulit buat pemula

Ini konsep yang bikin banyak orang nyerah. Singkatnya:

  • series = nilai yang bisa beda tiap bar (kayak close, ta.rsi(...)).
  • simple = nilai yang tetap sepanjang script jalan (kayak input yang gak berubah).
  • const = literal konstan (14, "D").

Banyak fungsi MINTA simple atau const, bukan series. Contoh klasik: argumen timeframe request.security di v5 harus simple string — gak bisa series (di v6 baru bisa via dynamic request).

Error-nya kira-kira: "Cannot call 'request.security' with argument 'timeframe'... a 'series string' was used but a 'simple string' is expected."

Gimana benerin?

  • Jangan bikin argumen yang harusnya tetap jadi tergantung close/bar.
  • Pakai input.timeframe(...) (hasilnya simple), bukan ekspresi series.
  • Di v6, kalau emang butuh series timeframe, pastikan lo pakai dynamic request yang emang dukung itu.

Tabel ringkas limitasi

LimitNilai defaultCara naikin
max_bars_back~244max_bars_back=N (hati-hati memori)
label/line/box500 maxmax_labels_count dst.
request.* calls~40gabung tuple, kurangi simbol
waktu eksekusifixedoptimasi loop

Hafalin ini. 80% error "misterius" di Pine balik ke salah satu dari sini.

Latihan

  1. Sengaja bikin error: akses close[300] di script default. Baca pesan error-nya. Lalu benerin pakai max_bars_back.
  2. Bikin script yang gambar label tiap bar tanpa hapus, jalanin di chart panjang, amati gimana label lama ilang di ~500.
  3. Jelasin dengan kata-kata sendiri beda series vs simple, dan kenapa timeframe di request.security butuh simple.

Pro Tips

  • Error 'referencing length of a series'? Naikin max_bars_back. Tapi naikin secukupnya, jangan langsung 5000.
  • Error 'simple/const expected, series found'? Argumen yang harusnya tetap kebawa jadi series. Pakai input.* atau literal.
  • Gambar label/line tiap bar? Hapus yang lama (label.delete) atau lo bakal mentok 500 dan yang lama ilang sendiri.
  • 'Loop takes too long' = optimasi. Kurangi iterasi, hindari nested loop besar tiap bar.

9. Volume Profile: POC, Value Area, HVN/LVN

Sekarang kita pindah dari ngoding ke baca data volume secara profesional. Volume Profile beda dari volume bar biasa: dia nunjukin berapa banyak volume terjadi di tiap LEVEL HARGA, bukan per waktu.

Analogi: volume bar biasa jawab "jam berapa rame?". Volume Profile jawab "di harga berapa orang paling banyak transaksi?". Yang kedua jauh lebih berguna buat nentuin support/resistance.

VOLUME PROFILE — POC, VALUE AREA, HVN/LVNVAHVALHVNLVNPOCPOC = level volume terbanyak (magnet). HVN = harga betah. LVN = jalan tol.
Volume Profile: POC sebagai magnet, VAH/VAL batas value area, HVN/LVN node

Jenis-jenis (perlu plan berbayar, min Essential)

  • VPVR (Visible Range) — profil dari range yang lagi keliatan di layar. Geser chart, profil ikut update.
  • VRVP / Fixed Range — lo drag manual over swing tertentu. Ini "jurus pro": pasang di area konsolidasi sebelumnya buat liat di mana institusi transaksi.
  • SVP (Session Volume) — profil per sesi (per hari).

Cara pasang: tombol Indicators (ikon beaker di top toolbar) → cari "Volume Profile" → pilih jenisnya. Buat Fixed Range, abis dipasang lo drag di area yang mau dianalisis.

Istilah yang WAJIB dipahami

  • POC (Point of Control) = level harga dengan volume TERBANYAK. Ini "harga wajar" / magnet. Harga sering balik ke POC. Garis paling tebal/mencolok di profil.
  • Value Area (VA) = rentang harga yang nampung ~70% volume. Batasnya:
    • VAH (Value Area High) = batas atas.
    • VAL (Value Area Low) = batas bawah.
    • VAH/VAL sering jadi support/resistance.
  • HVN (High Volume Node) = tonjolan volume tinggi. Zona konsolidasi, harga "betah" di sini → support/resistance kuat.
  • LVN (Low Volume Node) = lembah volume rendah. Harga lewat cepet di sini (sedikit transaksi) → zona "luncuran".

Cara baca buat trading IDX dan crypto

  • Harga deket POC = wajar, sering konsolidasi/balik.
  • Harga di atas VAH = mahal relatif, kemungkinan reversi balik ke VA atau breakout beneran.
  • LVN = target gerak cepet. Harga cenderung "loncat" lewat LVN sampai ketemu HVN berikutnya.
  • Pasang Fixed Range di base/akumulasi BBCA sebelumnya → POC-nya sering jadi level krusial ke depan.
  • Sama persis di crypto: pasang Fixed Range di range akumulasi BINANCE:BTCUSDT (misal base sebelum breakout) → POC-nya jadi magnet harga, VAH/VAL jadi pagar value area buat scalp/swing.
Konteks Crypto

Justru di crypto Volume Profile paling bersinar. Buka BINANCE:BTCUSDT atau BINANCE:ETHUSDT, data jalan 24/7 dan real-time gratis — gak ada gap sesi, gak butuh add-on berbayar. Bandingin sama IDX equity yang real-time-nya add-on terpisah. Buat BTC/ETH, profil intraday pun presisi karena volumenya beneran live.

Catatan kualitas data

Volume Profile cuma seakurat data volumenya. Buat IDX, data real-time itu add-on berbayar terpisah. Pakai data delay 15 menit buat profil intraday = kurang presisi. Buat analisis harian/swing pakai data historis, masih oke. Buat crypto (BINANCE:BTCUSDT/ETHUSDT) data real-time gratis, jadi profil intraday-nya akurat tanpa biaya tambahan.

Latihan

  1. Pasang Fixed Range Volume Profile di swing BBCA 3 bulan terakhir. Tandai POC, VAH, VAL.
  2. Cari satu LVN di chart TLKM dan amati: apakah harga emang gerak cepet pas lewat zona itu?
  3. Bandingin POC VPVR (visible range) saat lo zoom in vs zoom out. Perhatiin kenapa nilainya berubah.
  4. Ulang latihan 1 di BINANCE:BTCUSDT (range akumulasi sebelum breakout besar). Bandingin: profil crypto pakai data real-time gratis, jadi POC/VAH/VAL-nya lebih presisi dari profil intraday IDX yang delay.

Pro Tips

  • Fixed Range (VRVP) di area konsolidasi lama = jurus pro buat nemu level institusi. Jauh lebih berguna dari VPVR yang ikut geser.
  • POC = magnet harga. VAH/VAL = pagar value area. LVN = jalur tol (harga lewat cepet). Hafalin tiga ini.
  • Volume Profile butuh plan minimal Essential. Akurasi intraday IDX butuh add-on data real-time terpisah.
  • Jangan campur POC dari timeframe beda. POC harian beda makna sama POC mingguan.

10. Anchored VWAP dan VWAP Std-Dev Bands

VWAP = Volume Weighted Average Price = harga rata-rata tertimbang volume. Bedanya dari MA biasa: VWAP ngitung volume, jadi level harga yang ramai transaksinya punya bobot lebih besar. Institusi pakai VWAP sebagai patokan eksekusi, jadi VWAP berfungsi sebagai support/resistance dinamis.

Session VWAP (bawaan)

Di Pine: ta.vwap. Ini reset tiap sesi (harian). Cocok buat intraday.

//@version=6
indicator("Session VWAP", overlay=true)
plot(ta.vwap, "VWAP", color.blue, 2)

Anchored VWAP — upgrade profesional

Session VWAP reset tiap hari otomatis. Anchored VWAP lo "jangkar" (anchor) ke titik yang LO pilih dan punya makna: gap earnings, swing high/low, IPO, atau hari berita besar. Dia ngukur harga rata-rata yang dibayar SEJAK kejadian penting itu.

Di UI: tools Anchored VWAP (di drawing toolbar / via Indicators), lalu klik bar titik jangkarnya. Misal jangkar di bottom crash BBCA Maret lalu → VWAP-nya nunjukin rata-rata harga semua orang yang beli sejak titik balik itu.

Buat crypto, titik jangkar yang bermakna beda: hari halving BTC, tanggal listing besar di Binance, atau hari FOMC (BTC/ETH gerak keras ke event makro US). Jangkar anchored VWAP BINANCE:BTCUSDT di bottom pasca-FOMC → garisnya nunjukin rata-rata harga semua orang yang beli sejak titik balik itu, sama persis logikanya kayak di saham.

Kenapa lebih berguna? Karena "rata-rata sejak titik yang bermakna" jauh lebih informatif dari "rata-rata sejak jam buka hari ini".

VWAP Std-Dev Bands (pita deviasi)

Tambahin band 1/2/3 standar deviasi di atas/bawah VWAP. Gunanya:

  • Harga deket band atas (+2 SD) = relatif mahal → kandidat mean-reversion (balik ke VWAP).
  • Harga nembus band konsisten = trend kuat (continuation).

Di settings indikator VWAP bawaan TradingView, ada opsi nyalain bands dan atur multiplier-nya.

Contoh Pine: VWAP + band sederhana

//@version=6
indicator("VWAP + Bands", overlay=true)

vwap = ta.vwap
plot(vwap, "VWAP", color.blue, 2)

// deviasi sederhana berbasis ATR sebagai proxy band
dev = ta.atr(20)
plot(vwap + 2 * dev, "Upper", color.new(color.red, 50))
plot(vwap - 2 * dev, "Lower", color.new(color.green, 50))

Catatan: band std-dev VWAP "sebenarnya" dihitung dari deviasi harga-tertimbang-volume, bukan ATR. Buat presisi, pakai indikator VWAP bawaan TradingView yang udah punya band std-dev resmi. Contoh di atas cuma ilustrasi konsep.

Cara pakai gabungan

  • Intraday: session VWAP sebagai mean. Di atas = bias long, di bawah = bias short.
  • Swing: anchored VWAP dari swing penting + band buat zona entry.
  • Gabung dengan POC Volume Profile: kalau anchored VWAP nyambung sama POC, itu konfluensi level kuat.

Latihan

  1. Pasang Anchored VWAP di bottom terakhir BBRI. Amati apakah harga respek garis itu sebagai support.
  2. Nyalain VWAP bands (bawaan TradingView) di chart ASII intraday. Tandai kapan harga sentuh +2SD dan apa yang terjadi setelahnya.
  3. Cari satu titik di mana anchored VWAP ketemu POC. Catat reaksi harga di level itu.
  4. Pasang Anchored VWAP di BINANCE:ETHUSDT, jangkar di hari FOMC atau listing besar terakhir. Amati apakah harga respek garis itu. Bandingin presisinya sama anchored VWAP saham IDX yang datanya delay.

Pro Tips

  • Anchored VWAP > session VWAP buat swing. Jangkar di titik bermakna (gap, swing, IPO), bukan sekadar buka pasar.
  • VWAP = support/resistance dinamis karena institusi benchmark eksekusi ke situ. Hargai sebagai level, bukan sekadar garis.
  • Band +2SD = zona mahal (mean-reversion candidate). Nembus band terus = trend kuat. Konteks menentukan.
  • Konfluensi anchored VWAP + POC Volume Profile = level super kuat. Cari pertemuan keduanya.

11. Order Flow dengan Jujur: Footprint, DOM, Time & Sales

Bagian ini paling penting buat KEJUJURAN. Order flow itu "daleman" pasar — siapa beli agresif, siapa jual. TradingView punya tools-nya, tapi gue bakal kasih tau batasnya yang jarang diomongin penjual kursus.

Footprint charts (Premium+)

Tiap candle dipecah jadi volume di sisi bid vs sisi ask per level harga. Yang bisa lo baca:

ANATOMI SATU SEL FOOTPRINT — BID vs ASK PER LEVELBID (jual agresif)ASK (beli agresif)9.550120809.54090410← imbalance (ask >> bid)9.530602409.52088060absorption: volume gede, harga diam →9.510701109.5004070Delta = total ask − total bid per bar. Positif = pembeli dominan.
Anatomi satu sel footprint: bid vs ask per level harga
  • Bid x Ask = berapa volume kena di bid (jual agresif) vs ask (beli agresif) tiap level.
  • Delta = volume ask dikurangi volume bid, dijumlah per bar: Delta=bar(VaskVbid)\text{Delta} = \sum_{\text{bar}} \left(V_{\text{ask}} - V_{\text{bid}}\right). Delta positif = pembeli agresif dominan.
  • Imbalance = satu sisi jauh dominan di level tertentu (rasio diagonal bid/ask di atas ambang). Tanda tekanan.
  • Absorption (penyerapan) = volume gede di satu level tapi harga gak gerak. Artinya ada limit order gede nyerap agresi. Sering tanda titik balik.
  • Cumulative delta divergence = harga bikin high baru tapi delta enggak → gerakan lemah, hati-hati reversal.

Cara pasang: ganti chart type ke footprint (butuh Premium+), atur view template (delta/imbalance/volume).

DOM (Depth of Market)

Tangga order book: bid/ask yang lagi antri di tiap level beserta size-nya. Buat scalping, liat likuiditas, deteksi spoofing/iceberg. Butuh feed L2 yang dukung.

Time & Sales (tape)

Stream transaksi yang udah kejadian: waktu, harga, size. Ada filter size dan timezone. Baca print agresif (beli di ask vs jual di bid) buat lengkapin baca delta footprint.

BATAS YANG WAJIB LO TAU (kejujuran)

Ini yang sering disembunyiin:

Order flow TradingView BUKAN tick-level asli. Transaksi diagregasi ke ~1 detik.

Artinya: lo kehilangan sebagian detail real-time dibanding platform khusus order flow kayak Sierra Chart, Bookmap, ATAS, Jigsaw. Buat konteks dan belajar, TradingView oke. Buat scalping murni baca tape detik-detikan, TradingView lebih lemah.

Dan buat IDX spesifik:

  • Footprint/DOM/Time & Sales butuh data real-time + L2 yang memadai. Data IDX real-time = add-on berbayar terpisah.
  • Banyak feed equity gak punya L2 selengkap futures/crypto. Jadi DOM IDX bisa terbatas.

Buat crypto (BINANCE:BTCUSDT) data real-time + delta footprint biasanya tersedia tanpa add-on — beda jauh sama IDX equity yang butuh feed berbayar.

Konteks Crypto

Kabar baik buat lo yang main crypto: di pair likuid kayak BTCUSDT/ETHUSDT, DOM + footprint + delta jalan real-time tanpa add-on. Order flow di crypto justru tempat tools ini paling berguna; keterbatasan di atas terutama soal IDX equity. Naikin crypto jadi contoh utama lo, IDX jadi pengecualian.

Kesimpulan jujur

Kalau ada yang jual "order flow tick-level di TradingView", dia gak jujur. TradingView agregasi ~1 detik. Pakai TradingView buat baca konteks order flow (delta, imbalance, absorption di level penting), bukan buat tape-reading detik-detikan profesional. Buat itu, lo butuh platform khusus.

Latihan

  1. Kalau punya akses Premium: buka footprint di saham likuid (BBCA), cari satu bar dengan delta jelas positif tapi harga turun. Apa artinya?
  2. Buka Time & Sales di chart aktif, set filter size besar, amati print-print gede. Di bid atau ask?
  3. Tulis 2 kalimat: kenapa lo gak boleh percaya klaim 'tick-level order flow' di TradingView?
  4. Buka footprint di BINANCE:BTCUSDT (data real-time tanpa add-on). Cari satu bar absorption: volume gede di satu level tapi harga gak nembus. Bandingin betapa lebih lengkap order flow crypto ketimbang DOM IDX equity yang terbatas.

Pro Tips

  • JUJUR: order flow TradingView diagregasi ~1 detik, BUKAN tick asli. Cukup buat konteks, lemah buat scalping tape murni.
  • Absorption = volume gede + harga diam = limit order nyerap. Sering titik balik. Sinyal order flow paling berguna.
  • Cumulative delta divergence (harga high baru, delta enggak) = gerakan lemah. Waspada reversal.
  • Footprint butuh Premium+, dan DOM/T&S butuh real-time L2 — buat IDX itu add-on terpisah. Cek badge 'D' (delayed).

12. Debugging dengan Pine Logs

Dulu orang debug Pine pakai plot() nilai variabel atau bikin label.new() — ribet. Sekarang ada cara modern: Pine Logs.

log.info / warning / error

//@version=6
indicator("Debug pakai Logs")

rsi = ta.rsi(close, 14)

log.info("Bar {0}: RSI = {1}", bar_index, rsi)

if rsi > 70
    log.warning("RSI overbought: {0} di bar {1}", rsi, bar_index)

if na(rsi)
    log.error("RSI na di bar {0}!", bar_index)
  • {0}, {1} = placeholder, diisi argumen berikutnya sesuai urutan. Mirip str.format.
  • Output muncul di tab Pine Logs (panel bawah, sebelah Pine Editor / Strategy Tester).
  • log.info (info biasa), log.warning (kuning, perhatian), log.error (merah, masalah).

Kenapa Logs lebih baik dari plot debug

  • Bisa log string, gak cuma angka (plot cuma angka).
  • Gak ngotorin chart.
  • Ada timestamp dan nomor bar otomatis.
  • Bisa filter by level (info/warning/error).

Hati-hati: log tiap bar = banjir

Log di tiap bar bikin ribuan baris. Batasi:

if barstate.islast
    log.info("Nilai final RSI: {0}", rsi)

// atau cuma pas kondisi tertentu
if ta.crossover(ta.ema(close,12), ta.ema(close,26))
    log.info("Golden cross di bar {0}, harga {1}", bar_index, close)

Error compiler umum (muncul pas save, kode gak jalan)

ErrorArtinyaFix
Mismatched inputsintaks/indentasi salahcek indentasi (pakai spasi konsisten), kurung
Undeclared identifiervariabel belum dideklarasideklarasi dulu, cek typo nama
Cannot call X with argument...tipe argumen salahcek tipe (series vs simple, float vs int)
Could not find function or function referencenama fungsi salah/namespace lupata.rsi bukan rsi, dst

Error runtime umum (muncul pas eksekusi)

ErrorArtinyaFix
Pine cannot determine the referencing length...akses bar terlalu jauhnaikin max_bars_back
Loop takes too longloop beratoptimasi, kurangi iterasi
Memory limits exceededkebanyakan objekkurangi label/line, hapus yang lama
Array index out of boundsakses index gak adacek size() sebelum get()

Alur debug yang disaranin

  1. Save → baca error compiler kalau ada (paling atas).
  2. Kalau lolos compile tapi hasil aneh → tabur log.info di titik kunci.
  3. Buka tab Pine Logs, telusuri nilai per bar.
  4. Persempit: di bar mana nilai mulai salah? Mundur dari situ.

Latihan

  1. Tambahin log.info ke indikator yang udah lo bikin, log RSI + bar_index, cuma pas RSI > 70 (log.warning).
  2. Sengaja bikin error 'Undeclared identifier' (pakai variabel yang belum dideklarasi), baca pesannya, lalu benerin.
  3. Pakai log buat nyari di bar mana golden cross pertama terjadi di chart BBCA.

Pro Tips

  • log.info pakai 0,1 placeholder. Bisa log string + angka sekaligus, jauh lebih enak dari plot debug.
  • Jangan log tiap bar — banjir ribuan baris. Bungkus dalam barstate.islast atau kondisi spesifik.
  • Error compiler = kode gak jalan sama sekali (pas save). Error runtime = jalan tapi mati di tengah (pas eksekusi). Beda penanganan.
  • 'Cannot call X with argument' hampir selalu soal series vs simple. Balik ke bagian 8.

13. PROYEK AKHIR: Dashboard MTF Multi-Simbol IDX dengan UDT

Saatnya gabungin SEMUA. Kita bangun dashboard yang nampilin trend + RSI buat beberapa saham IDX, di tiga timeframe (Daily / Weekly / Monthly), pakai UDT buat state tiap simbol, request.security yang gak repaint, dan table sebagai output.

Desain dulu, baru ngoding (disiplin engineer)

Kebutuhan:

  • Array simbol IDX: BBCA, BBRI, TLKM, ASII.
  • Tiap simbol: trend (naik/turun, berbasis EMA50) + RSI(14), di D/W/M.
  • Output: tabel, baris = simbol, kolom = metrik per timeframe.
  • Anti-repaint: expr[1] + lookahead_off.
  • Hemat kuota: gabung trend+RSI jadi tuple per timeframe (1 panggilan, bukan 2).

Hitung kuota request: 4 simbol x 3 timeframe x 1 tuple = 12 panggilan. Aman di bawah limit ~40. Kalau gue gak pakai tuple: 4 x 3 x 2 = 24, masih aman tapi lebih boros. Tuple lebih baik.

Kode lengkap

//@version=6
indicator("Dashboard MTF Saham IDX", overlay=true)

// ---- enum buat trend (gantiin string ajaib) ----
enum Trend
    up
    down

// ---- UDT: state satu simbol ----
type SymState
    string ticker
    Trend  tD
    float  rD
    Trend  tW
    float  rW
    Trend  tM
    float  rM

// ---- helper: ambil [trend, rsi] satu timeframe, anti-repaint ----
f_tfData(simbol, tf) =>
    [emaVal, rsiVal, closeVal] = request.security(simbol, tf,
         [ta.ema(close, 50)[1], ta.rsi(close, 14)[1], close[1]],
         lookahead = barmerge.lookahead_off)
    tr = closeVal > emaVal ? Trend.up : Trend.down
    [tr, rsiVal]

// ---- daftar simbol IDX ----
var simbols = array.from("IDX:BBCA", "IDX:BBRI", "IDX:TLKM", "IDX:ASII")

// ---- bangun state tiap simbol (dynamic request v6) ----
var states = array.new<SymState>()

if barstate.islast
    states.clear()
    for s in simbols
        [tD, rD] = f_tfData(s, "D")
        [tW, rW] = f_tfData(s, "W")
        [tM, rM] = f_tfData(s, "M")
        states.push(SymState.new(s, tD, rD, tW, rW, tM, rM))

// ---- helper render warna ----
f_trendTxt(Trend t) => t == Trend.up ? "UP" : "DN"
f_trendCol(Trend t) => t == Trend.up ? color.new(color.green,0) : color.new(color.red,0)
f_rsiCol(float r)    => r > 70 ? color.red : r < 30 ? color.green : color.gray

// ---- table ----
var t = table.new(position.top_right, columns=7, rows=6,
                  bgcolor=color.new(color.black, 10), border_width=1)

if barstate.islast
    // header
    table.cell(t, 0, 0, "Simbol", text_color=color.white, bgcolor=color.new(color.gray,40))
    table.cell(t, 1, 0, "D trend", text_color=color.white, bgcolor=color.new(color.gray,40))
    table.cell(t, 2, 0, "D RSI",   text_color=color.white, bgcolor=color.new(color.gray,40))
    table.cell(t, 3, 0, "W trend", text_color=color.white, bgcolor=color.new(color.gray,40))
    table.cell(t, 4, 0, "W RSI",   text_color=color.white, bgcolor=color.new(color.gray,40))
    table.cell(t, 5, 0, "M trend", text_color=color.white, bgcolor=color.new(color.gray,40))
    table.cell(t, 6, 0, "M RSI",   text_color=color.white, bgcolor=color.new(color.gray,40))

    row = 1
    for st in states
        nama = str.replace_all(st.ticker, "IDX:", "")
        table.cell(t, 0, row, nama, text_color=color.white)
        table.cell(t, 1, row, f_trendTxt(st.tD), text_color=color.white, bgcolor=f_trendCol(st.tD))
        table.cell(t, 2, row, str.tostring(st.rD, "#.#"), text_color=f_rsiCol(st.rD))
        table.cell(t, 3, row, f_trendTxt(st.tW), text_color=color.white, bgcolor=f_trendCol(st.tW))
        table.cell(t, 4, row, str.tostring(st.rW, "#.#"), text_color=f_rsiCol(st.rW))
        table.cell(t, 5, row, f_trendTxt(st.tM), text_color=color.white, bgcolor=f_trendCol(st.tM))
        table.cell(t, 6, row, str.tostring(st.rM, "#.#"), text_color=f_rsiCol(st.rM))
        row += 1

Penjelasan baris-per-baris (bagian kunci)

  • enum Trendup/down typed, gak pakai string "UP" yang rawan typo.
  • type SymState — satu objek nampung semua state satu simbol (7 field). Ini inti kenapa UDT: 4 simbol = 4 objek rapi, bukan 28 variabel berserakan.
  • f_tfData — helper anti-repaint. Perhatiin [1] di SETIAP ekspresi dalam tuple, plus lookahead_off. Satu request.security narik 3 nilai sekaligus (ema, rsi, close) = hemat kuota. Trend dihitung dari close[1] > ema[1].
  • array.from(...) — daftar simbol. Loop for s in simbols pakai dynamic request (v6) — simbol-nya series, gak bisa di v5.
  • states.clear() + push dalam barstate.islast — bangun ulang state cuma di bar terakhir (yang penting buat dashboard). Gak perlu tiap bar.
  • table render — koordinat (kolom, baris), warna trend ijo/merah, RSI merah kalau >70 ijo kalau <30.
  • str.replace_all(..., "IDX:", "") — rapiin nama, buang prefix exchange.

Validasi (verification before completion)

Jangan ngaku selesai sebelum:

  1. Compile bersih — save, gak ada error merah.
  2. Tabel muncul di pojok kanan atas dengan 4 baris simbol + header.
  3. Cek repaint — refresh chart beberapa kali. Nilai gak boleh loncat-loncat (karena pakai [1]).
  4. Cek kuota — 12 panggilan request, jauh di bawah 40. Aman.
  5. Sanity check — buka chart BBCA Daily manual, bandingin RSI-nya sama yang di tabel (toleransi beda 1 bar karena [1]).

Pengembangan (kalau mau lanjut)

  • Tambah kolom "Sinyal": BUY kalau ketiga timeframe trend UP + RSI Daily < 70.
  • Tambah saham (inget cek kuota: tiap simbol +3 panggilan).
  • Pakai map<string, SymState> kalau mau lookup state by ticker.
  • Tambah log.info per simbol buat audit nilai di Pine Logs.

Latihan

  1. Jalanin kode dashboard lengkap. Pastiin compile bersih dan tabel muncul dengan 4 simbol.
  2. Tambah saham UNVR dan BMRI ke array simbol. Hitung ulang kuota request — masih aman?
  3. Tambah kolom 'Sinyal' (BUY/WAIT): BUY kalau tD, tW, tM semua Trend.up DAN rD < 70.
  4. Buktiin anti-repaint: refresh chart 5x, konfirmasi nilai RSI di tabel gak berubah-ubah posisinya.
  5. Ganti array<SymState> jadi map<string, SymState> dengan key ticker, lalu lookup state BBCA dan log RSI Daily-nya.

Pro Tips

  • Hitung kuota request SEBELUM ngoding: simbol x timeframe. Tuple (gabung nilai) bikin 1 panggilan, bukan banyak. 12 < 40, aman.
  • Tiap ekspresi di request.security pakai [1] + lookahead_off. Ini yang bedain dashboard jujur dari yang repaint.
  • UDT bikin 4 simbol jadi 4 objek rapi, bukan 28 variabel paralel. Itu seluruh alasan UDT ada.
  • Bangun state dalam barstate.islast doang. Dashboard cuma perlu nilai terakhir, gak perlu hitung tiap bar.
  • Sebelum ngaku selesai: compile bersih, tabel muncul, refresh gak repaint, RSI cocok sama chart manual. Bukti dulu, baru klaim.

Kuis

Di Pine, kalau lo nulis 'b = a' di mana a adalah objek UDT, lalu lo ubah 'b.rsi := 99', apa yang terjadi sama a.rsi?
  • A. a.rsi tetap nilai lamanya karena b adalah kopian
  • B. a.rsi ikut jadi 99 karena a dan b nunjuk objek yang sama (reference)
  • C. Error: gak bisa assign objek ke variabel
  • D. a.rsi jadi na
Answer

a.rsi ikut jadi 99 karena a dan b nunjuk objek yang sama (reference)

Objek UDT pakai reference semantics. 'b = a' bikin b dan a nunjuk ke objek YANG SAMA, jadi ubah b ikut ubah a. Kalau mau salinan terpisah, pakai a.copy(). Ini bug nomor satu pemula UDT.

Kenapa banyak indikator MTF dari komunitas 'bohong' (repaint), dan gimana cara paling aman menghindarinya di request.security?
  • A. Karena pakai timeframe terlalu tinggi; solusinya turunin timeframe
  • B. Karena nilai bar HTF yang belum tutup masih berubah; solusinya pakai expr[1] dengan barmerge.lookahead_off
  • C. Karena lupa pakai var; solusinya tambahin var di semua variabel
  • D. Karena data delay 15 menit; solusinya beli data real-time
Answer

Karena nilai bar HTF yang belum tutup masih berubah; solusinya pakai expr[1] dengan barmerge.lookahead_off

Bar timeframe lebih tinggi yang lagi jalan belum tutup, jadi nilainya masih goyang di real-time padahal di historis keliatan final. Pakai expr[1] (ambil bar HTF terkonfirmasi) + lookahead_off bikin nilainya gak berubah lagi — jujur meski telat satu bar HTF.

Berapa kira-kira batas maksimal panggilan request.security/financial/economic per script, dan apa trik utama buat menghematnya?
  • A. ~10 panggilan; trik: pakai var
  • B. ~100 panggilan; trik: pakai map
  • C. ~40 panggilan; trik: gabung beberapa nilai jadi satu tuple dalam satu request
  • D. Gak ada batas; bebas berapa aja
Answer

~40 panggilan; trik: gabung beberapa nilai jadi satu tuple dalam satu request

Batasnya sekitar 40 panggilan request.* per script. Trik hemat: gabung banyak nilai jadi tuple, misal request.security(sym, tf, [close[1], rsi[1], ema[1]]) — tiga nilai, satu panggilan. Wajib buat dashboard multi-simbol.

Apa pernyataan yang JUJUR tentang order flow (footprint/DOM/Time & Sales) di TradingView?
  • A. TradingView menyediakan order flow tick-level penuh, sama kayak Bookmap
  • B. Order flow TradingView diagregasi ke ~1 detik, bukan tick asli; cukup buat konteks tapi lemah buat scalping tape murni
  • C. Order flow di TradingView gratis dan real-time buat semua exchange termasuk IDX
  • D. Footprint chart tersedia di plan Free
Answer

Order flow TradingView diagregasi ke ~1 detik, bukan tick asli; cukup buat konteks tapi lemah buat scalping tape murni

TradingView mengagregasi transaksi ke ~1 detik, BUKAN tick-level asli. Cukup buat baca konteks (delta, imbalance, absorption) tapi kalah dari platform khusus (Sierra, Bookmap, ATAS, Jigsaw) buat tape-reading. Footprint juga butuh Premium+ dan data real-time (add-on terpisah buat IDX).

Dalam Volume Profile, apa itu POC dan kenapa penting?
  • A. Point of Close: harga penutupan terakhir; penting buat entry
  • B. Point of Control: level harga dengan volume terbanyak; berfungsi sebagai magnet/harga wajar
  • C. Percent of Change: persentase perubahan harga; penting buat momentum
  • D. Price of Candle: tinggi candle; penting buat volatilitas
Answer

Point of Control: level harga dengan volume terbanyak; berfungsi sebagai magnet/harga wajar

POC = Point of Control = level harga dengan volume transaksi tertinggi. Dia jadi 'harga wajar' dan magnet — harga sering balik ke POC. Dipadu VAH/VAL (batas value area ~70% volume) dan HVN/LVN, ini fondasi baca Volume Profile.

Lo dapet error 'Cannot call request.security... a series string was used but a simple string is expected' di Pine v5. Apa akar masalahnya?
  • A. max_bars_back terlalu kecil, harus dinaikin
  • B. Argumen timeframe yang harusnya tetap (simple) malah jadi series karena tergantung nilai per-bar
  • C. Kebanyakan label, harus dihapus
  • D. Loop terlalu lama
Answer

Argumen timeframe yang harusnya tetap (simple) malah jadi series karena tergantung nilai per-bar

Beberapa argumen butuh tipe 'simple' (tetap sepanjang script), bukan 'series' (berubah per bar). Di v5, timeframe request.security harus simple string. Pakai input.timeframe() atau literal, jangan ekspresi yang tergantung close/bar. Catatan: v6 dynamic request baru ngebolehin series timeframe.