1Copy the scriptPress Copy script above, or the copy button on the code below.
2Open the Pine EditorOpen a TradingView chart and choose Pine Editor at the bottom of the screen.
3Paste over the templateCreate a new indicator, select all of the template text and paste. Press Save and give it a name.
4Add it to the chartPress Add to chart, then adjust the settings with the gear icon next to its name.
Source Code
1,484 lines of Pine Script v6 from src/PapanGerak.pine, published under MIT. Keep the author's credit and licence lines when you reuse it.
PapanGerak.pine
// @version=6
// 01-base.pine — Papan Gerak: Header, Inputs, Tema, Utilities
// ==========================================================
// Papan Gerak — Technical Analysis Dashboard
// Author : Muhammad Akhmal (AKHMFZ)
// Contact: akhmfz.analytics@gmail.com
// License: MIT
// Built : Pine Script v6 for TradingView
// ==========================================================
// --- Indicator Definition ---
indicator(
"Saham: Papan Gerak By. Akhmfz",
shorttitle = "P.Gerak",
overlay = false,
format = format.price,
precision = 0
)
// ==========================================================
// 1. SETTINGS — General
// ==========================================================
// --- Display ---
tablePositionInput = input.string(
"Top Right",
"Table Position",
options = ["Top Left", "Top Right", "Bottom Left", "Bottom Right"],
display = display.data_window
)
fontSizeInput = input.int(1, "Font Size", minval = 1, maxval = 3, display = display.data_window)
modeInput = input.string("Standard", "Mode", options = ["Compact", "Standard", "Detailed"], display = display.data_window)
bahasaInput = input.string("Indonesia", "Bahasa", options = ["Indonesia", "English"], display = display.data_window)
colorThemeInput = input.string("Gelap", "Color Theme", options = ["Gelap", "Terang", "Bursa Hijau", "Biru Nusantara", "Emas Premium"], display = display.data_window)
// ==========================================================
// 2. SETTINGS — Trend Parameters
// ==========================================================
emaFastPeriod = input.int(20, "EMA Fast Period", group = "Trend", display = display.data_window)
emaMidPeriod = input.int(50, "EMA Mid Period", group = "Trend", display = display.data_window)
emaSlowPeriod = input.int(200, "EMA Slow Period", group = "Trend", display = display.data_window)
superTrendFactor = input.float(3.0, "SuperTrend Factor", step = 0.1, group = "Trend", display = display.data_window)
superTrendPeriod = input.int(10, "SuperTrend ATR Period", group = "Trend", display = display.data_window)
adxLength = input.int(14, "ADX Length", group = "Trend", display = display.data_window)
adxThreshold = input.int(25, "ADX Threshold", group = "Trend", display = display.data_window)
// ==========================================================
// 3. SETTINGS — Momentum Parameters
// ==========================================================
rsiLength = input.int(14, "RSI Length", group = "Momentum", display = display.data_window)
rsiOb = input.int(70, "RSI Overbought", group = "Momentum", display = display.data_window)
rsiOs = input.int(30, "RSI Oversold", group = "Momentum", display = display.data_window)
macdFast = input.int(12, "MACD Fast", group = "Momentum", display = display.data_window)
macdSlow = input.int(26, "MACD Slow", group = "Momentum", display = display.data_window)
macdSignal = input.int(9, "MACD Signal", group = "Momentum", display = display.data_window)
stochK = input.int(14, "Stochastic K", group = "Momentum", display = display.data_window)
stochD = input.int(3, "Stochastic D", group = "Momentum", display = display.data_window)
stochOb = input.int(80, "Stoch Overbought", group = "Momentum", display = display.data_window)
stochOs = input.int(20, "Stoch Oversold", group = "Momentum", display = display.data_window)
// ==========================================================
// 4. SETTINGS — Volatility Parameters
// ==========================================================
atrLength = input.int(14, "ATR Length", group = "Volatility", display = display.data_window)
bbLength = input.int(20, "Bollinger Length", group = "Volatility", display = display.data_window)
bbMult = input.float(2.0, "Bollinger Multiplier", step = 0.1, group = "Volatility", display = display.data_window)
chopLength = input.int(14, "Choppiness Length", group = "Volatility", display = display.data_window)
// ==========================================================
// 5. SETTINGS — Volume Parameters
// ==========================================================
volMaLength = input.int(20, "Volume MA Length", group = "Volume", display = display.data_window)
volSpikeMult = input.float(2.0, "Volume Spike Multiplier", step = 0.1, group = "Volume", display = display.data_window)
// ==========================================================
// 6. SETTINGS — Scoring Weights
// ==========================================================
weightTrend = input.int(30, "Trend Weight (%)", minval = 0, maxval = 100, step = 5, group = "Weights", display = display.data_window)
weightMomentum = input.int(30, "Momentum Weight (%)", minval = 0, maxval = 100, step = 5, group = "Weights", display = display.data_window)
weightVolatility = input.int(20, "Volatility Weight (%)", minval = 0, maxval = 100, step = 5, group = "Weights", display = display.data_window)
weightVolume = input.int(20, "Volume Weight (%)", minval = 0, maxval = 100, step = 5, group = "Weights", display = display.data_window)
// ==========================================================
// 7. SETTINGS — Smart Money (Optional)
// ==========================================================
enableSmartMoney = input.bool(false, "Enable Smart Money Flow", group = "Smart Money", display = display.data_window)
weightSmartMoney = input.int(20, "SM Weight (%)", minval = 0, maxval = 100, step = 5, group = "Smart Money", display = display.data_window)
smVolumeThreshold = input.float(2.5, "SM Volume Z-Threshold", step = 0.1, group = "Smart Money", display = display.data_window)
smPeriod = input.int(20, "SM Lookback Period", group = "Smart Money", display = display.data_window)
// ==========================================================
// 8. SETTINGS — Signal Parameters
// ==========================================================
minSignalBars = input.int(5, "Min Bars Between Signals", minval = 1, maxval = 50, group = "Signal", display = display.data_window, tooltip = "Jarak minimal antar sinyal (dalam bar). Mencegah sinyal beruntun terlalu rapat.")
signalFilterMode = input.string("Disabled", "Signal Filter", options = ["Disabled", "Only Trending (Chop<38)", "Only Ranging (Chop>62)"], group = "Signal", display = display.data_window, tooltip = "Hanya izinkan sinyal saat market trending (Chop<38) atau ranging (Chop>62). Nonaktifkan untuk semua kondisi.")
confirmOnBarClose = input.bool(true, "Confirm on Bar Close", group = "Signal", display = display.data_window, tooltip = "Hanya kirim alert saat candle benar-benar tutup. Mencegah sinyal/alert palsu di tengah bar berjalan (repaint). Nonaktifkan untuk respons real-time lebih cepat.")
// ==========================================================
// 9. SETTINGS — Risk Management
// ==========================================================
showRiskLevels = input.bool(true, "Show Risk Levels", group = "Risk Mgmt", display = display.data_window, tooltip = "Tampilkan baris Level Referensi (SL/Target) di tabel. Nonaktifkan untuk mode scan cepat.")
slTypeInput = input.string("ATR", "SL Type", options = ["ATR", "Swing Low/High", "Both (Wider)"], group = "Risk Mgmt", display = display.data_window, tooltip = "Metode Stop Loss: ATR-based, Swing Low/High, atau kombinasi (ambil yang lebih jauh).")
slAtrMult = input.float(1.5, "SL ATR Multiplier", minval = 0.5, maxval = 5.0, step = 0.1, group = "Risk Mgmt", display = display.data_window, tooltip = "Pengali ATR untuk SL. Semakin besar semakin longgar. Contoh: 1.5 × ATR(14).")
slSwingPeriod = input.int(10, "SL Swing Lookback", minval = 5, maxval = 50, group = "Risk Mgmt", display = display.data_window, tooltip = "Jumlah bar untuk mencari swing low/high sebagai referensi SL. Makin panjang makin jauh dari entry.")
targetR = input.float(2.0, "Target R-Multiple", minval = 0.5, maxval = 10.0, step = 0.5, group = "Risk Mgmt", display = display.data_window, tooltip = "Target take-profit sebagai kelipatan risiko. 2R = profit 2× dari jarak SL.")
// ==========================================================
// 10. SETTINGS — Position Sizing
// ==========================================================
showPositionSizing = input.bool(false, "Show Position Sizing", group = "Position Size", display = display.data_window, tooltip = "Tampilkan kalkulator ukuran posisi (dalam lot) berdasarkan balance dan risk %.")
accountBalanceInput = input.int(50000000, "Account Balance (Rp)", minval = 1000000, step = 1000000, group = "Position Size", display = display.data_window, tooltip = "Total modal trading dalam Rupiah. Digunakan untuk menghitung jumlah lot per sinyal.")
riskPctInput = input.float(1.0, "Risk Per Trade (%)", minval = 0.1, maxval = 5.0, step = 0.1, group = "Position Size", display = display.data_window, tooltip = "Persentase modal yang siap dirisikokan per trade. Contoh: 1% dari Rp50jt = Rp500rb.")
// ==========================================================
// 11. SETTINGS — Multi-Timeframe Alignment
// ==========================================================
enableMTF = input.bool(true, "Enable MTF Alignment", group = "MTF", display = display.data_window, tooltip = "Aktifkan Multi-Timeframe Alignment Score. Menghitung alignment 3 timeframe untuk konfirmasi sinyal.")
mtfPrimaryTF = input.string("D", "Primary TF", options = ["D", "W", "M"], group = "MTF", display = display.data_window, tooltip = "Primary timeframe — entry timing. Default: Daily.")
mtfSecondaryTF = input.string("W", "Secondary TF", options = ["D", "W", "M"], group = "MTF", display = display.data_window, tooltip = "Secondary timeframe — trend konfirmasi. Default: Weekly.")
mtfTertiaryTF = input.string("M", "Tertiary TF", options = ["D", "W", "M"], group = "MTF", display = display.data_window, tooltip = "Tertiary timeframe — konteks makro. Default: Monthly.")
mtfPrimaryWeight = input.float(0.50, "Primary Weight", minval = 0.0, maxval = 1.0, step = 0.05, group = "MTF", display = display.data_window, tooltip = "Bobot primary timeframe dalam skor akhir. Default: 0.50 (50%).")
mtfSecondaryWeight = input.float(0.35, "Secondary Weight", minval = 0.0, maxval = 1.0, step = 0.05, group = "MTF", display = display.data_window, tooltip = "Bobot secondary timeframe. Default: 0.35 (35%).")
mtfTertiaryWeight = input.float(0.15, "Tertiary Weight", minval = 0.0, maxval = 1.0, step = 0.05, group = "MTF", display = display.data_window, tooltip = "Bobot tertiary timeframe. Default: 0.15 (15%).")
mtfAlpha = input.float(0.70, "Directional Alpha", minval = 0.0, maxval = 1.0, step = 0.05, group = "MTF", display = display.data_window, tooltip = "Bobot directional components (Trend+Momentum) vs context (Volatility+Volume). Default: 0.70.")
mtfMaxPenalty = input.float(0.25, "Max Conflict Penalty", minval = 0.0, maxval = 0.50, step = 0.05, group = "MTF", display = display.data_window, tooltip = "Penalti maksimum saat timeframe saling bertentangan. 0 = tanpa penalti, 0.25 = 25% maks.")
mtfDirectionHysteresis = input.float(10.0, "Direction Hysteresis", minval = 0.0, maxval = 30.0, step = 1.0, group = "MTF", display = display.data_window, tooltip = "Ambang hysteresis untuk direction flag. Lebih tinggi = lebih jarang flip direction. Default: 10.")
mtfDebug = input.bool(false, "Debug Mode (detail semua komponen)", group = "MTF", display = display.data_window, tooltip = "Jika aktif, tampilkan semua komponen MTF di Data Window untuk debugging.")
// Legacy MTF filter (retained for backward compat — trend row & narrative)
enableMtfFilter = input.bool(false, "Enable MTF Trend Filter (legacy)", group = "MTF", display = display.data_window, tooltip = "Legacy: filter tren Multi-Timeframe sederhana. Sinyal harian dibandingkan dengan tren higher TF.")
mtfTimeframe = input.string("W", "MTF Timeframe (legacy)", options = ["W", "M"], group = "MTF", display = display.data_window, tooltip = "Timeframe legacy MTF. W = Weekly, M = Monthly.")
// ==========================================================
// 12. SETTINGS — Entry Trigger
// ==========================================================
entryTriggerMode = input.string("Composite Score", "Entry Trigger", options = ["Composite Score", "Pullback", "Breakout"], group = "Entry Trigger", display = display.data_window, tooltip = "Mode sinyal: Composite Score (zona 40/70), Pullback (entry saat retrace), atau Breakout (saat harga tembus level support/resistance).")
// ==========================================================
// 13. SETTINGS — Webhook / Alert Format
// ==========================================================
webhookFormat = input.string("Simple", "Alert Format", options = ["Simple", "JSON"], group = "Webhook", display = display.data_window, tooltip = "Format alert: Simple (teks biasa) atau JSON (PG|key=value untuk integrasi TradersPost/Pine Connector).")
// ==========================================================
// 14. COLOR THEMES
// ==========================================================
var color bgColor = na
var color headerColor = na
var color labelColor = na
var color scoreColor = na
var color tableBgColor = na
var color bullColor = na
var color bearColor = na
var color neutralColor = na
var color signalLineColor = na
var color dimTrend = na
var color dimMomentum = na
var color dimVolatility = na
var color dimVolume = na
if colorThemeInput == "Gelap"
bgColor := color.rgb(0, 0, 0, 85)
headerColor := color.rgb(38, 166, 154)
labelColor := color.white
scoreColor := color.rgb(38, 166, 154)
tableBgColor := color.rgb(13, 22, 30, 90)
bullColor := color.rgb(38, 166, 154)
bearColor := color.rgb(239, 83, 80)
neutralColor := color.rgb(255, 202, 40)
signalLineColor := color.rgb(100, 100, 100)
dimTrend := color.rgb(66, 165, 245)
dimMomentum := color.rgb(255, 202, 40)
dimVolatility := color.rgb(239, 83, 80)
dimVolume := color.rgb(126, 87, 194)
else
if colorThemeInput == "Terang"
bgColor := color.rgb(255, 255, 255, 85)
headerColor := color.rgb(0, 150, 136)
labelColor := color.rgb(33, 33, 33)
scoreColor := color.rgb(0, 150, 136)
tableBgColor := color.rgb(245, 245, 245, 90)
bullColor := color.rgb(0, 150, 136)
bearColor := color.rgb(211, 47, 47)
neutralColor := color.rgb(255, 160, 0)
signalLineColor := color.rgb(158, 158, 158)
dimTrend := color.rgb(33, 150, 243)
dimMomentum := color.rgb(255, 193, 7)
dimVolatility := color.rgb(244, 67, 54)
dimVolume := color.rgb(156, 39, 176)
else
if colorThemeInput == "Bursa Hijau"
bgColor := color.rgb(0, 30, 0, 85)
headerColor := color.rgb(76, 175, 80)
labelColor := color.white
scoreColor := color.rgb(76, 175, 80)
tableBgColor := color.rgb(0, 20, 0, 90)
bullColor := color.rgb(76, 175, 80)
bearColor := color.rgb(244, 67, 54)
neutralColor := color.rgb(255, 235, 59)
signalLineColor := color.rgb(100, 100, 100)
dimTrend := color.rgb(129, 199, 132)
dimMomentum := color.rgb(255, 235, 59)
dimVolatility := color.rgb(239, 83, 80)
dimVolume := color.rgb(186, 104, 200)
else
if colorThemeInput == "Biru Nusantara"
bgColor := color.rgb(0, 20, 50, 85)
headerColor := color.rgb(41, 98, 255)
labelColor := color.white
scoreColor := color.rgb(41, 98, 255)
tableBgColor := color.rgb(0, 10, 30, 90)
bullColor := color.rgb(41, 98, 255)
bearColor := color.rgb(255, 82, 82)
neutralColor := color.rgb(255, 202, 40)
signalLineColor := color.rgb(100, 100, 100)
dimTrend := color.rgb(100, 181, 246)
dimMomentum := color.rgb(255, 202, 40)
dimVolatility := color.rgb(239, 83, 80)
dimVolume := color.rgb(179, 136, 255)
else
bgColor := color.rgb(30, 20, 0, 85)
headerColor := color.rgb(255, 193, 7)
labelColor := color.rgb(33, 33, 33)
scoreColor := color.rgb(255, 193, 7)
tableBgColor := color.rgb(20, 15, 0, 90)
bullColor := color.rgb(76, 175, 80)
bearColor := color.rgb(244, 67, 54)
neutralColor := color.rgb(255, 193, 7)
signalLineColor := color.rgb(158, 158, 158)
dimTrend := color.rgb(255, 183, 77)
dimMomentum := color.rgb(255, 213, 79)
dimVolatility := color.rgb(239, 83, 80)
dimVolume := color.rgb(206, 147, 216)
// ==========================================================
// 10. UTILITY FUNCTIONS
// ==========================================================
f_secure(input, def) => na(input) ? def : input
f_scoreHigher(float val, float min, float max) =>
na(val) ? na : math.max(0, math.min(100, (val - min) / (max - min) * 100))
f_scoreLower(float val, float min, float max) =>
na(val) ? na : math.max(0, math.min(100, (max - val) / (max - min) * 100))
f_scoreRange(float val, float min, float max, float idealLow, float idealHigh) =>
if na(val)
na
else
if val >= idealLow and val <= idealHigh
100.0
else
if val < idealLow
if val <= min
0.0
else
(val - min) / (idealLow - min) * 100
else
if val >= max
0.0
else
(max - val) / (max - idealHigh) * 100
f_colorScore(float score) =>
if na(score)
color.gray
else
if score >= 70
bullColor
else
if score >= 40
neutralColor
else
bearColor
f_signalText(float score, string lang) =>
if na(score)
lang == "Indonesia" ? "N/A" : "N/A"
else
if score >= 70
lang == "Indonesia" ? "BULLISH" : "BULLISH"
else
if score >= 40
lang == "Indonesia" ? "NETRAL" : "NEUTRAL"
else
lang == "Indonesia" ? "BEARISH" : "BEARISH"
// @version=6
// 02-data.pine — Papan Gerak: Market Engine & TA Calculations
// ==========================================================
// ==========================================================
// 1. TREND INDICATORS
// ==========================================================
// --- EMA ---
emaFast = ta.ema(close, emaFastPeriod)
emaMid = ta.ema(close, emaMidPeriod)
emaSlow = ta.ema(close, emaSlowPeriod)
// EMA Position Score component
emaPosition = close > emaFast ? (close > emaMid ? (close > emaSlow ? 2 : 1) : 0) : -1
emaTrend = close > emaSlow ? 1 : -1
// --- SuperTrend ---
[superTrend, superTrendDir] = ta.supertrend(superTrendFactor, superTrendPeriod)
stBullish = superTrendDir == 1
// --- ADX / DMI ---
[adxUp, adxDn, adxValue] = ta.dmi(adxLength, adxLength)
// ADX strength
adxStrong = adxValue >= adxThreshold
adxTrend = adxUp > adxDn ? 1 : -1
// Trend alignment
trendAligned = (adxTrend == 1 and stBullish and emaTrend == 1) or (adxTrend == -1 and not stBullish and emaTrend == -1)
// ==========================================================
// 2. MOMENTUM INDICATORS
// ==========================================================
// --- RSI ---
rsiValue = ta.rsi(close, rsiLength)
rsiObHit = rsiValue >= rsiOb
rsiOsHit = rsiValue <= rsiOs
rsiDirection = ta.rising(rsiValue, 1) ? 1 : -1
// --- MACD ---
[macdLine, macdSignalLine, macdHist] = ta.macd(close, macdFast, macdSlow, macdSignal)
macdBullish = macdLine > macdSignalLine
macdHistRising = macdHist > macdHist[1]
macdZero = macdLine > 0
// --- Stochastic ---
stochKVal = ta.stoch(close, high, low, stochK)
stochDVal = ta.sma(stochKVal, stochD)
stochBullish = stochKVal > stochDVal
stochObHit = stochKVal >= stochOb
stochOsHit = stochKVal <= stochOs
// ==========================================================
// 3. VOLATILITY INDICATORS
// ==========================================================
// --- ATR% (normalized) ---
atrValue = ta.atr(atrLength)
atrPercent = atrValue / close * 100
// --- Bollinger Bands Width ---
bbMiddle = ta.sma(close, bbLength)
bbStdDev = ta.stdev(close, bbLength)
bbUpper = bbMiddle + bbMult * bbStdDev
bbLower = bbMiddle - bbMult * bbStdDev
bbWidth = bbMiddle != 0 ? (bbUpper - bbLower) / bbMiddle * 100 : 0
bbWidthAvg = ta.sma(bbWidth, bbLength)
// Price position within bands (0-100)
bbPosition = bbUpper != bbLower ? (close - bbLower) / (bbUpper - bbLower) * 100 : 50
// --- Choppiness Index ---
f_choppiness(int length) =>
highestHigh = ta.highest(high, length)
lowestLow = ta.lowest(low, length)
atrSum = math.sum(ta.atr(1), length)
range_ = highestHigh - lowestLow
if range_ == 0
100.0
else
math.log(atrSum / range_) / math.log(length) * 100
chopValue = f_choppiness(chopLength)
chopTrending = chopValue <= 38
chopRanging = chopValue >= 62
// ==========================================================
// 4. VOLUME INDICATORS
// ==========================================================
// --- Volume MA ---
volMA = ta.sma(volume, volMaLength)
volRatio = volMA > 0 ? volume / volMA : na
volSpike = volRatio >= volSpikeMult
// --- OBV ---
obvValue = ta.obv
obvMA = ta.sma(obvValue, volMaLength)
obvTrend = obvValue > obvMA ? 1 : -1
obvAgree = not na(close[1]) and ((obvTrend == 1 and close > close[1]) or (obvTrend == -1 and close < close[1]))
// --- Net Volume (proxy) ---
f_netVolume() =>
mfMultiplier = (close - low) - (high - close)
upVol = volume * mfMultiplier / (high - low + 1e-10)
na(upVol) ? 0 : upVol
netVol = f_netVolume()
netVolSmooth = ta.sma(netVol, 5)
// ==========================================================
// 5. SMART MONEY FLOW (Optional)
// ==========================================================
// Institutional flow detection via volume Z-score
f_smartMoneyDetect() =>
if not enableSmartMoney
false
else
volSMA = ta.sma(volume, smPeriod)
volStd = ta.stdev(volume, smPeriod)
if volStd == 0
false
else
zScore = (volume - volSMA) / volStd
zScore >= smVolumeThreshold and close > open
// Volume inside body (institutional) vs at wick (retail)
f_isBodyVolume() =>
bodyRange = math.abs(close - open)
totalRange = high - low
if totalRange == 0
false
else
bodyRange / totalRange >= 0.6 and volSpike
smDetected = f_smartMoneyDetect()
smBodyVol = f_isBodyVolume()
// ==========================================================
// 6. MULTI-TIMEFRAME DATA FETCHERS
// ==========================================================
// Self-contained HTF scoring — semua computed dari built-in vars (close/high/low/volume)
// sehingga aman dipanggil dari dalam request.security(). Tidak mereferensi global vars
// yang sudah di-binding ke primary timeframe.
f_mtfTfScores() =>
emaF = ta.ema(close, emaFastPeriod)
emaM = ta.ema(close, emaMidPeriod)
emaS = ta.ema(close, emaSlowPeriod)
[st, stD] = ta.supertrend(superTrendFactor, superTrendPeriod)
trendScore = 50.0
if close > emaF and close > emaM and close > emaS
trendScore := 80.0
else if close < emaF and close < emaM and close < emaS
trendScore := 20.0
else if close > emaF
trendScore := 60.0
else
trendScore := 40.0
if not na(stD)
trendScore := stD == 1 ? math.min(100, trendScore + 10) : math.max(0, trendScore - 10)
rsi = ta.rsi(close, rsiLength)
[mL, mS, mH] = ta.macd(close, macdFast, macdSlow, macdSignal)
momScore = f_scoreHigher(rsi, 30, 70)
if not na(mL) and not na(mS)
momScore := mL > mS ? math.min(100, momScore + 10) : math.max(0, momScore - 10)
atr = ta.atr(atrLength)
atrPct = atr / close * 100
volScore = f_scoreRange(atrPct, 0.5, 10.0, 1.0, 3.0)
vMA = ta.sma(volume, volMaLength)
vR = vMA > 0 ? volume / vMA : na
volmScore = f_scoreRange(vR, 0.2, 5.0, 0.8, 2.0)
[trendScore, momScore, volScore, volmScore]
mtfTf1Trend = na
mtfTf1Mom = na
mtfTf1Vol = na
mtfTf1Volm = na
mtfTf2Trend = na
mtfTf2Mom = na
mtfTf2Vol = na
mtfTf2Volm = na
mtfTf3Trend = na
mtfTf3Mom = na
mtfTf3Vol = na
mtfTf3Volm = na
if enableMTF
[t1tr, t1mo, t1vo, t1vl] = request.security(syminfo.tickerid, mtfPrimaryTF, f_mtfTfScores(), barmerge.gaps_off, barmerge.lookahead_off)
mtfTf1Trend := t1tr
mtfTf1Mom := t1mo
mtfTf1Vol := t1vo
mtfTf1Volm := t1vl
[t2tr, t2mo, t2vo, t2vl] = request.security(syminfo.tickerid, mtfSecondaryTF, f_mtfTfScores(), barmerge.gaps_off, barmerge.lookahead_off)
mtfTf2Trend := t2tr
mtfTf2Mom := t2mo
mtfTf2Vol := t2vo
mtfTf2Volm := t2vl
[t3tr, t3mo, t3vo, t3vl] = request.security(syminfo.tickerid, mtfTertiaryTF, f_mtfTfScores(), barmerge.gaps_off, barmerge.lookahead_off)
mtfTf3Trend := t3tr
mtfTf3Mom := t3mo
mtfTf3Vol := t3vo
mtfTf3Volm := t3vl
// Legacy MTF trend filter (retained for backward compat)
mtfTrendScore = if enableMtfFilter
request.security(syminfo.tickerid, mtfTimeframe,
(close > ta.ema(close, emaSlowPeriod) ? 50 : 0) +
(close > ta.ema(close, emaMidPeriod) ? 30 : 0) +
(close > ta.ema(close, emaFastPeriod) ? 20 : 0),
barmerge.gaps_off, barmerge.lookahead_off)
else
na
// @version=6
// 03-scoring.pine — Papan Gerak: Scoring Engine + Render
// ==========================================================
// ==========================================================
// 1. TREND SCORE (0-100)
// ==========================================================
f_trendScore() =>
scoreSum = 0.0
count = 0.0
// EMA Position (weight: 3)
pos = emaPosition
if not na(pos)
scoreSum += (pos + 1) / 3 * 100 * 3
count += 3
// SuperTrend Direction (weight: 2)
if not na(superTrendDir)
scoreSum += (superTrendDir == 1 ? 100 : 0) * 2
count += 2
// ADX Strength (weight: 2)
if not na(adxValue)
adxScore = adxStrong ? (adxTrend == 1 ? 80 : 20) : 50
scoreSum += adxScore * 2
count += 2
// Trend Alignment (weight: 1 — sinyal echo, bukan indepen)
if not na(adxValue)
alignScore = trendAligned ? (adxTrend == 1 ? 100 : 0) : 50
scoreSum += alignScore * 1
count += 1
// EMA Slope — rate of change (weight: 1)
// [1] ratio dipilih alih-alih [5] offset agar konsisten di semua timeframe.
// Pada monthly chart, [5] = 5 bulan (~150 hari) tidak relevan untuk slope jangka pendek.
slopeFast = emaFast / emaFast[1] - 1
if not na(slopeFast)
slopeScore = f_scoreHigher(slopeFast, -0.02, 0.02)
scoreSum += slopeScore * 1
count += 1
count > 0 ? scoreSum / count : na
// ==========================================================
// 2. MOMENTUM SCORE (0-100)
// ==========================================================
f_momentumScore() =>
scoreSum = 0.0
count = 0.0
// RSI (weight: 3)
if not na(rsiValue)
rsiScore = f_scoreHigher(rsiValue, 30, 70)
scoreSum += rsiScore * 3
count += 3
// MACD (weight: 3)
if not na(macdHist)
macdScore = 50.0
if macdBullish and macdHistRising
macdScore := macdZero ? 90 : 70
else
if macdBullish
macdScore := macdZero ? 70 : 55
else
if macdHistRising
macdScore := 45
else
macdScore := macdZero ? 35 : 10
scoreSum += macdScore * 3
count += 3
// Stochastic (weight: 2)
if not na(stochKVal)
stochScore = f_scoreHigher(stochKVal, 20, 80)
scoreSum += stochScore * 2
count += 2
// RSI Direction (weight: 1)
if not na(rsiDirection)
dirScore = rsiDirection == 1 ? 70 : 30
scoreSum += dirScore * 1
count += 1
// MACD Histogram Slope (weight: 1)
if not na(macdHist) and not na(macdHist[2])
slopeScore = macdHist > macdHist[2] ? 70 : 30
scoreSum += slopeScore * 1
count += 1
count > 0 ? scoreSum / count : na
// ==========================================================
// 3. VOLATILITY SCORE (0-100)
// ==========================================================
f_volatilityScore() =>
scoreSum = 0.0
count = 0.0
// Bollinger Width vs Average (weight: 3)
if not na(bbWidth) and not na(bbWidthAvg)
// Squeeze = low volatility, Expansion = high volatility
widthRatio = bbWidth / bbWidthAvg
// Ideal: moderate volatility (0.8-1.2x average)
volScore = f_scoreRange(widthRatio, 0.3, 3.0, 0.8, 1.2)
scoreSum += volScore * 3
count += 3
// Choppiness Index (weight: 3)
if not na(chopValue)
// Low chop = trending (good), High chop = ranging (bad)
chopScore = 100 - math.min(100, chopValue)
scoreSum += chopScore * 3
count += 3
// ATR% (weight: 2)
if not na(atrPercent)
// Moderate ATR is ideal
atrScore = f_scoreRange(atrPercent, 0.5, 10.0, 1.0, 3.0)
scoreSum += atrScore * 2
count += 2
// Bollinger Position (weight: 1)
if not na(bbPosition)
// Price near middle = moderate, near bands = extreme
posScore = 100 - math.abs(bbPosition - 50) * 2
scoreSum += posScore * 1
count += 1
count > 0 ? scoreSum / count : na
// ==========================================================
// 4. VOLUME SCORE (0-100)
// ==========================================================
f_volumeScore() =>
scoreSum = 0.0
count = 0.0
// Volume vs MA (weight: 3)
if not na(volRatio)
volScore = f_scoreRange(volRatio, 0.2, 5.0, 0.8, 2.0)
scoreSum += volScore * 3
count += 3
// OBV Agreement (weight: 3)
if not na(obvValue)
obvScore = obvAgree ? 80 : 30
scoreSum += obvScore * 3
count += 3
// Net Volume Direction (weight: 2)
if not na(netVolSmooth)
netScore = netVolSmooth > 0 ? 70 : 30
scoreSum += netScore * 2
count += 2
// Volume Spike Quality (weight: 1)
if not na(volRatio)
spikeScore = 50.0
if volSpike
closeInUpperHalf = close >= (high + low) / 2
netPositive = not na(netVolSmooth) and netVolSmooth > 0
spikeScore := closeInUpperHalf and netPositive ? 90 : closeInUpperHalf or netPositive ? 70 : 30
scoreSum += spikeScore * 1
count += 1
count > 0 ? scoreSum / count : na
// ==========================================================
// 5. SMART MONEY SCORE (Optional, 0-100)
// ==========================================================
f_smartMoneyScore() =>
if not enableSmartMoney
na
else
scoreSum = 0.0
count = 0.0
// SM Detection (weight: 3)
if not na(volume)
scoreSum += (smDetected ? 90 : 50) * 3
count += 3
// Body Volume (weight: 2)
if not na(volume)
scoreSum += (smBodyVol ? 80 : 50) * 2
count += 2
// OBV Alignment (weight: 2)
if not na(obvValue)
scoreSum += (obvAgree ? 75 : 40) * 2
count += 2
// Net Volume (weight: 1)
if not na(netVolSmooth)
scoreSum += (netVolSmooth > 0 ? 70 : 40) * 1
count += 1
count > 0 ? scoreSum / count : na
// ==========================================================
// 6. MULTI-TIMEFRAME ALIGNMENT SCORE
// ==========================================================
// Menghitung alignment 3 timeframe:
// - Directional (Trend+Momentum) untuk conflict detection
// - Context (Volatility+Volume) sebagai modifier non-directional
// - Continuous penalty berdasarkan divergence directional antar TF
//
// Output: [finalScore (0-100), direction (1/-1/0), debugString, tf1Dir, tf2Dir, tf3Dir, penalty]
f_mtfAlignmentScore() =>
if not enableMTF
[na, 0, "", na, na, na, na]
// --- Directional components (Trend + Momentum) ---
tf1Dir = mtfTf1Trend * 0.55 + mtfTf1Mom * 0.45
tf2Dir = mtfTf2Trend * 0.55 + mtfTf2Mom * 0.45
tf3Dir = mtfTf3Trend * 0.55 + mtfTf3Mom * 0.45
// --- Context components (Volatility + Volume) ---
tf1Ctx = mtfTf1Vol * 0.55 + mtfTf1Volm * 0.45
tf2Ctx = mtfTf2Vol * 0.55 + mtfTf2Volm * 0.45
tf3Ctx = mtfTf3Vol * 0.55 + mtfTf3Volm * 0.45
// Guard: jika data tidak tersedia, skip
if na(tf1Dir) or na(tf2Dir)
[na, 0, "", na, na, na, na]
// --- Weighted combination ---
totalW = mtfPrimaryWeight + mtfSecondaryWeight + mtfTertiaryWeight
normW1 = totalW > 0 ? mtfPrimaryWeight / totalW : 0.50
normW2 = totalW > 0 ? mtfSecondaryWeight / totalW : 0.35
normW3 = totalW > 0 ? mtfTertiaryWeight / totalW : 0.15
mtfDirRaw = tf1Dir * normW1 + tf2Dir * normW2 + tf3Dir * normW3
mtfCtxRaw = tf1Ctx * normW1 + tf2Ctx * normW2 + tf3Ctx * normW3
// --- Fuzzy threshold untuk direction detection (symmetric bull/bear) ---
f_bullConf(float score) => math.max(math.min((score - 50) / 20, 1), 0)
f_bearConf(float score) => math.max(math.min((50 - score) / 20, 1), 0)
tf1Bull = f_bullConf(tf1Dir)
tf2Bull = f_bullConf(tf2Dir)
tf3Bull = f_bullConf(tf3Dir)
tf1Bear = f_bearConf(tf1Dir)
tf2Bear = f_bearConf(tf2Dir)
tf3Bear = f_bearConf(tf3Dir)
// Weighted majority vote
weightedBull = tf1Bull * normW1 + tf2Bull * normW2 + tf3Bull * normW3
weightedBear = tf1Bear * normW1 + tf2Bear * normW2 + tf3Bear * normW3
hysteresis = mtfDirectionHysteresis / 100
direction = if weightedBull > weightedBear + hysteresis
1
else if weightedBear > weightedBull + hysteresis
-1
else
0
// --- Continuous penalty (directional divergence only) ---
maxDiv = math.max(math.abs(tf1Dir - tf2Dir), math.abs(tf1Dir - tf3Dir), math.abs(tf2Dir - tf3Dir))
penalty = (maxDiv / 100) * mtfMaxPenalty
conflictScore = mtfDirRaw * (1 - penalty)
finalScore = conflictScore * mtfAlpha + mtfCtxRaw * (1 - mtfAlpha)
// --- Debug string (untuk mtfDebug mode) ---
debugStr = if mtfDebug
"MTF DEBUG\n" +
" Primary (" + mtfPrimaryTF + "): T" + str.tostring(math.round(mtfTf1Trend)) +
" M" + str.tostring(math.round(mtfTf1Mom)) +
" V" + str.tostring(math.round(mtfTf1Vol)) +
" Vo" + str.tostring(math.round(mtfTf1Volm)) + "\n" +
" Secondary (" + mtfSecondaryTF + "): T" + str.tostring(math.round(mtfTf2Trend)) +
" M" + str.tostring(math.round(mtfTf2Mom)) +
" V" + str.tostring(math.round(mtfTf2Vol)) +
" Vo" + str.tostring(math.round(mtfTf2Volm)) + "\n" +
" Tertiary (" + mtfTertiaryTF + "): T" + str.tostring(math.round(mtfTf3Trend)) +
" M" + str.tostring(math.round(mtfTf3Mom)) +
" V" + str.tostring(math.round(mtfTf3Vol)) +
" Vo" + str.tostring(math.round(mtfTf3Volm)) + "\n" +
" Dir: " + (direction == 1 ? "Bullish" : direction == -1 ? "Bearish" : "Mixed") +
" | Pen: " + str.tostring(math.round(penalty * 100)) + "%" +
" | DirRaw: " + str.tostring(math.round(mtfDirRaw)) +
" | CtxRaw: " + str.tostring(math.round(mtfCtxRaw)) +
" | Final: " + str.tostring(math.round(finalScore))
else
""
[finalScore, direction, debugStr, tf1Dir, tf2Dir, tf3Dir, penalty]
[mtfScore, mtfDirection, mtfDebugStr, mtfTf1Dir, mtfTf2Dir, mtfTf3Dir, mtfPenalty] = f_mtfAlignmentScore()
// ==========================================================
// 7. CALCULATE ALL SCORES
// ==========================================================
trendScore = f_trendScore()
momentumScore = f_momentumScore()
volatilityScore = f_volatilityScore()
volumeScore = f_volumeScore()
smartMoneyScore = f_smartMoneyScore()
// Normalize weights
totalWeight = weightTrend + weightMomentum + weightVolatility + weightVolume
normTrend = totalWeight > 0 ? weightTrend / totalWeight * 100 : 25
normMomentum = totalWeight > 0 ? weightMomentum / totalWeight * 100 : 25
normVolatility = totalWeight > 0 ? weightVolatility / totalWeight * 100 : 25
normVolume = totalWeight > 0 ? weightVolume / totalWeight * 100 : 25
float overallScore = na
if not na(trendScore) or not na(momentumScore) or not na(volatilityScore) or not na(volumeScore)
sum = 0.0
wSum = 0.0
if not na(trendScore)
sum += trendScore * normTrend
wSum += normTrend
if not na(momentumScore)
sum += momentumScore * normMomentum
wSum += normMomentum
if not na(volatilityScore)
sum += volatilityScore * normVolatility
wSum += normVolatility
if not na(volumeScore)
sum += volumeScore * normVolume
wSum += normVolume
if not na(smartMoneyScore) and enableSmartMoney
sum += smartMoneyScore * weightSmartMoney
wSum += weightSmartMoney
overallScore := wSum > 0 ? sum / wSum : na
// ==========================================================
// 7. SIGNAL GENERATION
// ==========================================================
signalText = f_signalText(overallScore, bahasaInput)
signalColor = f_colorScore(overallScore)
// ==========================================================
// 8. CONFLUENCE COUNTERS (berapa sub-indikator setuju per dimensi)
// ==========================================================
// Trend
trendBull = 0
trendTotal = 0
if not na(emaPosition)
trendBull += emaPosition >= 1 ? 1 : 0
trendTotal += 1
if not na(superTrendDir)
trendBull += superTrendDir == 1 ? 1 : 0
trendTotal += 1
if not na(adxValue)
trendBull += (adxStrong and adxTrend == 1) ? 1 : 0
trendTotal += 1
if not na(adxValue)
trendBull += trendAligned ? 1 : 0
trendTotal += 1
// Momentum
momBull = 0
momTotal = 0
if not na(rsiValue)
momBull += rsiValue > 50 ? 1 : 0
momTotal += 1
if not na(macdLine)
momBull += macdBullish ? 1 : 0
momTotal += 1
if not na(stochKVal)
momBull += stochBullish ? 1 : 0
momTotal += 1
if not na(rsiDirection)
momBull += rsiDirection == 1 ? 1 : 0
momTotal += 1
if not na(macdHist)
momBull += macdHistRising ? 1 : 0
momTotal += 1
// Volatility ("ideal" = trending/tenang, bukan ranging/liar)
volBull = 0
volTotal = 0
if not na(bbWidth) and not na(bbWidthAvg)
volBull += (bbWidth / bbWidthAvg >= 0.8 and bbWidth / bbWidthAvg <= 1.2) ? 1 : 0
volTotal += 1
if not na(chopValue)
volBull += chopValue <= 38 ? 1 : 0
volTotal += 1
if not na(atrPercent)
volBull += (atrPercent >= 1.0 and atrPercent <= 3.0) ? 1 : 0
volTotal += 1
if not na(bbPosition)
volBull += (bbPosition >= 30 and bbPosition <= 70) ? 1 : 0
volTotal += 1
// Volume
volmBull = 0
volmTotal = 0
if not na(volRatio)
volmBull += volRatio >= 0.8 ? 1 : 0
volmTotal += 1
if not na(obvValue)
volmBull += obvAgree ? 1 : 0
volmTotal += 1
if not na(netVolSmooth)
volmBull += netVolSmooth > 0 ? 1 : 0
volmTotal += 1
if not na(volRatio)
volmBull += volSpike ? 1 : 0
volmTotal += 1
// ==========================================================
// 9. TOOLTIP STRINGS (detail per dimensi untuk hover)
// ==========================================================
// Trend tooltip
trendDetail = "—"
if not na(emaPosition) and not na(trendScore)
emaLabel = emaPosition >= 1 ? "Price > EMA50 ✓" : emaPosition >= 0 ? "Price > EMA20" : "Price < EMAs ✗"
stLabel = not na(superTrendDir) ? (superTrendDir == 1 ? "SuperTrend Up ✓" : "SuperTrend Down ✗") : ""
adxLabel = not na(adxValue) ? (adxStrong ? (adxTrend == 1 ? "ADX " + str.tostring(math.round(adxValue)) + " (strong) ✓" : "ADX " + str.tostring(math.round(adxValue)) + " (strong, down)") : "ADX " + str.tostring(math.round(adxValue)) + " (weak)") : ""
alignLabel = not na(adxValue) ? (trendAligned ? "Aligned ✓" : "Mixed") : ""
trendDetail := emaLabel + " | " + stLabel + " | " + adxLabel + " | " + alignLabel
// Momentum tooltip
momDetail = "—"
if not na(rsiValue) and not na(momentumScore)
rsiLbl = rsiValue >= rsiOb ? "RSI " + str.tostring(math.round(rsiValue)) + " Overbought" : rsiValue <= rsiOs ? "RSI " + str.tostring(math.round(rsiValue)) + " Oversold" : "RSI " + str.tostring(math.round(rsiValue)) + " Netral"
macdLbl = macdBullish ? (macdHistRising ? "MACD Bullish + hist ↑" : "MACD Bullish + hist ↓") : (macdHistRising ? "MACD Bearish + hist ↑" : "MACD Bearish + hist ↓")
stochLbl = not na(stochKVal) ? (stochKVal >= stochOb ? "Stoch Overbought" : stochKVal <= stochOs ? "Stoch Oversold" : "Stoch " + str.tostring(math.round(stochKVal)) + " Netral") : ""
momDetail := rsiLbl + " | " + macdLbl + " | " + stochLbl
// Volatility tooltip
volDetail = "—"
if not na(bbWidth) and not na(atrPercent) and not na(volatilityScore)
bbRatio = bbWidth / bbWidthAvg
bbLbl = bbRatio >= 0.8 and bbRatio <= 1.2 ? "BB Moderate ✓" : bbRatio < 0.8 ? "BB Squeeze" : "BB Expand"
chopLbl = not na(chopValue) ? (chopValue <= 38 ? "Chop " + str.tostring(math.round(chopValue)) + " Trending ✓" : chopValue >= 62 ? "Chop " + str.tostring(math.round(chopValue)) + " Ranging" : "Chop " + str.tostring(math.round(chopValue)) + " Netral") : ""
atrLbl = "ATR " + str.tostring(math.round(atrPercent * 10) / 10) + "%"
bbPosLbl = not na(bbPosition) ? (bbPosition >= 30 and bbPosition <= 70 ? "BB Pos " + str.tostring(math.round(bbPosition)) + " Mid ✓" : "BB Pos " + str.tostring(math.round(bbPosition)) + " Edge") : ""
volDetail := bbLbl + " | " + chopLbl + " | " + atrLbl + " | " + bbPosLbl
// Volume tooltip
volmDetail = "—"
if not na(volRatio) and not na(volumeScore)
vrLbl = volRatio >= volSpikeMult ? "Vol " + str.tostring(math.round(volRatio * 10) / 10) + "x (spike!)" : volRatio >= 0.8 ? "Vol " + str.tostring(math.round(volRatio * 10) / 10) + "x (healthy) ✓" : "Vol " + str.tostring(math.round(volRatio * 10) / 10) + "x (low)"
obvLbl = not na(obvValue) ? (obvAgree ? "OBV Agree ✓" : "OBV Diverge ✗") : ""
netLbl = not na(netVolSmooth) ? (netVolSmooth > 0 ? "Net Vol + Bullish" : "Net Vol - Bearish") : ""
spikeLbl = volSpike ? "Spike ✓" : "Spike ✗"
volmDetail := vrLbl + " | " + obvLbl + " | " + netLbl + " | " + spikeLbl
// ==========================================================
// 10. MULTI-TIMEFRAME CONFLICT DETECTION
// ==========================================================
// Legacy conflict (simple binary, for backward compat)
mtfConflict = enableMtfFilter and not na(mtfTrendScore) and not na(overallScore) and (
(mtfTrendScore >= 50 and overallScore < 40) or
(mtfTrendScore < 50 and overallScore >= 70)
)
mtfWarning = if enableMtfFilter and not na(mtfTrendScore)
if mtfTrendScore >= 50
(bahasaInput == "Indonesia" ? "MTF ▲ " + str.tostring(math.round(mtfTrendScore)) : "MTF ▲ " + str.tostring(math.round(mtfTrendScore)))
else
(bahasaInput == "Indonesia" ? "MTF ▼ " + str.tostring(math.round(mtfTrendScore)) : "MTF ▼ " + str.tostring(math.round(mtfTrendScore)))
else
""
// New MTF alignment summary (for UI — Sprint C) — disiapkan sekarang
mtfAlignmentDirection = if not na(mtfScore)
if mtfDirection == 1
"Bullish"
else if mtfDirection == -1
"Bearish"
else
"Mixed"
else
""
// ==========================================================
// 11. NARRATIVE (1 baris insight untuk trader)
// ==========================================================
f_narrative() =>
lang = bahasaInput
if not na(momentumScore) and not na(rsiValue) and not na(trendScore) and not na(overallScore)
// 1. Momentum kuat tapi RSI overbought — prioritas tertinggi (konflik)
if momentumScore >= 65 and rsiValue >= rsiOb
lang == "Indonesia" ? "⚠️ Momentum kuat tapi RSI overbought — waspada koreksi" : "⚠️ Strong momentum but RSI overbought — watch for pullback"
// 2. RSI oversold + volume spike = potensi bounce
else if rsiValue <= rsiOs and not na(volRatio) and volSpike
lang == "Indonesia" ? "📈 RSI oversold + volume spike — potensi bounce" : "📈 RSI oversold + volume spike — potential bounce"
// 3. Tren lemah & volatil
else if not na(volatilityScore) and trendScore < 40 and volatilityScore < 40
lang == "Indonesia" ? "⚠️ Tren lemah & volatil — hindari entry baru" : "⚠️ Weak trend & volatile — avoid new entries"
// 4. Volume konfirmasi tren
else if trendScore >= 65 and not na(volmBull) and not na(volmTotal) and volmBull >= 3 and volmTotal >= 3
lang == "Indonesia" ? "✅ Volume konfirmasi tren — bias searah" : "✅ Volume confirms trend — trade with trend"
// 5. Semua align bullish
else if overallScore >= 70 and trendScore >= 65 and not na(volmBull) and volmBull >= 3
lang == "Indonesia" ? "✅ Semua dimensi align — bias bullish" : "✅ All dimensions aligned — bullish bias"
// 6. Semua align bearish
else if overallScore <= 30 and not na(trendScore) and trendScore <= 35 and not na(volmBull) and volmBull <= 1
lang == "Indonesia" ? "✅ Semua dimensi align — bias bearish" : "✅ All dimensions aligned — bearish bias"
// 7. OBV divergen
else if not na(obvValue) and close > close[10] and not obvAgree
lang == "Indonesia" ? "⚠️ Harga naik tapi OBV divergen — konfirmasi dulu" : "⚠️ Price up but OBV diverging — confirm first"
// 8. MTF conflict
else if enableMtfFilter and not na(mtfTrendScore) and mtfConflict and not na(overallScore)
isBullishDaily = overallScore >= 70
if lang == "Indonesia"
if isBullishDaily
"⚠️ Tren " + mtfTimeframe + " bearish — sinyal harian perlu konfirmasi"
else
"⚠️ Tren " + mtfTimeframe + " bullish — jangan short saat tren besar naik"
else
if isBullishDaily
"⚠️ " + mtfTimeframe + " trend bearish — confirm daily signal"
else
"⚠️ " + mtfTimeframe + " trend bullish — don't short against higher TF"
// 9. Ranging market
else if not na(chopValue) and chopRanging
lang == "Indonesia" ? "⏳ Pasar ranging — tunggu breakout" : "⏳ Ranging market — wait for breakout"
// 10. Fallback: mixed
else
lang == "Indonesia" ? "💡 Kondisi campuran — cek detail per dimensi" : "💡 Mixed conditions — check per-dimension details"
else
lang == "Indonesia" ? "⏳ Menunggu data..." : "⏳ Waiting for data..."
narrative = f_narrative()
// ==========================================================
// 12. ENTRY TRIGGER ENGINE (3 modes)
// ==========================================================
// Zone tracking (untuk Composite Score mode)
currentZone = na(overallScore) ? -1 : (overallScore >= 70 ? 2 : overallScore >= 40 ? 1 : 0)
var int prevZone = -1
if prevZone < 0 and currentZone >= 0
prevZone := currentZone
var int lastSignalBar = -1
var float signalEntryPrice = na
var int signalEntryBar = na
var int signalDirection = 0 // 1 = long, -1 = short
var string signalLabel = ""
zoneChanged = prevZone >= 0 and currentZone >= 0 and currentZone != prevZone
minBarsOk = lastSignalBar < 0 or bar_index - lastSignalBar >= minSignalBars
chopFilterOk = signalFilterMode == "Disabled" or (not na(chopValue) and (
signalFilterMode == "Only Trending (Chop<38)" ? chopValue < 38 :
signalFilterMode == "Only Ranging (Chop>62)" ? chopValue >= 62 : true
))
// Mode A: Composite Score (zone-based)
signalTriggered = zoneChanged and minBarsOk and chopFilterOk
// Mode B: Pullback (trend pullback ke EMA20 + RSI rebound)
pullbackTrigger = entryTriggerMode == "Pullback" and not na(trendScore) and trendScore >= 65 and not na(emaFast) and not na(atrValue) and math.abs(close - emaFast) <= atrValue * 1.5 and not na(rsiValue) and rsiValue <= 50 and rsiDirection == 1 and minBarsOk and chopFilterOk
// Mode C: Breakout (price break recent high/low + volume confirmation)
recentHigh = ta.highest(high, 20)
recentLow = ta.lowest(low, 20)
breakoutTrigger = entryTriggerMode == "Breakout" and not na(volumeScore) and volumeScore >= 60 and (close >= recentHigh or close <= recentLow) and minBarsOk and chopFilterOk
// Unified entry trigger
entryTriggered = if entryTriggerMode == "Composite Score"
signalTriggered
else if entryTriggerMode == "Pullback"
pullbackTrigger
else
breakoutTrigger
// Signal direction & label
if entryTriggered
signalDir = if entryTriggerMode == "Composite Score"
(currentZone > prevZone ? 1 : -1)
else if entryTriggerMode == "Pullback"
1
else
(close >= recentHigh ? 1 : -1)
signalLbl = if entryTriggerMode == "Composite Score"
if signalDir > 0
if prevZone == 0
(bahasaInput == "Indonesia" ? "Bearish→Netral (Bullish)" : "Bearish→Neutral (Bullish)")
else
(bahasaInput == "Indonesia" ? "Netral→Bullish" : "Neutral→Bullish")
else
if prevZone == 2
(bahasaInput == "Indonesia" ? "Bullish→Netral (Bearish)" : "Bullish→Neutral (Bearish)")
else
(bahasaInput == "Indonesia" ? "Netral→Bearish" : "Neutral→Bearish")
else if entryTriggerMode == "Pullback"
(bahasaInput == "Indonesia" ? "Pullback Entry" : "Pullback Entry")
else
if signalDir > 0
(bahasaInput == "Indonesia" ? "Breakout Long" : "Breakout Long")
else
(bahasaInput == "Indonesia" ? "Breakout Short" : "Breakout Short")
signalEntryPrice := close
signalEntryBar := bar_index
signalDirection := signalDir
signalLabel := signalLbl
lastSignalBar := bar_index
if zoneChanged
prevZone := currentZone
// Forward return
fwdReturn = na(signalEntryPrice) or signalEntryPrice == 0 ? na : (close / signalEntryPrice - 1) * signalDirection * 100
signalStatus = na(fwdReturn) ? "" : fwdReturn >= 5 ? "✅ +" + str.tostring(math.round(fwdReturn)) + "%" : fwdReturn >= 0 ? "➖ +" + str.tostring(math.round(fwdReturn)) + "%" : fwdReturn >= -5 ? "➖ " + str.tostring(math.round(fwdReturn)) + "%" : "❌ " + str.tostring(math.round(fwdReturn)) + "%"
fwdBars = na(signalEntryBar) ? na : bar_index - signalEntryBar
// ==========================================================
// 13. HISTORI SINYAL (circular buffer, evaluasi setiap 10 bar)
// ==========================================================
LOOKBACK_BARS = 10
BT_SIZE = 20
var int[] btEntryBars = array.new_int(BT_SIZE, -1)
var float[] btEntryPrices = array.new_float(BT_SIZE, na)
var int[] btDirs = array.new_int(BT_SIZE, 0)
var int btHead = 0
var int btTotalEval = 0
var int btWins = 0
var float btTotalReturn = 0.0
// Record new signal
if entryTriggered
array.set(btEntryBars, btHead, bar_index)
array.set(btEntryPrices, btHead, close)
array.set(btDirs, btHead, signalDirection)
btHead := (btHead + 1) % BT_SIZE
// Evaluate resolved signals
btNewWins = 0
btNewLosses = 0
for i = 0 to BT_SIZE - 1
eb = array.get(btEntryBars, i)
if eb >= 0 and bar_index - eb >= LOOKBACK_BARS
ep = array.get(btEntryPrices, i)
ed = array.get(btDirs, i)
ret = (close / ep - 1) * ed * 100
btTotalReturn += ret
if ret > 0
btNewWins += 1
else
btNewLosses += 1
array.set(btEntryBars, i, -1)
btTotalEval += btNewWins + btNewLosses
btWins += btNewWins
btWinRate = btTotalEval > 0 ? btWins / btTotalEval * 100 : na
// ==========================================================
// 14. LEVEL REFERENSI RISIKO (SL & Target)
// ==========================================================
var float riskEntryPrice = na
var float riskAtrCapture = na
var float riskSwingLow = na
var float riskSwingHigh = na
var int riskDirection = 0
// ta.lowest/ta.highest must be called unconditionally on every bar (Pine v6 requirement)
riskSwingLowUncond = ta.lowest(low, slSwingPeriod)
riskSwingHighUncond = ta.highest(high, slSwingPeriod)
if entryTriggered
riskEntryPrice := signalEntryPrice
riskAtrCapture := atrValue
riskSwingLow := riskSwingLowUncond
riskSwingHigh := riskSwingHighUncond
riskDirection := signalDirection
hasRisk = riskDirection != 0 and not na(riskEntryPrice)
// ATR-based SL
atrSl = hasRisk ? riskEntryPrice - riskAtrCapture * slAtrMult * riskDirection : na
// Swing-based SL
swingSl = hasRisk ? (riskDirection == 1 ? riskSwingLow : riskSwingHigh) : na
// Final SL price
float slPrice = na
if hasRisk
if slTypeInput == "ATR"
slPrice := atrSl
else if slTypeInput == "Swing Low/High"
slPrice := swingSl
else
slPrice := riskDirection == 1 ? math.min(atrSl, swingSl) : math.max(atrSl, swingSl)
// Target (R-multiple: entry + Risk * targetR)
riskAmount = hasRisk ? math.abs(riskEntryPrice - slPrice) : na
targetPrice = hasRisk ? riskEntryPrice + riskAmount * targetR * riskDirection : na
// Format for display
slPct = hasRisk and riskEntryPrice != 0 ? riskAmount / riskEntryPrice * 100 : na
targetPct = hasRisk and riskEntryPrice != 0 ? math.abs(targetPrice - riskEntryPrice) / riskEntryPrice * 100 : na
riskBaseLabel = if hasRisk
"SL: " + str.tostring(math.round(slPrice)) + " (" + (riskDirection == 1 ? "-" : "+") + str.tostring(math.round(slPct)) + "%) | Tgt: " + str.tostring(math.round(targetPrice)) + " (" + (riskDirection == 1 ? "+" : "-") + str.tostring(math.round(targetPct)) + "%) [" + str.tostring(targetR) + "R]"
else
"—"
// ==========================================================
// 15. POSITION SIZING
// ==========================================================
riskRp = showPositionSizing ? accountBalanceInput * riskPctInput / 100 : na
suggestedLots = showPositionSizing and not na(riskAmount) and riskAmount > 0 ? math.floor((riskRp / riskAmount) / 100) : na
riskRpK = not na(riskRp) and not na(suggestedLots) ? math.round(riskRp / 1000) : na
riskRpDisplay = not na(riskRpK) ? (riskRpK >= 1000 ? "Rp" + str.tostring(math.round(riskRpK / 1000)) + "jt" : "Rp" + str.tostring(riskRpK) + "rb") : ""
lotLabel = bahasaInput == "Indonesia" ? "Lot" : "Lots"
suggestedLotStr = if hasRisk and showPositionSizing and not na(suggestedLots)
" | " + lotLabel + ": " + str.tostring(math.max(1, suggestedLots)) + (suggestedLots < 1 ? " (min. 1 lot)" : "") + " | " + riskRpDisplay + " (" + str.tostring(riskPctInput) + "%)"
else
""
riskLevelLabel = riskBaseLabel + suggestedLotStr
// @version=6
// 04-ui.pine — Papan Gerak: Table & Signal Rendering
// ==========================================================
// ==========================================================
// 1. SIGNAL LINE (Overall Score as Oscillator)
// ==========================================================
hline(70, "OB Line", color = color.rgb(239, 83, 80, 60), linestyle = hline.style_dashed)
hline(40, "OS Line", color = color.rgb(38, 166, 154, 60), linestyle = hline.style_dashed)
hline(50, "Midline", color = color.rgb(100, 100, 100, 40), linestyle = hline.style_dotted)
plot(overallScore, "Overall Score", signalColor, 2, plot.style_line)
fill(plot(70), plot(40), color = color.rgb(100, 100, 100, 10))
oscBg = na(overallScore) ? color.new(color.gray, 95) : overallScore >= 70 ? color.new(bullColor, 88) : overallScore >= 40 ? color.new(neutralColor, 88) : color.new(bearColor, 88)
bgcolor(oscBg, title = "Score Zone")
// ==========================================================
// 1b. MTF PLOTS & DEBUG
// ==========================================================
plot(mtfScore, "MTF Score", color.new(color.blue, 60), 1, plot.style_line, display = display.data_window)
plot(mtfDirection * 50 + 50, "MTF Direction", color.new(color.gray, 70), 1, plot.style_cross, display = display.data_window)
if mtfDebug and barstate.islast and not na(mtfScore)
label.new(bar_index, 3, mtfDebugStr, style = label.style_label_center,
size = size.small, text_size = size.small, text_color = color.white,
color = color.new(color.blue, 60))
// ==========================================================
// 2. BUILD TABLE
// ==========================================================
f_getPosition() =>
if tablePositionInput == "Top Left"
position.top_left
else
if tablePositionInput == "Bottom Left"
position.bottom_left
else
if tablePositionInput == "Bottom Right"
position.bottom_right
else
position.top_right
f_getFontSize() =>
if fontSizeInput == 1
size.small
else
if fontSizeInput == 2
size.normal
else
size.large
f_confStr(n, total) =>
str.tostring(n) + "/" + str.tostring(total)
tablePos = f_getPosition()
fontSz = f_getFontSize()
// Render on last bar only
if barstate.islast
// --- Compact Mode ---
if modeInput == "Compact"
tblCompact = table.new(tablePos, 2, 1, bgcolor = tableBgColor, border_width = 1, border_color = signalColor)
overallDisplay = str.tostring(math.round(overallScore))
mtfCompact = not na(mtfScore) ? " | MTF: " + str.tostring(math.round(mtfScore)) + (mtfDirection == 1 ? "▲" : mtfDirection == -1 ? "▼" : "—") : ""
table.cell(tblCompact, 0, 0, "PAPAN GERAK " + signalText + " " + overallDisplay + mtfCompact,
text_color = signalColor, text_size = fontSz, text_halign = text.align_center, width = 10)
else
// --- Standard & Detailed Mode ---
isDetailed = modeInput == "Detailed"
rowCount = 12 // +1 untuk MTF Alignment row
if enableSmartMoney
rowCount := rowCount + 1
if isDetailed
rowCount := rowCount + 5 // +1 sub-row per dimensi (+1 untuk MTF detail)
tblFull = table.new(tablePos, 2, rowCount, bgcolor = tableBgColor, border_width = 1, border_color = signalColor,
frame_color = signalColor, frame_width = 1)
// Narrative row (full width - column 0 only)
table.cell(tblFull, 0, 0, narrative, text_color = headerColor, text_size = size.small, text_halign = text.align_left, bgcolor = bgColor)
// Header
table.cell(tblFull, 0, 1, "PAPAN GERAK (last bar)", text_color = headerColor, text_size = fontSz, text_halign = text.align_left, bgcolor = bgColor)
overallDisplay = str.tostring(math.round(overallScore))
overallTip = not na(overallScore) ? (overallScore >= 70 ? "Bullish: overall >70" : overallScore >= 40 ? "Neutral: 40-70" : "Bearish: <40") : ""
table.cell(tblFull, 1, 1, overallDisplay, text_color = signalColor, text_size = fontSz, text_halign = text.align_right, bgcolor = bgColor, tooltip = overallTip)
// Signal
table.cell(tblFull, 0, 2, bahasaInput == "Indonesia" ? "Sinyal" : "Signal", text_color = labelColor, text_size = fontSz, text_halign = text.align_left, bgcolor = bgColor)
table.cell(tblFull, 1, 2, signalText, text_color = signalColor, text_size = fontSz, text_halign = text.align_right, bgcolor = bgColor)
// Forward Return (sejak sinyal terakhir)
valLabel = bahasaInput == "Indonesia" ? "Return Sejak Sinyal" : "Forward Return"
valDisplay = not na(signalStatus) and signalStatus != "" ? signalStatus : "—"
valTooltip = not na(fwdBars) and not na(signalLabel) ? signalLabel + " | Bars: " + str.tostring(fwdBars) + " | Trigger: " + entryTriggerMode + " | MinBars: " + str.tostring(minSignalBars) : ""
table.cell(tblFull, 0, 3, " 📊 " + valLabel, text_color = labelColor, text_size = size.small, text_halign = text.align_left, bgcolor = bgColor)
table.cell(tblFull, 1, 3, valDisplay, text_color = labelColor, text_size = size.small, text_halign = text.align_right, bgcolor = bgColor, tooltip = valTooltip)
// Histori Sinyal row
btLabel = bahasaInput == "Indonesia" ? "Histori Sinyal (kumulatif)" : "Signal History (cumulative)"
btDisplay = not na(btWinRate) ? str.tostring(math.round(btWinRate)) + "% win (" + str.tostring(btWins) + "/" + str.tostring(btTotalEval) + ")" : "—"
btTooltip = not na(btTotalEval) and btTotalEval > 0 ? "Total return: " + str.tostring(math.round(btTotalReturn * 10) / 10) + "% | Lookback: 10 bar | Filter: " + signalFilterMode : ""
table.cell(tblFull, 0, 4, " 📊 " + btLabel, text_color = labelColor, text_size = size.small, text_halign = text.align_left, bgcolor = bgColor)
table.cell(tblFull, 1, 4, btDisplay, text_color = btWinRate >= 60 ? bullColor : btWinRate >= 40 ? neutralColor : bearColor, text_size = size.small, text_halign = text.align_right, bgcolor = bgColor, tooltip = btTooltip)
// MTF Alignment
mtfArrow = not na(mtfScore) ? (mtfScore >= 70 ? "↑" : mtfScore >= 40 ? "→" : "↓") : "—"
mtfDirLabel = not na(mtfDirection) ? (mtfDirection == 1 ? " (Bullish)" : mtfDirection == -1 ? " (Bearish)" : " (Mixed)") : ""
mtfDisplay = not na(mtfScore) ? str.tostring(math.round(mtfScore)) + mtfDirLabel : "—"
mtfDetailTF = not na(mtfScore) ? str.format("{0}:{1}/{2}/{3}",
mtfPrimaryTF, str.tostring(math.round(mtfTf1Dir)), str.tostring(math.round(mtfTf2Dir)), str.tostring(math.round(mtfTf3Dir))) : ""
table.cell(tblFull, 0, 5, " " + mtfArrow + " " + (bahasaInput == "Indonesia" ? "MTF Alignment" : "MTF Alignment"), text_color = color.new(color.blue, 70), text_size = fontSz, text_halign = text.align_left, bgcolor = bgColor)
table.cell(tblFull, 1, 5, mtfDisplay, text_color = f_colorScore(mtfScore), text_size = fontSz, text_halign = text.align_right, bgcolor = bgColor, tooltip = mtfDebug ? mtfDebugStr : mtfDetailTF)
if isDetailed
table.cell(tblFull, 0, 6, "", text_size = size.small, text_halign = text.align_left, bgcolor = bgColor)
table.cell(tblFull, 1, 6, mtfDebugStr, text_color = labelColor, text_size = size.small, text_halign = text.align_left, bgcolor = bgColor)
// Trend (geser 1 row karena MTF row)
trendDisplay = str.tostring(math.round(trendScore))
trendArrow = not na(trendScore) ? (trendScore >= 70 ? "↑" : trendScore >= 40 ? "→" : "↓") : "—"
trendConf = not na(trendBull) ? " (" + f_confStr(trendBull, trendTotal) + ")" : ""
trendMtf = enableMtfFilter and not na(mtfTrendScore) ? " " + mtfWarning : ""
table.cell(tblFull, 0, 7, " " + trendArrow + " " + (bahasaInput == "Indonesia" ? "Tren" : "Trend"), text_color = dimTrend, text_size = fontSz, text_halign = text.align_left, bgcolor = bgColor)
table.cell(tblFull, 1, 7, trendDisplay + trendConf + trendMtf, text_color = f_colorScore(trendScore), text_size = fontSz, text_halign = text.align_right, bgcolor = bgColor, tooltip = trendDetail)
if isDetailed
table.cell(tblFull, 0, 8, "", text_size = size.small, text_halign = text.align_left, bgcolor = bgColor)
table.cell(tblFull, 1, 8, trendDetail, text_color = labelColor, text_size = size.small, text_halign = text.align_left, bgcolor = bgColor)
// Momentum (geser 1 row)
momDisplay = str.tostring(math.round(momentumScore))
momArrow = not na(momentumScore) ? (momentumScore >= 70 ? "↑" : momentumScore >= 40 ? "→" : "↓") : "—"
momConf = not na(momBull) ? " (" + f_confStr(momBull, momTotal) + ")" : ""
momBase = 8
if isDetailed
momBase := 9
table.cell(tblFull, 0, momBase, " " + momArrow + " " + (bahasaInput == "Indonesia" ? "Momentum" : "Momentum"), text_color = dimMomentum, text_size = fontSz, text_halign = text.align_left, bgcolor = bgColor)
table.cell(tblFull, 1, momBase, momDisplay + momConf, text_color = f_colorScore(momentumScore), text_size = fontSz, text_halign = text.align_right, bgcolor = bgColor, tooltip = momDetail)
if isDetailed
table.cell(tblFull, 0, momBase + 1, "", text_size = size.small, text_halign = text.align_left, bgcolor = bgColor)
table.cell(tblFull, 1, momBase + 1, momDetail, text_color = labelColor, text_size = size.small, text_halign = text.align_left, bgcolor = bgColor)
// Volatility
volDisplay = str.tostring(math.round(volatilityScore))
volArrow = not na(volatilityScore) ? (volatilityScore >= 70 ? "≈" : volatilityScore >= 40 ? "→" : "⚡") : "—"
volConf = not na(volBull) ? " (" + f_confStr(volBull, volTotal) + ")" : ""
volBase = momBase + (isDetailed ? 2 : 1)
table.cell(tblFull, 0, volBase, " " + volArrow + " " + (bahasaInput == "Indonesia" ? "Volatilitas" : "Volatility"), text_color = dimVolatility, text_size = fontSz, text_halign = text.align_left, bgcolor = bgColor)
table.cell(tblFull, 1, volBase, volDisplay + volConf, text_color = f_colorScore(volatilityScore), text_size = fontSz, text_halign = text.align_right, bgcolor = bgColor, tooltip = volDetail)
if isDetailed
table.cell(tblFull, 0, volBase + 1, "", text_size = size.small, text_halign = text.align_left, bgcolor = bgColor)
table.cell(tblFull, 1, volBase + 1, volDetail, text_color = labelColor, text_size = size.small, text_halign = text.align_left, bgcolor = bgColor)
// Volume
volmDisplay = str.tostring(math.round(volumeScore))
volmArrow = not na(volumeScore) ? (volumeScore >= 70 ? "↑" : volumeScore >= 40 ? "→" : "↓") : "—"
volmConf = not na(volmBull) ? " (" + f_confStr(volmBull, volmTotal) + ")" : ""
volmBase = volBase + (isDetailed ? 2 : 1)
table.cell(tblFull, 0, volmBase, " " + volmArrow + " " + (bahasaInput == "Indonesia" ? "Volume" : "Volume"), text_color = dimVolume, text_size = fontSz, text_halign = text.align_left, bgcolor = bgColor)
table.cell(tblFull, 1, volmBase, volmDisplay + volmConf, text_color = f_colorScore(volumeScore), text_size = fontSz, text_halign = text.align_right, bgcolor = bgColor, tooltip = volmDetail)
if isDetailed
table.cell(tblFull, 0, volmBase + 1, "", text_size = size.small, text_halign = text.align_left, bgcolor = bgColor)
table.cell(tblFull, 1, volmBase + 1, volmDetail, text_color = labelColor, text_size = size.small, text_halign = text.align_left, bgcolor = bgColor)
// Smart Money (if enabled)
if enableSmartMoney
smBase = volmBase + (isDetailed ? 2 : 1)
smDisplay = str.tostring(math.round(smartMoneyScore))
smTooltip = not na(smartMoneyScore) ? (smDetected ? "SM flow: detected" : "SM flow: none") : ""
table.cell(tblFull, 0, smBase, " ◆ " + (bahasaInput == "Indonesia" ? "Smart Money" : "Smart Money"), text_color = color.rgb(255, 64, 129), text_size = fontSz, text_halign = text.align_left, bgcolor = bgColor)
table.cell(tblFull, 1, smBase, smDisplay, text_color = f_colorScore(smartMoneyScore), text_size = fontSz, text_halign = text.align_right, bgcolor = bgColor, tooltip = smTooltip)
// Risk Levels (SL & Target)
riskBase = volmBase + (isDetailed ? 2 : 1)
if enableSmartMoney
riskBase := riskBase + 1
if showRiskLevels
riskTooltip = bahasaInput == "Indonesia" ? "Level referensi — bukan rekomendasi. Sesuaikan dgn toleransi risiko Anda." : "Reference levels — not a recommendation. Adjust to your risk tolerance."
table.cell(tblFull, 0, riskBase, " ⚠️ " + (bahasaInput == "Indonesia" ? "Level Referensi" : "Risk Levels"), text_color = color.rgb(255, 64, 129), text_size = size.small, text_halign = text.align_left, bgcolor = bgColor, tooltip = riskTooltip)
table.cell(tblFull, 1, riskBase, riskLevelLabel, text_color = labelColor, text_size = size.small, text_halign = text.align_right, bgcolor = bgColor, tooltip = riskTooltip)
// Legend (last row)
legendBase = showRiskLevels ? riskBase + 1 : riskBase
legendText = bahasaInput == "Indonesia" ? "🔴 <40 Bearish | 🟡 40-70 Netral | 🟢 >70 Bullish" : "🔴 <40 Bearish | 🟡 40-70 Neutral | 🟢 >70 Bullish"
table.cell(tblFull, 0, legendBase, legendText, text_color = labelColor, text_size = size.small, text_halign = text.align_left, bgcolor = bgColor)
// ==========================================================
// 3. ALERTS
// ==========================================================
f_webhookMsg(eventType, params) =>
if webhookFormat == "JSON"
str.format("PG|event={0}|time={1}|ticker={2}|{3}",
eventType, str.tostring(time), syminfo.tickerid, params)
else
params
// Alert: Entry trigger (mode-aware) — guard: bar-close confirmation
if entryTriggered and (not confirmOnBarClose or barstate.isconfirmed)
msg = if webhookFormat == "JSON"
f_webhookMsg("entry", str.format("mode={0}|action={1}|price={2}|score={3}|signal={4}",
entryTriggerMode, signalDirection > 0 ? "buy" : "sell", str.tostring(close),
str.tostring(math.round(overallScore)), signalLabel))
else
str.format("PAPAN GERAK ({0}): {1} → {2}", entryTriggerMode, signalLabel, signalText)
alert(msg)
// Alert: Trend Extreme
if (trendScore >= 80 or trendScore <= 20) and (not confirmOnBarClose or barstate.isconfirmed)
msg = if webhookFormat == "JSON"
f_webhookMsg("trend_extreme", "trendScore=" + str.tostring(math.round(trendScore)))
else
str.format("PAPAN GERAK: Trend ekstrem ({0})", str.tostring(math.round(trendScore)))
alert(msg)
// Alert: Momentum Oversold
if rsiOsHit and (not confirmOnBarClose or barstate.isconfirmed)
msg = if webhookFormat == "JSON"
f_webhookMsg("rsi_oversold", "rsi=" + str.tostring(math.round(rsiValue)))
else
str.format("PAPAN GERAK: RSI oversold ({0})", str.tostring(math.round(rsiValue)))
alert(msg)
// Alert: Momentum Overbought
if rsiObHit and (not confirmOnBarClose or barstate.isconfirmed)
msg = if webhookFormat == "JSON"
f_webhookMsg("rsi_overbought", "rsi=" + str.tostring(math.round(rsiValue)))
else
str.format("PAPAN GERAK: RSI overbought ({0})", str.tostring(math.round(rsiValue)))
alert(msg)
// Alert: Volume Spike
if volSpike and (not confirmOnBarClose or barstate.isconfirmed)
msg = if webhookFormat == "JSON"
f_webhookMsg("volume_spike", "ratio=" + str.tostring(math.round(volRatio * 100)))
else
str.format("PAPAN GERAK: Volume spike ({0}% of MA)", str.tostring(math.round(volRatio * 100)))
alert(msg)
// Alert: Smart Money Detected
if smDetected and (not confirmOnBarClose or barstate.isconfirmed)
msg = if webhookFormat == "JSON"
f_webhookMsg("smart_money", "detected=true")
else
"PAPAN GERAK: Institutional flow terdeteksi"
alert(msg)
// Alert: Choppiness (ranging market)
if chopRanging and (not confirmOnBarClose or barstate.isconfirmed)
msg = if webhookFormat == "JSON"
f_webhookMsg("choppiness", "chop=" + str.tostring(math.round(chopValue)))
else
str.format("PAPAN GERAK: Pasar ranging (Choppiness: {0})", str.tostring(math.round(chopValue)))
alert(msg)
Overview
Saham: Papan Gerak By. Akhmfz evaluates multiple technical indicators including exponential moving averages, SuperTrend, ADX, RSI, MACD, Stochastic, Bollinger Bands, and Choppiness Index to generate composite technical scores. The indicator renders its metrics in a customizable on-screen dashboard table with selectable visual themes, language options (Indonesian or English), and display modes. Traders can configure individual indicator lengths, overbought and oversold thresholds, and scoring weight distributions across dimensions directly from the input settings.
The inputs you can change from the gear icon on the chart. Defaults are the author's.
Setting
Type
Default
Variable
Table Position
string
Top Right
tablePositionInput
Font Size
int
1
fontSizeInput
Mode
string
Standard
modeInput
Bahasa
string
Indonesia
bahasaInput
Color Theme
string
Gelap
colorThemeInput
EMA Fast Period
int
20
emaFastPeriod
EMA Mid Period
int
50
emaMidPeriod
EMA Slow Period
int
200
emaSlowPeriod
SuperTrend Factor
float
3.0
superTrendFactor
SuperTrend ATR Period
int
10
superTrendPeriod
ADX Length
int
14
adxLength
ADX Threshold
int
25
adxThreshold
Show all 40 settings
Setting
Type
Default
Variable
RSI Length
int
14
rsiLength
RSI Overbought
int
70
rsiOb
RSI Oversold
int
30
rsiOs
MACD Fast
int
12
macdFast
MACD Slow
int
26
macdSlow
MACD Signal
int
9
macdSignal
Stochastic K
int
14
stochK
Stochastic D
int
3
stochD
Stoch Overbought
int
80
stochOb
Stoch Oversold
int
20
stochOs
ATR Length
int
14
atrLength
Bollinger Length
int
20
bbLength
Bollinger Multiplier
float
2.0
bbMult
Choppiness Length
int
14
chopLength
Volume MA Length
int
20
volMaLength
Volume Spike Multiplier
float
2.0
volSpikeMult
Trend Weight (%)
int
30
weightTrend
Momentum Weight (%)
int
30
weightMomentum
Volatility Weight (%)
int
20
weightVolatility
Volume Weight (%)
int
20
weightVolume
Enable Smart Money Flow
bool
false
enableSmartMoney
SM Weight (%)
int
20
weightSmartMoney
SM Volume Z-Threshold
float
2.5
smVolumeThreshold
SM Lookback Period
int
20
smPeriod
Min Bars Between Signals
int
5
minSignalBars
Signal Filter
string
Disabled
signalFilterMode
Confirm on Bar Close
bool
true
confirmOnBarClose
Show Risk Levels
bool
true
showRiskLevels
Third-party code. ForexR did not write, test or endorse this script, and the summary above was generated automatically from its code and README. Indicators describe the past, and a strategy's backtest can look better than live trading, especially when a script repaints. Check it in the Strategy Tester and on a demo account before you rely on it.
The Adaptive Cycle Detector estimates the dominant market cycle from WaveTrend oscillator zero-crossing frequency and applies smoothing to recommend adaptive indicator lengths. Fast markets with frequent crossings receive shorter lengths while slow swing marke
Manias, Panics and Crashes is a separate-pane market regime indicator that quantifies market sentiment and systemic risk. It processes technical metrics including RSI, moving averages, and volume alongside optional intermarket data such as VIX volatility and c
Elton's Screener is an indicator that runs in a separate pane and scores one of twenty selectable strategies, or auto-selects a route from the symbol type. It filters signals by a minimum confidence level and a higher-timeframe EMA trend alignment. ATR multipl
This indicator evaluates signal performance and multi-timeframe market dynamics through a shadow bankroll tracking framework. It measures market conditions across trend, momentum ROC Z-scores, ATR volatility expansion, and volume flow, alongside multi-timefram
The Market Regime Dashboard is a multi-timeframe dashboard indicator that evaluates market conditions across three user-defined timeframes. It measures four core dimensions: trend strength using ADX and DI direction, momentum using RSI z-scores, volatility via
This indicator evaluates market conditions across four user-defined timeframes by combining RSI, MACD crossovers, triple EMA trend filters, Bollinger Band boundaries, and volume spikes. It computes aggregate bullish and bearish scores, plotting buy and sell si