Versions 1.3.03 – 1.3.05 of the Data Saham Indonesia app added the Stock Screener and Stock Scoring features. This article covers how to write criteria for both.

Boolean Expression
Screener criteria are written as a mathematical formula called a Boolean Expression (BE) — a logical statement the system evaluates to one of two values: true or false.
Examples:
4 == 4 ==> true
4 == 3 + 1 ==> true
4 > 5 ==> false
true and false ==> false
true or false ==> true
4 == 4 and 4 == 3 + 1 ==> true
==is the operator for testing equality of values.
The screener evaluates your Boolean Expression against every stock listed on the Indonesia Stock Exchange, then shows those that return true.
For example:
prev hourly close <= current hourly close
Returns stocks whose close on the previous hourly candle is less than or equal to the close on the current hourly candle.
prev hourly close <= current hourly close AND hourly close >= hourly sma("close", 5)
The same criterion, AND close above the hourly MA5.
Screener criteria are flexible. They can be as simple as:
open < close
or as involved as:
hourly high > prev hourly hhv("high", 100) and
hourly close > hourly open and
index macd_histogram > 0 and
high < bollinger_top
Expression hierarchy
Boolean Expressions and Arithmetic Expressions are both derived from Expression. An Arithmetic Expression is a statement that produces a single value in the set of real numbers. Expression itself is defined as “anything that produces a single value”.
The hierarchy is as follows:
Expression :
- Boolean Expression (BE)
- Boolean Value : true, false
- Bracket Expression : ( BE )
- Boolean Operation : and, or, not
- Arithmetic Comparison : >= , <= , > , < , == , !=
- Boolean Function : code_index("<index code>")
- Arithmetic Expression (E)
- Numeric Expression
- Arithmetic Operation : * , / , + , -
- Bracket Expression : ( E )
- Stock Expression
In this document and in the criteria composer,
Eis shorthand for Arithmetic Expression.
Number notation
Numeric values can be written with a unit suffix, so large numbers stay short and readable:
K = thousand 1,000
M = million 1,000,000
B = billion 1,000,000,000
T = trillion 1,000,000,000,000
Examples:
value > 1B ==> transaction value above 1 billion rupiah
market_cap >= 10T ==> market capitalisation of at least 10 trillion rupiah
volume > 500K ==> volume above 500 thousand
Writing the number in full still works — 1B and 1000000000 are the same value.
Conditional operator
The ? : operator picks one of two values based on a condition:
<condition> ? <value if true> : <value if false>
It is most useful inside a subformula, because it turns a condition into a number that can then be summed or averaged.
sum("close > open ? 1 : 0", 20) ==> how many green candles in 20 bars
sma("volume > 1B ? 1 : 0", 10) ==> proportion of high-volume days
sum("close > prev close ? volume : 0", 5) ==> volume on up days only
To simply count how many times a condition holds, the count function is shorter:
count("close > open", 20) ==> equivalent to the first example above
Stock Expression
The most important part of a screener criterion is the Stock Expression, which is derived from Arithmetic Expression.
Its structure is:
E: stock_expression: [candle_selector] [timeframe] [target] <stock_attr>
E: stock_expression: [candle_selector] [timeframe] [target] <stock_function>
E: stock_expression: <stock_fundamental_attr>
E: stock_expression: <bid_offer_variables>
candle_selector
candle_selector: prev_N, ..., prev_3, prev_2, prev, current
default: current

timeframe
timeframe: yearly, monthly, weekly, daily, hourly, 15min, 5min, 1min
default: daily
target
target: stock, sector, index
default: stock
- stock — the chart of the stock itself. For TLKM and BMRI, target
stockselects the TLKM and BMRI charts. - sector — the chart of the sectoral index. For TLKM and BMRI, target
sectorselects the INFRA and FINANCE index charts. - index — the composite index chart for that stock. For TLKM and BMRI, target
indexselects COMPOSITE (^JKSE).
Writing parameters
Parameters with a default value may be omitted. In a Stock Expression, only stock_attr is required.
current daily stock close == close ==> true
current daily stock close == daily close ==> true
current daily stock close == stock close ==> true
current daily stock close == current daily close ==> true
But when parameters are written, they must follow the order defined above:
stock daily close ==> error
daily current close ==> error
current daily close ==> OK
stock_attr list
open, high, low, close, volume
macd, macd_signal, macd_histogram
ppo, ppo_signal, ppo_histogram, pvo
ao, chaikin_osc, tsi, mass_index, trix
aroon_up, aroon_down, aroon_osc
donchian_up, donchian_down, donchian_mid
rsi, stoch_k, stoch_d, stochrsi, atr, adx, pdx, ndx, cci, uo, roc, william_r, mfi, cmf
up_fractal, down_fractal
alligator_jaw, alligator_teeth, alligator_lips, alligator_ao, alligator_ac
bollinger_top, bollinger_bottom, bollinger_mean, bollinger_bandwidth,
bollinger_percent_b, bollinger_percent_b_avg
value, top_broker, foreign, top_broker_hist, foreign_hist
dto_stochk, dto_stochd, obv, adl, sar
pivot_s1, pivot_s2, pivot_s3, pivot_r1, pivot_r2, pivot_r3
camarilla_h1, camarilla_h2, camarilla_h3, camarilla_h4
camarilla_l1, camarilla_l2, camarilla_l3, camarilla_l4
mid_price, avg_price, tick, freq, change_percent
up_fractal_index, down_fractal_index
frequency_analyzer, mfv, ma_net_buy_sell_signal_count
projected_volume, projected_value
iep, iev, time_progress
open_date, open_time, close_date, close_time
Short forms with a standard period
The indicator names above can be written without brackets. The short form uses that indicator’s standard period, so ppo is the same as ppo(12, 26) and ao the same as ao(5, 34).
| Short form | Standard period |
|---|---|
rsi, atr, adx, pdx, ndx, cci, mfi, william_r | 14 |
roc | 12 |
cmf | 21 |
trix | 15 |
stochrsi | 14 |
dto_stochk, dto_stochd | 14, 8, 5, 3 |
stoch_k, stoch_d | 15, 3, 3 |
uo | 7, 14, 28 |
macd, ppo, pvo | 12, 26 |
macd_signal, macd_histogram, ppo_signal, ppo_histogram | 12, 26, 9 |
ao | 5, 34 |
chaikin_osc | 3, 10 |
tsi | 25, 13 |
mass_index | 9, 25 |
aroon_up, aroon_down, aroon_osc | 25 |
donchian_up, donchian_down, donchian_mid | 20 |
bollinger_* | 20, 2 |
sar | 0.02, 0.2 |
When you need a different period, write the full function form:
rsi < 30 ==> uses the standard period, rsi(14)
rsi(7) < 30 ==> a period of your own choosing
Moving averages also have a numbered form — the function name followed by the period:
sma20 ==> the same as sma("close", 20)
ema50, wma10, aema20, dema20, tema50, vwma20
stock_attr definitions
open,high,low,close,volume— the open, high, low, close and volume of the candlestick chartmacd,macd_signal,macd_histogram— values from the MACD(12,26,9) chart;macd_histogram=macd−macd_signalrsi— value from the RSI(14) chartstoch_k,stoch_d— values from the Stochastic(15,3,3) chartstochrsi— value from the StochRSI(9,6) chartatr— value from the ATR(14) chartadx,pdx,ndx— values from the ADX(14) chart;pdxis the DI+ line andndxthe DI− linecci— value from the CCI(14) chartuo— value from the UO(7,14,28) chartroc— value from the ROC(12) chartwilliam_r— value from the WilliamsR(14) chartmfi— value from the MFI(14) chartcmf— value from the CMF(21) chartalligator_jaw,alligator_teeth,alligator_lips,alligator_ao,alligator_ac— values from the Alligator(13,8,5) chartbollinger_top,bollinger_bottom,bollinger_mean,bollinger_bandwidth,bollinger_percent_b,bollinger_percent_b_avg— values from the Bollinger(20,2) chartvalue— total transaction value of the stocktop_broker— cumulative top broker transactions (current top_broker=prev top_broker+current top_broker_hist)foreign— cumulative foreign transactions (current foreign=prev foreign+current foreign_hist)top_broker_hist— value from the top broker transaction histogram, in sharesforeign_hist— value from the foreign transaction histogram, in sharesadl— Accumulation/Distribution Line, a running total of money flow volume: the position of the close within the daily range, multiplied by volume. It rises when closes sit near the high and falls when they sit near the lowdto_stochk,dto_stochd— values from the DTOStoch(8,5,3) chartsar— value from the SAR(0.02,0.2) chartmid_price—mid_price= (high+low) / 2tick— the exchange price fraction (price < 200 : 1 ; 200–500 : 2 ; 500–2000 : 5 ; 2000–5000 : 10 ; > 5000 : 25)vwap—vwap= (high+low+close) / 3avg_price— the average transaction price on that bar;avg_price=value/volumefreq— the number of transactions on that barchange_percent— price change against the previous close, as a percentagecamarilla_h1…camarilla_h4,camarilla_l1…camarilla_l4— Camarilla pivot levelsup_fractal_index,down_fractal_index— bars elapsed since the last upper or lower fractal formedfrequency_analyzer—volume/freq³, a measure of average transaction size. High values indicate trading dominated by large ordersmfv— money flow volume, the position of the close within the daily range multiplied by volumema_net_buy_sell_signal_count— buy signals minus sell signals across 12 moving averages (SMA and EMA at periods 5, 10, 20, 50, 100, 200). Ranges from −12 to +12projected_volume,projected_value— estimated volume and transaction value through to the session close, based on the pace of trading so fariep,iev— indicative equilibrium price and volume during the pre-opening sessiontime_progress— the proportion of the trading session elapsed, 0 to 1open_date,close_date,open_time,close_time— the opening and closing date and time of the bar
A note on
adl,top_brokerandforeign. All three are cumulative, counted from the oldest bar available, so their values are relative: they cannot be compared between stocks, nor stored and compared again on a different day. What carries meaning is the direction — against the previous bar, or against an average of the value itself.
adl > prev adl ==> accumulation increased today
adl > sma("adl", 20) ==> accumulation above its own average
prev adl < prev sma("adl", 20) and adl > sma("adl", 20)

top_broker_hist = [5 top net buyer] - [5 top net seller]
stock_function list
A stock_function is a function of a stock_attr.
sma(n), sma(subformula,n)
ema(n), ema(subformula,n)
aema(n), aema(subformula,n)
med(n), med(subformula,n)
highest(subformula,n), hhv(subformula,n)
lowest(subformula,n), llv(subformula,n)
ranking(subformula,n)
ranking_per_sector(subformula,n)
roc(n)
macd(fast_period,slow_period)
macd_signal(fast_period,slow_period,signal_period)
macd_histogram(fast_period,slow_period,signal_period)
sum(subformula,n)
cum(subformula)
since(date)
vwap(n)
vwma(n)
wma(n), wma(subformula,n)
linreg(n), linreg(subformula,n)
slope(n), slope(subformula,n)
linreg_r2(n), linreg_r2(subformula,n)
cci(n)
atr(n)
adx(n)
pdx(n)
ndx(n)
stoch_k(lookback_period,k_period)
stoch_d(lookback_period,k_period,d_period)
bollinger_top(period, multiplier)
bollinger_bottom(period, multiplier)
bollinger_mean(period, multiplier)
bollinger_bandwidth(period, multiplier)
bollinger_percent_b(period, multiplier)
bollinger_percent_b_avg(period, multiplier)
stdev_p(n), stdev_p(subformula,n)
stdev_s(n), stdev_s(subformula,n)
stdev(n), stdev(subformula,n)
rsi(n)
stochrsi(period,dtostoch_period,dtostoch_k_period,dtostoch_d_period)
dto_stochk(period,dtostoch_period,dtostoch_k_period,dtostoch_d_period)
dto_stochd(period,dtostoch_period,dtostoch_k_period,dtostoch_d_period)
mfi(n)
cmf(n)
chaikin_osc(fast_period, slow_period)
aroon_up(period)
aroon_down(period)
aroon_osc(period)
dema(period)
tema(period)
trix(period)
mass_index(ema_period, sum_period)
tsi(long_period, short_period)
donchian_up(period)
donchian_down(period)
donchian_mid(period)
ppo(fast_period, slow_period)
ppo_signal(fast_period, slow_period, signal_period)
ppo_histogram(fast_period, slow_period, signal_period)
pvo(fast_period, slow_period)
ao(fast_period, slow_period)
abs(x)
pow(x, y)
sqrt(x)
exp(x)
ln(x)
log10(x), log(x, base)
ceil(x), ceil(x, decimals)
floor(x), floor(x, decimals)
round(x), round(x, decimals)
trunc(x, decimals)
min(a, b, ...), max(a, b, ...), avg(a, b, ...)
count(subformula, n)
hhvbars(subformula, n)
llvbars(subformula, n)
pivot_high(k), pivot_high(k, n)
pivot_low(k), pivot_low(k, n)
pivot_high_bars(k), pivot_high_bars(k, n)
pivot_low_bars(k), pivot_low_bars(k, n)
trade_book(price)
freq(price)
tick_up(price), tick_down(price)
stock_function definitions
sma("<stock_attr>", N) — simple moving average of a stock_attr over N bars.
sma("close", 5) ==> daily MA5 of close
hourly sma("volume", 20) ==> hourly MA20 of volume
ema("<stock_attr>", N) — exponential moving average of a stock_attr over N bars.
ema("close", 5) ==> daily EMA5 of close
hourly ema("volume", 20) ==> hourly EMA20 of volume
aema("<stock_attr>", N) — an exponential moving average that stays more accurate on limited history. Written exactly like ema.
aema("close", 5) ==> daily EMA5 of close
aema("volume * close", 20) ==> EMA20 of transaction value
Use aema when your criterion uses a long period or works on sharply fluctuating values — volume, price differences, ratios. For short periods on price, the two give practically identical results.
If you already have screeners or alerts running on
ema, there is no need to change them.emaremains available and its behaviour is unchanged.
highest("<stock_attr>", N) / hhv("<stock_attr>", N) — the highest value of a stock_attr over N bars.
daily hhv("high", 5) ==> 5 day highest high daily
hourly hhv("close", 5) ==> 5 hour highest close hourly
lowest("<stock_attr>", N) / llv("<stock_attr>", N) — the lowest value of a stock_attr over N bars.
daily llv("low", 5) ==> 5 day lowest low daily
hourly llv("close", 5) ==> 5 hour lowest close hourly
ranking("<stock_attr>", N) — the rank of a stock_attr across all stocks, where rank 1 is the highest value. To order from lowest to highest instead, multiply the stock_attr by −1.
ranking("value") <= 10 ==> the top 10 stocks by transaction value
ranking("roc") <= 10 ==> the 10 stocks with the highest ROC
ranking("roc * -1") <= 10 ==> the 10 stocks with the lowest ROC
roc(period) — value from the Rate of Change (ROC) chart.
roc(12) ==> value of roc(12)
macd(fast_period, slow_period), macd_signal(...), macd_histogram(...) — values from the macd, macd_signal and macd_histogram charts.
macd(12, 26) ==> value of macd(12, 26)
macd_signal(12, 26, 9) ==> value of macd signal(12, 26, 9)
macd_histogram(12, 26, 9) ==> value of macd histogram(12, 26, 9)
sum("<stock_attr>", N) — the sum of a stock_attr over N bars.
sum("foreign", 5) ==> total foreign accumulation over the last 5 days
cum("<expression>") — the sum of an expression across every bar available, the equivalent of Cum() in AmiBroker or MetaStock.
cum("volume") ==> accumulated volume across all data
cum("close > open ? 1 : 0") ==> the number of green candles across all data
Because each calculation reads only a bounded stretch of recent bars, the total starts from the oldest bar available rather than from the day the stock was first listed. Its value is therefore relative: it cannot be compared between stocks, nor stored and compared again on a different day — the same caveat that applies to adl, top_broker and foreign.
For most purposes sum with an explicit period is the more useful of the two, since its result is comparable between stocks:
sum("volume", 20) ==> total volume over the last 20 bars
since("<date>") — the number of bars from that date up to the current bar. It is rarely used on its own; its purpose is to stand in for the period argument of another function, so that a calculation is bounded by a date rather than by a bar count.
The date can be written in several forms:
since("2026/01/01") ==> YYYY/MM/DD
since("01/01/2026") ==> DD/MM/YYYY
since("20260101") ==> YYYYMMDD
A hyphenated form such as 2026-01-01 is not recognised.
Examples:
sum("volume", since("2026/01/01")) ==> accumulated volume year to date
hhv("high", since("2026/01/01")) ==> the highest price year to date
llv("low", since("2026/07/01")) ==> the lowest price since the start of July
count("close > open", since("2026/01/01")) ==> green candles year to date
If the date requested is older than all the data held, the result is the number of bars available — the calculation stops at the oldest bar rather than at the date you wrote.
Because
sincecan return a large number, pair it with functions that have no tight period limit:sum,sma,hhv,llv,countandstdev. On functions such asemaortrix, a long date range can exceed the accurate period limit and return an empty value. See the accurate period limits table.
vwap(N) — value from the Volume-Weighted Average Price indicator.
vwap(N) = sum("volume * (high + low + close) / 3", N) / sum("volume", N)
vwap(5) ==> vwap over the last 5 days
vwma(N) — Volume Weighted Moving Average, a close-price average weighted by volume. Unlike vwap, the weighting is applied to the close.
vwma(20) ==> 20-period VWMA
close > vwma(20) ==> price above VWMA20
wma(N) and wma(subformula, N) — Weighted Moving Average, an average with rising weights: the newest bar carries weight N, the oldest weight 1. It responds to a change in price faster than sma over the same period.
wma(20) ==> 20-period WMA
wma("volume", 20) ==> 20-period WMA of volume
close > wma(20)
linreg(N) and linreg(subformula, N) — the value of the linear regression line through the last N bars, read at the current bar. That line is the straight line closest to every point in the range, not merely a line joining the first and last.
linreg(20) ==> the 20-bar regression line at the latest bar
close > linreg(20) ==> price sits above its own regression line
slope(N) and slope(subformula, N) — the gradient of that same line, in price units per bar. Positive when the line rises, negative when it falls.
slope(20) > 0 ==> the 20-bar trend is rising
slope(20) > slope(50) ==> the shorter trend is steeper
prev slope(20) < 0 and slope(20) > 0 ==> the gradient has just turned up
Because it is measured in price per bar, slope cannot be compared directly between stocks trading at very different prices. For screening across the market, convert it to a percentage first:
slope(20) / close * 100 > 0.5 ==> rising by more than 0.5% per bar
slope needs at least 2 bars; with a period of 1 the result is empty.
linreg_r2(N) and linreg_r2(subformula, N) — how closely price hugs that regression line, between 0 and 1. A value of 1 means every point sits exactly on the line; a value near 0 means the line explains essentially nothing about the movement.
slope gives the direction and steepness of a trend; linreg_r2 gives how much that trend is worth trusting. The two are complementary: two stocks can share almost the same gradient, one climbing tidily, the other wandering and merely finishing higher.
linreg_r2(20) > 0.7 ==> the last 20 bars follow the line closely
slope(20) > 0 and linreg_r2(20) > 0.7 ==> an orderly uptrend
slope(20) < 0 and linreg_r2(20) > 0.8 ==> a consistent downtrend
Over a completely flat range the result is 0. Like slope, it needs at least 2 bars.
atr(period), adx(period), cci(period)
atr(14) ==> value of the atr(14) line
adx(14) ==> value of the adx(14) line
pdx(14) ==> value of the DI+ line in adx(14)
ndx(14) ==> value of the DI- line in adx(14)
cci(20) ==> value of cci(20)
stoch_k(lookback_period, k_period) / stoch_d(lookback_period, k_period, d_period) — values from the Stochastic Oscillator chart.
stoch_k(15, 3) ==> Stochastic %K(15, 3)
stoch_d(15, 3, 3) ==> Stochastic %D(15, 3, 3)
bollinger_*(period, multiplier) — values from the Bollinger Bands chart.
bollinger_top(20, 2) ==> Bollinger Band top line(20, 2)
bollinger_bottom(20, 2) ==> Bollinger Band bottom line(20, 2)
stdev_p(n), stdev_s(n) and stdev(n) — the standard deviation over the last N bars: a measure of how widely values are spread around their average. The larger the number, the more volatile the movement.
Two forms are available, following Excel’s own naming:
stdev_p(20) ==> population standard deviation (same as Excel's STDEV.P)
stdev_s(20) ==> sample standard deviation (same as Excel's STDEV or STDEV.S)
stdev(20) ==> the same as stdev_s(20)
stdev_p is the form Bollinger Bands use, so it holds that:
stdev_p(20) == bollinger_top(20, 1) - bollinger_mean(20, 1)
The difference between the two is small — around 2.6% at period 20 and 0.5% at period 100 — but it widens at short periods, to roughly 12% at period 5. If you are matching results against a spreadsheet, use stdev. If you are recreating Bollinger bands, use stdev_p.
Without a subformula all three read the closing price. With one, they can be applied to any value:
stdev("volume", 20) ==> spread of 20-day volume
stdev("high - low", 20) ==> spread of the daily range
stdev("(close - prev close) / prev close * 100", 20)
==> volatility of daily returns, in percent
Examples:
close > sma("close", 20) + 2 * stdev_p(20)
==> breaking above two standard deviations
stdev("high - low", 20) < stdev("high - low", 60) * 0.7
==> daily range narrowing against the last three months
stdev(20) / sma("close", 20) * 100 < 2
==> volatility below 2% of price
stdev_s and stdev need at least 2 bars; with a period of 1 the result is empty.
rsi(n) — value from the Relative Strength Index chart.
rsi(14) ==> value of the rsi(14) line
stochrsi(...), dto_stochk(...), dto_stochd(...) — values from the StochRSI chart.
stochrsi(14, 8, 5, 3) ==> value of the stochrsi(14, 8, 5, 3) line
dto_stochk(14, 8, 5, 3) ==> the %K line of stochrsi(14, 8, 5, 3)
dto_stochd(14, 8, 5, 3) ==> the %D line of stochrsi(14, 8, 5, 3)
chaikin_osc(fast_period, slow_period) — the Chaikin Oscillator: the difference between two EMAs of the Accumulation/Distribution Line. It measures accumulation and distribution pressure by taking the position of the close within the daily range, multiplied by volume.
Positive values indicate accumulation pressure, negative values distribution. Zero-line crossings are commonly used as a signal.
chaikin_osc(3, 10) ==> Chaikin Oscillator with standard periods
chaikin_osc(5, 20) ==> a slower variant
Example: switching from distribution to accumulation.
prev chaikin_osc(3, 10) < 0 and chaikin_osc(3, 10) > 0
aroon_up(period) and aroon_down(period) — measure how recently the highest or lowest price was reached within the last N periods, not how high the price is.
Values range from 0 to 100:
- 100 means the high (or low) occurred on the current bar.
- 0 means it occurred on the oldest bar in the window.
aroon_up(25) ==> 100 if today is the highest price of the last 25 days
aroon_down(25) ==> 100 if today is the lowest price of the last 25 days
aroon_osc(period) — the difference between the two, aroon_up minus aroon_down. It ranges from −100 to +100; positive values indicate an upward bias.
aroon_osc(25) ==> equivalent to aroon_up(25) - aroon_down(25)
The usual periods are 25 (Chande’s default) and 14.
dema(period) and tema(period) — Double and Triple Exponential Moving Average of the close. Both track price faster than a plain EMA of the same period, making them more responsive to changes in direction.
dema(20) ==> 20-period DEMA
close > tema(50) ==> price above TEMA50
prev close <= prev dema(20) and close > dema(20)
trix(period) — the percentage rate of change of a triple-smoothed price. It oscillates around zero: positive indicates upward momentum, negative downward.
trix(15) ==> 15-period TRIX
prev trix(15) < 0 and trix(15) > 0 ==> momentum turning up
mass_index(ema_period, sum_period) — detects expansion in the price range (the high-to-low distance), which often precedes a reversal. Standard parameters are mass_index(9, 25).
The common pattern is the reversal bulge: the value rises above 27, then falls back below 26.5.
mass_index(9, 25) > 27
prev mass_index(9, 25) > 27 and mass_index(9, 25) < 26.5
tsi(long_period, short_period) — True Strength Index, measuring the strength and direction of momentum on a scale of −100 to +100. Standard parameters are tsi(25, 13).
tsi(25, 13) > 0 ==> net positive momentum
prev tsi(25, 13) < 0 and tsi(25, 13) > 0
donchian_up(period), donchian_down(period) and donchian_mid(period) — the upper band, lower band and midline of the Donchian Channel: the highest and lowest values over the last N bars.
donchian_up(20) ==> highest high of the last 20 bars
donchian_down(20) ==> lowest low of the last 20 bars
donchian_mid(20) ==> the midpoint of the two
The current bar is included, as with
hhvandllv. This meansclose > donchian_up(20)can never be true — to find a breakout, compare against the previous bar usingprev.
close > prev donchian_up(20) ==> upside breakout
close < prev donchian_down(20) ==> downside breakdown
ppo(fast_period, slow_period) — Percentage Price Oscillator, MACD expressed as a percentage. Because the unit is a percentage, values are comparable across stocks trading at very different price levels.
ppo(12, 26) ==> PPO with standard periods
ppo(12, 26) > 0 ==> short-term trend above long-term
prev ppo(12, 26) < 0 and ppo(12, 26) > 0
ppo_signal(fast_period, slow_period, signal_period) and ppo_histogram(fast_period, slow_period, signal_period) — the signal line and histogram for PPO, matching macd_signal and macd_histogram.
The signal line is a moving average of the PPO value itself; the histogram is the difference between the two (ppo minus ppo_signal).
ppo_signal(12, 26, 9) ==> signal line with standard periods
ppo_histogram(12, 26, 9) ==> PPO relative to its signal line
PPO crossing its signal line — equivalent to the histogram turning from negative to positive:
prev ppo_histogram(12, 26, 9) < 0 and ppo_histogram(12, 26, 9) > 0
Momentum strengthening while the trend is still below zero:
ppo(12, 26) < 0 and ppo_histogram(12, 26, 9) > 0
pvo(fast_period, slow_period) — Percentage Volume Oscillator, the same formula applied to volume. Useful for spotting surges or a drying-up of trading activity.
pvo(12, 26) > 0 ==> short-term volume above the long-term average
ao(fast_period, slow_period) — Awesome Oscillator, the difference between two simple moving averages of the median price. Standard parameters are ao(5, 34).
ao(5, 34) ==> Awesome Oscillator value
prev ao(5, 34) < 0 and ao(5, 34) > 0 ==> zero-line crossing
alligator_aois also available as astock_attr, but its value follows the Alligator(13,8,5) chart settings. Useao(5, 34)if you want the standard periods.
Maths functions
These work on plain numbers rather than price series, so they can be used anywhere in a formula.
abs(x) ==> absolute value
pow(x, y) ==> x to the power of y
sqrt(x) ==> square root
exp(x) ==> e to the power of x
ln(x) ==> natural logarithm, base e
log10(x) ==> logarithm base 10
log(x, base) ==> logarithm to a base you choose
ceil(x), ceil(x, d) ==> round up, optionally to d decimal places
floor(x), floor(x, d) ==> round down
round(x), round(x, d) ==> round to nearest
trunc(x, d) ==> truncate decimals without rounding
min(a, b, ...) ==> the smallest of several arguments
max(a, b, ...) ==> the largest
avg(a, b, ...) ==> the average of several arguments
Examples:
abs(close - open) / open * 100 > 3 ==> intrabar move above 3%, up or down
max(high, prev high) > bollinger_top
A note on logarithm naming. There is deliberately no single-argument
log: in some applicationslogmeans the natural logarithm, in others base 10. Writeln(x)for base e,log10(x)for base 10, orlog(x, base)to choose the base yourself.
The natural logarithm is what makes log returns writable, the usual unit of quantitative analysis:
ln(close / prev close) ==> daily log return
sum("ln(close / prev close)", 20) ==> cumulative 20-day log return
stdev("ln(close / prev close)", 20) * sqrt(252) * 100
==> annualised volatility, in percent
ln and log10 return an empty value when their argument is zero or negative, as does sqrt for a negative argument.
med(n) and med(subformula, n) — the median (not the mean) over the last N bars. More resistant to a single extreme bar than sma.
med("volume", 20) ==> 20-day median volume
volume > med("volume", 20) * 3
count(subformula, n) — counts how many times a condition holds over the last N bars.
count("close > open", 20) >= 14 ==> at least 14 green candles out of 20
count("volume > sma(\"volume\", 20)", 10) >= 5
hhvbars(subformula, n) and llvbars(subformula, n) — how many bars have passed since the highest or lowest value occurred within the last N bars. A value of 0 means it occurred on the current bar.
hhvbars("high", 50) == 0 ==> today is the 50-day high
llvbars("low", 20) <= 3 ==> the 20-day low occurred within the last 3 bars
Swing pivots
pivot_high(k) and pivot_low(k) — the price at the most recent confirmed swing point. A bar is a pivot high when its high stands above every bar within K bars to its left and right; a pivot low is the mirror image.
K sets the size of the swing you are looking for. The larger K is, the fewer pivots are found, and the larger the swings they represent.
pivot_high(5) ==> the price at the last swing peak
pivot_low(5) ==> the price at the last swing trough
A pivot can only be confirmed once K further bars have formed, so the value never changes after the fact.
pivot_high_bars(k) and pivot_low_bars(k) — how many bars have passed since that pivot occurred. The smallest possible value is K, matching the confirmation delay above.
pivot_high_bars(5) == 5 ==> the peak has just been confirmed
pivot_high_bars(5) < pivot_low_bars(5) ==> the peak is newer than the trough
All four take an optional second argument: which pivot to read, counting back from the most recent.
pivot_high(5, 1) ==> the last peak, the same as pivot_high(5)
pivot_high(5, 2) ==> the peak before it
That makes two consecutive swings directly comparable:
pivot_high(5, 1) > pivot_high(5, 2) ==> higher high
pivot_low(5, 1) > pivot_low(5, 2) ==> higher low
pivot_high(5, 1) < pivot_high(5, 2) and pivot_low(5, 1) < pivot_low(5, 2)
==> lower high and lower low together
When the requested pivot cannot be found in the available data, the result is empty.
The
up_fractalanddown_fractalvariables do much the same job with K fixed at 2.pivot_high(2)andpivot_low(2)are their closest equivalent — the two differ slightly in how they treat several bars sharing exactly the same high — with the added ability to set K and to step back through earlier pivots.
Trade book functions
These read the trade book — the breakdown of transactions at each price level within a bar.
trade_book(price) — the transaction volume executed at that price.
freq(price) — the transaction frequency at that price.
trade_book(close) > trade_book(open)
tick_up(price) and tick_down(price) — the price one tick above or below the given price, following the exchange’s price fraction rules.
close == tick_up(prev close) ==> up exactly one tick from yesterday's close
Accurate period limits
Every calculation reads only a bounded slice of recent bars. The longer the period you ask for, the more of that history it consumes — and past a certain point the result is no longer reliable.
The table below gives the maximum period that still produces an accurate result. Beyond it the function returns an empty value and the stock will not appear in your screening results.
| Function | Safe period | Notes |
|---|---|---|
sma, hhv, llv, sum, ranking | up to 500 | No practical limit |
wma, linreg, slope, linreg_r2 | up to 500 | No practical limit |
stdev_p, stdev_s, stdev | up to 500 | No practical limit |
pivot_high, pivot_low, and the _bars variants | K up to 100 | The larger K is, the fewer pivots are found |
aroon_up, aroon_down, aroon_osc | up to 500 | No practical limit |
donchian_up, donchian_down, donchian_mid | up to 500 | No practical limit |
vwma, vwap, ao | up to 500 | No practical limit |
ema | 120 | Up to 250 when used on price (close, open, high, low) |
aema | 140 | Up to 200 on price; more accurate than ema for volume and ratios |
dema, tema | 160 | |
trix | 75 | Considerably stricter than the other functions |
ppo, pvo | slow_period up to 140 | Up to 200 for ppo; the standard (12, 26) is well inside |
ppo_signal, ppo_histogram | slow + signal up to 128 | The standard (12, 26, 9) is well inside |
chaikin_osc | slow_period up to 60 | The standard (3, 10) is well inside |
mass_index | ema_period up to 100 | The standard (9, 25) is well inside |
tsi | long + short up to 120 | The standard (25, 13) is well inside |
These are conservative figures that hold for any kind of input. Where your criterion works on closing prices, ema and aema stay accurate to the longer periods noted in the last column.
For everyday use, the periods people actually reach for — 5, 9, 14, 20, 26, 50, even 100 — sit far inside the limit for every function.
Boolean functions
Unlike a stock_function, which produces a number, the following produce a true or false value directly — so they can be used as a criterion without a comparison operator.
code_index("<index code>") — tests whether the stock being evaluated is a member of that index. Returns true if it is, false otherwise.
code_index("ISSI") ==> true for members of the ISSI index
Because the result is already a Boolean Expression, the function is written as-is and combined with and or or:
code_index("ISSI") and rsi < 30
code_index("ISSI") and code_index("LQ45")
code_index("LQ45") and close > sma("close", 20)
Writing it with a comparison operator — code_index("ISSI") > 0, for instance — is not valid, because there is no numeric value to compare.
stock_fundamental_attr
debt_equity, ebitda, ebitda_anl, eps, eps_anl
ev_ebitda, ev_ebitda_anl, market_cap, netprofit, netprofit_anl
pbv, pbv_anl, per, per_anl, revenue, revenue_anl
roa, roa_anl, roe, roe_anl
The _anl suffix means the annualized value; without it, the value comes from the most recent financial report.
debt_equity— Debt to Equity Ratioebitda/ebitda_anl— Earnings Before Interest, Taxes, Depreciation and Amortizationeps/eps_anl— Earning Per Shareev_ebitda/ev_ebitda_anl— Enterprise Multiplemarket_cap— Market Capitalizationnetprofit/netprofit_anl— Net Profitpbv/pbv_anl— Price-to-Book Ratioper/per_anl— Price-Earnings Ratiorevenue/revenue_anl— Revenueroa/roa_anl— Return on Assetsroe/roe_anl— Return on Equity
bid_offer_variables
The variables in this section, together with Other variables and Broker transaction variables below, have a limitation when reading values from earlier bars. See Earlier-bar values for bid, offer and broker variables.
top1_bid_volume, top1_bid_count, top1_offer_volume, top1_offer_count
top3_bid_volume, top3_bid_count, top3_offer_volume, top3_offer_count
top5_bid_volume, top5_bid_count, top5_offer_volume, top5_offer_count
top10_bid_volume, top10_bid_count, top10_offer_volume, top10_offer_count
all_bid_volume, all_bid_count, all_offer_volume, all_offer_count

top1_bid_volume— total volume on the bid at the best price, in sharestop1_bid_count— number of orders on the bid at the best pricetop1_offer_volume— total volume on the offer at the best price, in sharestop1_offer_count— number of offer orders at the best pricetop3_*,top5_*,top10_*— the same pattern across the 3, 5 and 10 best pricesall_bid_volume— total volume on the bid across all prices, in sharesall_bid_count— number of orders on the bid across all pricesall_offer_volume— total volume on the offer across all prices, in sharesall_offer_count— number of offer orders across all prices
Function forms are also available:
bid_volume(n) : volume on the bid at the nth best price, where n: 1,2,3,...
bid_count(n) : number of orders on the bid at the nth best price
offer_volume(n) : volume on the offer at the nth best price
offer_count(n) : number of orders on the offer at the nth best price
sum_bid_volume(n) : total volume on the bid across the n best prices
sum_bid_count(n) : number of orders on the bid across the n best prices
sum_offer_volume(n) : total volume on the offer across the n best prices
sum_offer_count(n) : number of orders on the offer across the n best prices
Other variables
To check the value of a variable or test a formula, use the Formula Evaluator menu.
best_bid_price— the highest (best) bid price for the stockbest_offer_price— the lowest (best) offer price for the stockblot— transaction volume executed at the bid price (ha-ki), in lotsslot— transaction volume executed at the offer price (ha-ka), in lotsbvol— transaction volume executed at the bid price, in sharessvol— transaction volume executed at the offer price, in sharesbfreq— transaction frequency at the bid pricesfreq— transaction frequency at the offer priceall_buy_volume/all_sell_volume— the same asbvol/svolall_buy_freq/all_sell_freq— the same asbfreq/sfreqbroker_buyer_count— the number of brokers buyingbroker_seller_count— the number of brokers sellingnet_broker_buyer_count— the number of brokers with a net buynet_broker_seller_count— the number of brokers with a net sell
Broker transaction variables
Available in the pattern top{1,3,5,10}_net_broker_{buyer,seller}_{volume,value,avg_price}:
top1_net_broker_buyer_volume : net buy volume of the single largest broker, in shares
top1_net_broker_buyer_value : net buy transaction value of the single largest broker
top1_net_broker_buyer_avg_price : average net buy price of the single largest broker
The same pattern applies to top3_, top5_, top10_ and to the seller side, giving the full list:
top1_net_broker_buyer_volume top1_net_broker_buyer_value top1_net_broker_buyer_avg_price
top3_net_broker_buyer_volume top3_net_broker_buyer_value top3_net_broker_buyer_avg_price
top5_net_broker_buyer_volume top5_net_broker_buyer_value top5_net_broker_buyer_avg_price
top10_net_broker_buyer_volume top10_net_broker_buyer_value top10_net_broker_buyer_avg_price
top1_net_broker_seller_volume top1_net_broker_seller_value top1_net_broker_seller_avg_price
top3_net_broker_seller_volume top3_net_broker_seller_value top3_net_broker_seller_avg_price
top5_net_broker_seller_volume top5_net_broker_seller_value top5_net_broker_seller_avg_price
top10_net_broker_seller_volume top10_net_broker_seller_value top10_net_broker_seller_avg_price
Versions without the word net are available in the same pattern, covering _volume, _value and _avg_price:
top1_broker_buyer_volume top1_broker_buyer_value top1_broker_buyer_avg_price
top3_broker_buyer_volume top3_broker_buyer_value top3_broker_buyer_avg_price
top5_broker_buyer_volume top5_broker_buyer_value top5_broker_buyer_avg_price
top10_broker_buyer_volume top10_broker_buyer_value top10_broker_buyer_avg_price
top1_broker_seller_volume top1_broker_seller_value top1_broker_seller_avg_price
top3_broker_seller_volume top3_broker_seller_value top3_broker_seller_avg_price
top5_broker_seller_volume top5_broker_seller_value top5_broker_seller_avg_price
top10_broker_seller_volume top10_broker_seller_value top10_broker_seller_avg_price
Earlier-bar values for bid, offer and broker variables
The variables in the three sections above — bid_offer_variables, Other variables and Broker transaction variables — come from the live data feed rather than from a calculation over candle history.
For some past bars their value is not stored. When that happens, prev and prev_N return the current value instead of the value on the bar you asked for.
prev top5_net_broker_buyer_volume ==> may return the same value as the
current bar
So comparisons across bars on these variables are not yet dependable — the result can be a value compared against itself:
top5_net_broker_buyer_volume > prev top5_net_broker_buyer_volume
all_bid_volume > prev all_bid_volume
The same applies when these variables are used inside an sma, hhv, llv, sum or count subformula, since all of those read a value per bar.
Variables and functions computed from candle history, by contrast, support prev fully:
close > prev close
volume > prev volume
foreign > prev foreign
top_broker > prev_5 top_broker
rsi(14) < prev rsi(14)
To check whether a particular variable returns a different value on an earlier bar, use the Formula Evaluator menu.
We are preparing stored history for these variables, starting with the daily timeframe. This page will be updated once it is available.
Building your own formulas
Beyond the built-in functions, you can assemble calculations yourself. The key: sma, ema, aema, sum, hhv, llv and ranking accept a subformula that is a full expression, not merely a stock_attr name. Look at the definition of vwap above — it contains sum("volume * (high + low + close) / 3", N), a complete expression inside quotes.
This section shows how that works, using several widely known indicators.
Every indicator in this section is now available as its own function. For everyday use, call the function directly — it is shorter, faster, and less prone to typos. The long forms are kept here as worked examples, so you can apply the same pattern to indicators that do not yet have one.
Donchian Channel
The upper and lower Donchian bands are the highest and lowest values over the last N bars:
hhv("high", 20) ==> upper band
llv("low", 20) ==> lower band
(hhv("high", 20) + llv("low", 20)) / 2 ==> midline
Available directly as donchian_up(20), donchian_down(20) and donchian_mid(20).
VWMA — Volume Weighted Moving Average
A close-price average weighted by volume. The pattern matches the definition of vwap:
sum("volume * close", 20) / sum("volume", 20)
Available directly as vwma(20).
PPO and PVO
MACD expressed as a percentage, and the same measure applied to volume:
(ema("close", 12) - ema("close", 26)) / ema("close", 26) * 100
(ema("volume", 12) - ema("volume", 26)) / ema("volume", 26) * 100
Available directly as ppo(12, 26) and pvo(12, 26).
Awesome Oscillator
The difference between two simple moving averages of the median price. Since mid_price is already (high + low) / 2, this becomes:
sma("mid_price", 5) - sma("mid_price", 34)
Available directly as ao(5, 34).
Crossover and breakout signals
The signals people reach for — golden cross, death cross, breakout — are fundamentally a comparison between the previous bar and the current one. Writing them yourself lets you choose your own periods, and keeps the criterion readable.
The pattern is always the same: the condition did not hold on prev, and holds now.
Golden cross and death cross, MA 5–20
prev sma("close", 5) < prev sma("close", 20) and sma("close", 5) > sma("close", 20)
prev sma("close", 5) > prev sma("close", 20) and sma("close", 5) < sma("close", 20)
Replace 5 and 20 with whatever periods suit your strategy, or swap sma for ema or aema.
MACD crossing the zero line
prev macd(12, 26) < 0 and macd(12, 26) > 0 ==> golden cross
prev macd(12, 26) > 0 and macd(12, 26) < 0 ==> death cross
MACD crossing its signal line — equivalent to the histogram changing sign
prev macd_histogram(12, 26, 9) < 0 and macd_histogram(12, 26, 9) > 0
prev macd_histogram(12, 26, 9) > 0 and macd_histogram(12, 26, 9) < 0
Stochastic %K crossing %D
prev stoch_k(15, 3) < prev stoch_d(15, 3, 3) and stoch_k(15, 3) > stoch_d(15, 3, 3)
prev stoch_k(15, 3) > prev stoch_d(15, 3, 3) and stoch_k(15, 3) < stoch_d(15, 3, 3)
Fractal breakout
high > up_fractal and prev close <= up_fractal ==> breaking the upper fractal
low < down_fractal and prev close >= down_fractal ==> breaking the lower fractal
Confirming across many moving averages at once
The ma_net_buy_sell_signal_count variable nets buy against sell signals across 12 moving averages, ranging from −12 to +12.
ma_net_buy_sell_signal_count >= 6 ==> most moving averages strengthening
ma_net_buy_sell_signal_count <= -6 ==> most weakening
Fibonacci retracement
There is no single fib() function in the app, and that is deliberate. A Fibonacci result depends entirely on which points are used as anchors: two people drawing lines on different swings get different numbers, and both are legitimate. So the anchors are yours to choose.
The arithmetic itself is simple. Once a peak and a trough are settled on:
peak - (peak - trough) * ratio ==> up swing, retracement measured down from the peak
trough + (peak - trough) * ratio ==> down swing, retracement measured up from the trough
The ratios in common use are 0.236, 0.382, 0.5, 0.618 and 0.786. The 0.5 is not a Fibonacci ratio at all, merely the midpoint, but it has long since become conventional.
Anchored on swing pivots
The closest thing to the line you would draw yourself on a chart:
pivot_low_bars(5) > pivot_high_bars(5)
? pivot_high(5) - (pivot_high(5) - pivot_low(5)) * 0.618
: pivot_low(5) + (pivot_high(5) - pivot_low(5)) * 0.618
The ? : operator settles the direction of the swing. If the trough is older than the peak, price has just climbed, so the retracement is measured down from the peak.
Anchored on an N-bar range
Simpler, using the highest and lowest price over the last N bars:
llvbars("low", 60) > hhvbars("high", 60)
? hhv("high", 60) - (hhv("high", 60) - llv("low", 60)) * 0.618
: llv("low", 60) + (hhv("high", 60) - llv("low", 60)) * 0.618
Screening for a stock that touched a level
low <= pivot_high(5) - (pivot_high(5) - pivot_low(5)) * 0.618
and close > pivot_high(5) - (pivot_high(5) - pivot_low(5)) * 0.618
==> price dipped through the 61.8% level during the day, then closed back above it.
A shortcut: how far price has already retraced
For screening, what is usually wanted is not the level itself but how far price has retraced within its swing. That number is already available through stoch_k with a smoothing period of 1:
stoch_k(60, 1) ==> where price sits within the 60-bar range, as a percentage
On an up swing, a retracement of R means stoch_k reads 100 − R:
| Retracement | stoch_k(N, 1) |
|---|---|
| 23.6% | 76.4 |
| 38.2% | 61.8 |
| 50.0% | 50.0 |
| 61.8% | 38.2 |
| 78.6% | 21.4 |
So “retraced close to 61.8% of the 60-bar swing” is simply:
stoch_k(60, 1) > 35 and stoch_k(60, 1) < 42
Patterns with no function
Some calculations have no function name but are still easy to write with the same pattern.
Distance from a moving average — useful for screening out stocks that have run too far from their average.
(close - sma("close", 20)) / sma("close", 20) * 100 > 5
Volume spike
volume > sma("volume", 20) * 2
The average of an expression — anything you can write as a single-bar expression, you can average.
sma("(high - low)", 14) ==> average daily range
sma("(close - open) / open * 100", 5) ==> average intrabar change
sum("top_broker_hist * close", 5) ==> 5-day broker accumulation value
Ratios across timeframes
volume / weekly volume ==> daily volume as a share of weekly
close / weekly sma("close", 4) ==> price against the 4-week average
Screener formula examples
Higher high higher low
prev high < high and prev low < low
Inside bar
prev high > high and prev low < low
Golden cross MA 5-20 (the MA 5 line crossing above MA 20)
prev sma(5) < prev sma(20) and sma(5) > sma(20)
MACD rising
prev macd < macd and macd > 0
Three white soldiers
prev_2 close > prev_2 open
and prev close > prev open
and close > open
The 10 highest accumulation stocks by top-5-net-broker-accumulation value
ranking("top_broker_hist * daily_price_avg") <= 10
Stochastic %K crossing %D from below
prev stoch_k < prev stoch_d and stoch_k > stoch_d
Stocks that have just set a 25-day high
aroon_up(25) == 100
A strong uptrend by Aroon — the high is recent, the low is old
aroon_up(25) > 70 and aroon_down(25) < 30
Aroon Oscillator crossing up — a turn from down to up
prev aroon_osc(25) < 0 and aroon_osc(25) > 0
A new trend forming, not yet overbought
aroon_up(14) > 70 and aroon_down(14) < 30 and rsi < 70
Accumulation by Chaikin Oscillator, with above-average volume
chaikin_osc(3, 10) > 0
and prev chaikin_osc(3, 10) < 0
and volume > sma("volume", 20)
Accumulation divergence — price sets a new high while accumulation falls away
close == hhv("close", 20) and adl < prev_5 adl
Double confirmation — Chaikin positive alongside top 5 broker accumulation
chaikin_osc(3, 10) > 0 and top_broker_hist > 0
Restricting results to members of a particular index
code_index("ISSI") and close > sma("close", 20)
Liquid stocks with large transaction value — 5-day average above 1 billion
sma("value", 5) > 1B
A combination: price above the weekly SMA26, MACD histogram rising below the centre line, and Stochastic %K below 19 crossing %D from below.
close > weekly sma("close", 26)
and macd_histogram < 0
and prev_2 macd_histogram < prev macd_histogram
and prev macd_histogram < macd_histogram
and stoch_k < 19
and prev stoch_k < prev stoch_d
and stoch_k > stoch_d
Complete screener examples
The section above shows single conditions. This one assembles them into whole screeners, together with the tradeability filter that is most often left out.
One thing to be clear about first: a screener filters, it does not predict. It narrows hundreds of stocks down to the few meeting conditions you set yourself. When to enter, when to leave, and where to stop remain your decisions — and that is where the result is actually determined. The formulas below are examples to study and test, not recommendations.
The round-trip cost that is easy to forget
Every trade carries two costs before the price moves at all: crossing one price fraction, and the broker fee together with the sales tax. The exchange’s price fraction is tiered, so its weight relative to price differs sharply between levels:
| Price | Fraction | 1 fraction | + fees ±0.4% | Share of a 2% target |
|---|---|---|---|---|
| Rp 100 | 1 | 1.00% | 1.40% | 70% |
| Rp 200 | 2 | 1.00% | 1.40% | 70% |
| Rp 500 | 5 | 1.00% | 1.40% | 70% |
| Rp 1,200 | 5 | 0.42% | 0.82% | 41% |
| Rp 1,999 | 5 | 0.25% | 0.65% | 33% |
| Rp 4,999 | 10 | 0.20% | 0.60% | 30% |
| Rp 10,000 | 25 | 0.25% | 0.65% | 33% |
The cost is heaviest just above each fraction boundary: at Rp 200, Rp 500, Rp 2,000 and Rp 5,000 a single fraction is a full 1% or 0.5% of the price. On a 2% target that consumes 30% to 70% of the target before the stock does anything.
The consequence is clearest when you work out how often a strategy must be right merely to break even:
| Target | Stop | Must be right |
|---|---|---|
| 2% | 2% | 72.5% |
| 2% | 1% | 63.3% |
| 3% | 1.5% | 53.3% |
| 7% | 3% | 39.0% |
| 10% | 5% | 37.7% |
This is why a 5–10% target over a few days is generally easier to live with than 2–3% within a day. Not because it is easier to predict, but because the fixed cost weighs proportionally far less.
The tradeability filter
All three examples below open with the same line. It discards stocks that trade too thinly, and stocks whose price fraction is too expensive relative to their price:
value > 5B and tick / close * 100 < 0.30
The tick variable holds the price fraction in force, so tick / close * 100 gives the cost of one fraction as a percentage directly. No need to guess at a price floor.
Intraday momentum
Finding stocks moving on volume above their own habit, but not yet run too far:
value > 5B
and tick / close * 100 < 0.30
and projected_volume > sma("volume", 20) * 2
and change_percent > 1
and change_percent < 6
and bvol > svol
projected_volume is the key piece: it estimates volume through to the end of the session from the pace so far, so “unusual volume” can be detected at ten in the morning rather than only near the close. The change_percent < 6 ceiling discards stocks whose move is already spent. bvol > svol says more business is being done on the demand side.
Breakout with confirmation
For a two- to three-day hold:
value > 5B
and tick / close * 100 < 0.30
and close > prev hhv("high", 20)
and volume > sma("volume", 20) * 2
and slope(50) > 0
and linreg_r2(50) > 0.5
and close > sma("close", 200)
linreg_r2 is what separates a breakout from an orderly base from a breakout out of pure chop. Without that line the two look identical.
A pullback inside an orderly trend
Buying weakness within strength, while waiting for the turn:
value > 5B
and tick / close * 100 < 0.30
and slope(60) > 0
and linreg_r2(60) > 0.6
and close > sma("close", 200)
and rsi < 40
and close > prev close
That last line waits for price to turn back up rather than catching a falling knife.
Making them your own
Every number above — 20, 50, 60, 5B, 0.30, 40 — is a choice, not a fixture. Change them and test against the stocks you actually follow. A good screener reflects how you read the market, not how someone else does.
For minute-by-minute watching there is also a far shorter form, such as finding stocks with at least one billion rupiah of haka in the last minute:
1min slot * 100 * close >= 1B
Questions that come up often
These are the things most frequently asked, with their answers.
The formula looks right but the result is always empty
The most common cause: hhv, highest, llv and lowest already include the current bar. So this is almost never true:
close > hhv("close", 20) ==> almost always false
If today really is the highest close in 20 bars, then hhv("close", 20) equals close, which makes close > hhv(...) false. Compare against the earlier bars instead:
close > prev hhv("close", 19) ==> breaking above the previous 19 bars' high
To say “today is the highest of the last 20 bars” there is a shorter form:
hhvbars("close", 20) == 0
The multiplication sign
Multiplication is written with an asterisk *, not the letter x:
volume >= 2 * ema("volume", 5) ==> correct
volume >= 2 x ema("volume", 5) ==> not valid
The tick variable at fraction boundaries
tick holds the price fraction in force at the current price. Because the fraction changes at Rp 200, Rp 500, Rp 2,000 and Rp 5,000, a calculation that crosses one of those boundaries can give an unexpected result.
Take a stock that opens at 1,980 and closes at 2,020:
close >= open + 5 * tick
2020 >= 1980 + 5 * 10 ==> 2020 >= 2030, which is false
The rise actually spans two fraction tiers, but tick at the close is 10. For a range that may cross a boundary, a percentage is safer:
close >= open * 1.02
The bandarmology figure differs when you add it up yourself
The 20 days top 5 broker accumulation shown on the Bandarmology tab is calculated like this:
sum("top_broker_hist * avg_price", 20)
Reading it through:
top_broker_hist— the net buy volume of the top 5 brokers minus their net sell volume, in sharesavg_price— the average transaction price on that bar, so the product is a transaction value- the two are summed over the last 20 bars
Summing top_broker_hist on its own, without multiplying by price, gives a different number — its unit is shares, not rupiah.
When broker transaction data is updated
top_broker_hist and the other broker transaction variables are updated after the market closes, at around 18:00 on each trading day, and reach back roughly six months.
What the screener scans
Stocks, warrants and ETFs are all scanned when they meet the criteria. Indices, sectoral indices, world indices, commodities and currencies are not included in screener results.
How often results refresh
During trading hours the screener refreshes its results automatically every minute.
If you are still stuck
Send a description of the condition you are looking for to idx.datasaham@gmail.com. Describe what you want to find rather than the formula, and we will help you write it.