Language reference
Sift
A query language for candles. SQL’s structure and English’s readability, over 3,740 NSE instruments with daily history to 25 Sept 2026.
Overview#
The common case is one line with no ceremony. Everything else is optional.
close > ema(21)That is a complete query. It reads the latest bar of every stock in the universe and keeps the ones closing above their 21-day exponential moving average. There is no timeframe to declare, no latest to repeat, and no wrapper.
Compare the same three intents against Chartink, whose syntax makes you restate the timeframe on every single term:
| Intent | Chartink | Sift |
|---|---|---|
| Close above the 20 EMA | ( {cash} ( latest close > latest ema( latest close , 20 ) ) ) | close > ema(21) |
| Volume twice its 20-bar average | ( {cash} ( latest volume > 2 * latest sma( latest volume , 20 ) ) ) | volume > 2x avg(volume, 20) |
| RSI crossed 60 in the last 3 bars | not expressible | rsi(14) crossed above 60 within 3 bars |
| Within 2% of the 52-week high | manual max plus arithmetic | close within 2% of high_52w |
Sift is not Turing complete: no loops, no user-defined functions, no side effects. Every query is statically analysable, which is what lets the editor underline a mistake before you run anything and lets the engine bound the cost of a scan before it starts.
Query shape#
Five clauses, all optional, conventionally in this order.
on <timeframe>
from <universe>
where <condition>
sort by <expression> [asc | desc]
top <n>A bare condition is a complete query — where is optional. Clauses may appear in any order. Newlines and indentation carry no meaning, and # or // starts a comment that runs to the end of the line.
on daily
from NSE
where close > ema(21) > ema(50) # a trend in good order
and volume > 2x avg(volume, 20)
and delivery_pct > 55
sort by turnover desc
top 25| Clause | Purpose | Default |
|---|---|---|
| on | Candle timeframe. | daily — the only one this dataset carries |
| from | Which universe to scan: `NSE` (all equities) or `fno` (stocks with listed futures & options). | NSE |
| where | The condition. Optional keyword. | everything matches |
| sort by | Order the results by any expression. | turnover, descending |
| top | Row cap. | 300 |
sort by accepts any expression, not just a returned column — sort by (close - ema(50)) / ema(50) desc ranks by distance above the average.Fields#
Written bare, with no parentheses. Each maps to one precomputed column.
Price
| Field | Meaning |
|---|---|
| close | Closing price, adjusted for splits and bonuses. |
| open | Opening price. |
| high | Session high. |
| low | Session low. |
| hl2 | Midpoint of the session range. |
| hlc3 | Typical price. |
| ohlc4 | Average of all four session prices. |
| ha_open | Heikin-Ashi open — the average of the previous HA open and close. |
| ha_high | Heikin-Ashi high. |
| ha_low | Heikin-Ashi low. |
| ha_close | Heikin-Ashi close — the average of the bar's four prices. |
| high_52w | Highest high of the last 252 sessions. The same as `highest_high(1y)`; write `highest_high(N)` for any other window. |
| low_52w | Lowest low of the last 252 sessions. The same as `lowest_low(1y)`; write `lowest_low(N)` for any other window. |
| pct_from_52w_high | Distance below the 52-week high, as a negative percentage. The same as `pct_from_high(1y)`; write `pct_from_high(N)` for any other window. |
| pct_from_52w_low | Distance above the 52-week low, as a percentage. The same as `pct_from_low(1y)`; write `pct_from_low(N)` for any other window. |
| pivot | Classic floor-trader pivot from the previous session. |
| pivot_r1 | First resistance above the pivot. |
| pivot_r2 | Second resistance above the pivot. |
| pivot_s1 | First support below the pivot. |
| pivot_s2 | Second support below the pivot. |
Volume & delivery
| Field | Meaning |
|---|---|
| volume | Shares traded. |
| turnover | Close × volume, in rupees. |
| trades | Number of trades executed. |
| delivery_pct | Share of volume taken to demat rather than squared off intraday. High delivery on a rising day suggests genuine accumulation. |
| delivery_qty | Shares taken to demat. |
| rel_volume | Volume divided by its own 20-day average. 2 means twice normal. |
| obv | Running total of volume signed by the day's direction. |
| acc_dist | Running total weighted by where the close sits in the range. |
| cmf | Accumulation/distribution normalised over 20 bars. Positive means buying pressure. |
| force_index | Price change times volume, smoothed over 13 bars. |
Momentum
| Field | Meaning |
|---|---|
| stoch_k | Slow stochastic %K over 14 bars, smoothed by 3. |
| stoch_d | 3-bar average of stochastic %K. |
| stoch_rsi | The stochastic oscillator applied to RSI itself. |
| cci | Commodity Channel Index over the typical price. |
| williams_r | Like the stochastic, scaled −100 to 0. |
| mfi | A volume-weighted RSI over the typical price. |
| roc | Percentage change over 10 bars. |
Trend
| Field | Meaning |
|---|---|
| adx | Trend strength, direction-agnostic. Above 25 is usually read as trending. |
| di_plus | Positive directional indicator. |
| di_minus | Negative directional indicator. |
| supertrend | ATR-banded trailing stop (10, 3). |
| supertrend_dir | +1 while Supertrend is bullish, −1 while bearish. |
| psar | Parabolic stop-and-reverse (0.02, 0.2). |
| aroon_up | How recently the 25-bar high occurred. |
| aroon_down | How recently the 25-bar low occurred. |
| aroon_osc | Aroon up minus Aroon down. |
| ichimoku_conversion | Tenkan-sen, the 9-bar midpoint. |
| ichimoku_base | Kijun-sen, the 26-bar midpoint. |
| ichimoku_span_a | Cloud edge A, unshifted. |
| ichimoku_span_b | Cloud edge B, unshifted. |
Volatility
| Field | Meaning |
|---|---|
| atr | Wilder's average true range over 14 bars. |
| true_range | This bar's true range. |
| bb_upper | 20-period SMA plus two standard deviations. |
| bb_mid | 20-period simple moving average. |
| bb_lower | 20-period SMA minus two standard deviations. |
| bb_pct_b | Where the close sits within the bands: 0 at the lower, 1 at the upper. |
| bb_width | Band separation as a fraction of the middle band. Low means a squeeze. |
| donchian_upper | Highest high of the last 20 bars. |
| donchian_mid | Midpoint of the Donchian channel. |
| donchian_lower | Lowest low of the last 20 bars. |
| keltner_upper | 20-EMA plus two ATRs. |
| keltner_lower | 20-EMA minus two ATRs. |
Performance
| Field | Meaning |
|---|---|
| change | Percentage change against the previous close. The same as `return(1)`; write `return(N)` for any other span. |
| return_1w | Percentage change over 5 sessions. |
| return_1m | Percentage change over 21 sessions. |
| return_3m | Percentage change over 63 sessions. |
| return_6m | Percentage change over 126 sessions. |
| return_1y | Percentage change over 252 sessions. The same as `return(1y)`; write `return(N)` for any other span, e.g. `return(3y)`. |
Derivatives (F&O)
| Field | Meaning |
|---|---|
| fut_oi | Total futures open interest across all expiries, in contracts. |
| fut_oi_change_pct | Day-over-day change in total futures OI. Read it with price: price up with OI up is long buildup, price down with OI up is short buildup. The drop after an expiry day is genuine, not noise. |
| fut_volume | Futures contracts traded across all expiries. |
| fut_basis_pct | Front-month futures premium (positive) or discount (negative) to the cash price, in percent. Compared on the raw price scale — corporate actions are bridged out. Reads near zero on expiry day by construction. |
| fut_rollover_pct | Share of futures OI already sitting in later expiries. On expiry day this is the classic rollover number the derivatives desks quote. |
| pcr_oi | Put OI divided by call OI across this stock's options, all expiries. Above 1 means more open puts than calls. NULL when no calls are open. |
| pcr_vol | Put contracts traded divided by call contracts traded, all expiries. |
Fundamentals
| Field | Meaning |
|---|---|
| marketcap | Close times shares outstanding, in rupees — write `marketcap > 5000cr`. The share count is restated onto the adjusted-price basis, so a split does not fake a jump. |
| pe | Price to trailing-twelve-month earnings, from the last four filed quarters as known on that date. NULL when TTM earnings are negative or not yet filed — a loss-maker has no P/E rather than a misleading one. |
| eps_ttm | Trailing-twelve-month earnings per share, split-adjusted to match the adjusted price series (filed EPS is not — it is never used directly). Negative for loss-makers, NULL until four consecutive quarters are on file. |
| revenue_growth_yoy | Latest filed quarter's revenue against the same quarter last year. |
| profit_growth_yoy | Latest filed quarter's net profit against the same quarter last year. NULL when the base quarter was a loss — growth from negative earnings is not a number. |
| revenue_growth_qoq | Latest filed quarter's revenue against the previous quarter. |
| profit_growth_qoq | Latest filed quarter's net profit against the previous quarter. NULL when the base quarter was a loss. |
| profit_cagr_2y | Annualized growth in trailing-twelve-month net profit over two years — the shortest window where compounding says anything a single YoY does not. |
| profit_cagr_3y | Annualized growth in trailing-twelve-month net profit over three years. Wider coverage than the five-year window, and long enough to outlast one soft base year. |
| profit_cagr_4y | Annualized growth in trailing-twelve-month net profit over four years. |
| profit_cagr_5y | Annualized growth in trailing-twelve-month net profit over five years — the usual test of whether earnings compound or merely cycle. A company needs five unbroken years of filings on one basis to get a number; about half of covered names do. |
| profit_cagr_6y | Annualized growth in trailing-twelve-month net profit over six years. Sparse — few names have this much filed history yet. |
| profit_cagr_7y | Annualized growth in trailing-twelve-month net profit over seven years — the longest window the results store reaches, and the sparsest. |
| revenue_cagr_2y | Annualized growth in trailing-twelve-month revenue over two years. |
| revenue_cagr_3y | Annualized growth in trailing-twelve-month revenue over three years. |
| revenue_cagr_4y | Annualized growth in trailing-twelve-month revenue over four years. |
| revenue_cagr_5y | Annualized growth in trailing-twelve-month revenue over five years. Pair it with `profit_cagr_5y` to separate operating leverage from growth that only arrived through the top line. |
| revenue_cagr_6y | Annualized growth in trailing-twelve-month revenue over six years. |
| revenue_cagr_7y | Annualized growth in trailing-twelve-month revenue over seven years. |
| interest_cost_growth_yoy | Trailing-twelve-month finance costs against a year earlier. This is a proxy for the direction of borrowing, not a measure of it: quarterly filings carry a P&L and no balance sheet, so there is no debt figure here to read — flat interest beside growing profit is the shape of growth funded from earnings, and a jump is the shape of fresh debt. Read it against `pe` and a profit CAGR, exclude lenders (for a bank interest is the cost of goods, not leverage), and expect NULL when a quarter's finance-cost line is missing. |
| promoter_pct | Promoter shareholding from the latest pattern filed by that date. |
| public_pct | Public shareholding from the latest pattern filed by that date. |
| fii_pct | Foreign institutional holding from the latest pattern filed by that date. Sparse until the shareholding XBRL backfill completes. |
| dii_pct | Domestic institutional holding from the latest pattern filed by that date. Sparse until the shareholding XBRL backfill completes. |
| promoter_pledged_pct | Share of the promoter stake pledged as collateral. Zero is the healthy reading; a rising number is the classic distress tell. |
| promoter_change_qoq | Percentage-point change in promoter holding against the previous quarter's pattern. Positive means promoters bought. |
| dividend_ttm | Rupees per share paid over the trailing twelve months, by ex-date, on the same split-adjusted basis as close. Zero for a stock that paid nothing. The same as `dividend(1y)`; write `dividend(N)` for any span. |
| dividend_yield | Trailing-twelve-month dividends per share as a percentage of close. The same as `dividend_yield(1y)`. |
| dividend_growth_yoy | Trailing-year dividends per share against the trailing year before it. Null when the earlier year paid nothing. The same as `dividend_growth(1y)`. |
| dividend_streak_years | Consecutive trailing years, counting back from today, in which the stock paid a dividend. Capped at 10, and a lower bound where the record starts in 2012. |
price is accepted as an alias for close, so price between 50 and 5000 reads naturally.Sectors & categories#
Closed sets of string values, tested with `is`, `is not` and `in`.
where sector is "Information Technology"
and pe < 25 and pe > 0| Form | Meaning |
|---|---|
| sector is "Information Technology" | Exactly this NSE sector. |
| sector is not "Financial Services" | Everything but this sector. |
| industry in ("Banks", "Finance") | Any of the listed industries. |
| macro_sector is "Consumer Discretionary" | The broadest tier, above sector. |
The values are NSE’s official classification — 22 sectors, 12 macro sectors, 58 industries — and the editor autocompletes them after is, so nobody has to remember that the exact spelling is “Oil Gas & Consumable Fuels”. Matching is case-insensitive; the compiler canonicalises onto the official name.
Derivatives#
Per-stock daily aggregates from the NSE F&O bhavcopy, for the roughly 200 stocks with listed futures & options.
from fno where fut_oi_change_pct > 3 and change > 1Price and open interest rising together is a long buildup — fresh money agreeing with the move, rather than shorts giving up. Open interest is in contracts, summed across all expiries, because contracts are the unit NSE actually publishes.
| Field | Meaning |
|---|---|
| fut_oi | Total futures open interest, in contracts. |
| fut_oi_change_pct | Day-over-day change in futures OI. Rising OI with rising price is a long buildup. |
| fut_volume | Futures contracts traded. |
| fut_basis_pct | Front-month futures premium (+) or discount (−) to cash. |
| fut_rollover_pct | Share of OI already in later expiries. |
| pcr_oi | Put-call ratio by open interest. |
| pcr_vol | Put-call ratio by contracts traded. |
from fno where pcr_oi > 0.8 and close > sma(50)from fno makes that scope explicit — writing an F&O field without it earns a compiler warning rather than a silently thin result.Fundamentals#
Valuation, growth and shareholding, as the market knew them on the scan date.
where marketcap > 20000cr and pe < 30 and pe > 0Fundamentals are point-in-time. A result filed after the 15:30 close becomes visible from the next session, and a restatement counts only from its own filing date — so a scan on any past date sees exactly what a trader could have known that day, never what the filings later became. That is what keeps the hit-rate replay honest for fundamental screens.
| Field | Meaning |
|---|---|
| marketcap | Close × shares outstanding, in rupees — `marketcap > 5000cr`. |
| pe | Price to trailing-twelve-month earnings. NULL for loss-makers rather than a misleading number. |
| eps_ttm | Trailing EPS, split-adjusted to match the adjusted price series. |
| revenue_growth_yoy / profit_growth_yoy | Latest filed quarter against the same quarter last year. |
| revenue_growth_qoq / profit_growth_qoq | Latest filed quarter against the previous quarter. |
| profit_cagr_2y … profit_cagr_7y | Annualized profit growth over 2 to 7 years, trailing twelve months against the TTM N years earlier. NULL when the history is short or either end was a loss. |
| revenue_cagr_2y … revenue_cagr_7y | The same window on revenue — pair with the profit CAGR to see whether margins widened or only sales did. |
| interest_cost_growth_yoy | TTM finance costs against a year ago. A proxy for the direction of borrowing, not a debt figure — quarterly filings carry no balance sheet. |
| promoter_pct / public_pct / fii_pct / dii_pct | Shareholding from the latest pattern filed by the scan date. |
| promoter_pledged_pct | Share of the promoter stake pledged as collateral. |
| promoter_change_qoq | Percentage-point change in promoter holding vs the previous quarter. |
| dividend_ttm / dividend_yield | Rupees per share paid over the trailing year by ex-date, split-adjusted to match close, and that as a percentage of close. Non-payers read 0. |
| dividend_growth_yoy | Trailing-year dividends against the trailing year before. NULL when the base year paid nothing. |
| dividend_streak_years | Consecutive trailing years that paid, counting back from today. Capped at 10; the record starts in 2012. |
| dividend(Ny) / dividend_yield(Ny) | The same over any span of years — `dividend_yield(3y) > 9%`. One year reads the stored column; other spans are derived at scan time. |
| dividend_growth(Ny) / dividend_cagr(Ny) | Trailing-year dividends against the trailing year that ended N years ago: total change, and annualised. |
| dividend_years(Ny) | How many of the trailing N years paid — `dividend_years(5y) == 5` is a stock that never skipped. |
where promoter_change_qoq > 0.5 and close > sma(200)Dividends
where dividend_yield(1y) > 3% and dividend_years(5y) == 5 and dividend_cagr(5y) > 5
sort by dividend_yield(1y) descDividends are counted by ex-date, in rupees per share on the same split-adjusted basis as close, so a yield survives a split. The dividend functions take a number of years rather than bars — dividend(3y), dividend_yield(1y), dividend_cagr(5y) — and any span from one to ten years is derived at scan time from the ex-date record; the one-year forms read the stored columns. A stock that paid nothing reads 0, not NULL, so dividend_yield(1y) == 0 finds the non-payers. The record begins in 2012; a span reaching before it reads NULL rather than a partial total.
Indicators#
Called with a period, over close.
where rsi(14) < 40 and close > sma(200)| Indicator | Periods | Meaning |
|---|---|---|
| sma(n) | 5, 10, 20, 50, 100, 200, any | Unweighted mean close over the period. |
| ema(n) | 9, 21, 50, 200, any | Exponentially weighted mean close, seeded from the SMA. |
| wma(n) | 20, any | Linearly weighted mean — the newest bar counts most. |
| hma(n) | 21, any | Hull's low-lag moving average. |
| tema(n) | 20, any | Triple-smoothed EMA, with much of the lag removed. |
| rma(n) | 14, any | Wilder's smoothing, as used inside RSI and ATR. |
| vwma(n) | 20, any | Mean close weighted by each bar's volume. |
| rsi(n) | 14, any | Wilder's relative strength index. |
| atr(n) | 14, any | Wilder's average true range. |
| adx(n) | 14, any | Trend strength, direction-agnostic. |
| cci(n) | 20, any | Commodity Channel Index. |
| mfi(n) | 14, any | Volume-weighted RSI. |
| cmf(n) | 20, any | Accumulation/distribution normalised over a window. |
| roc(n) | 10, any | Percentage change over the period. |
| return(n) | 1, 5, 10, 21, 63, 126, 252, any | Percentage change in close over the period. The listed periods read the stored return columns; any other, `return(3y)` say, is derived at scan time. |
| highest_high(n) | 252, any | Highest high over the period. 252 reads the stored 52-week column; any other period is derived at scan time. |
| lowest_low(n) | 252, any | Lowest low over the period. 252 reads the stored 52-week column; any other period is derived at scan time. |
| pct_from_high(n) | 252, any | Distance below the highest high of the period, as a negative percentage. 252 reads the stored 52-week column. |
| pct_from_low(n) | 252, any | Distance above the lowest low of the period, as a percentage. 252 reads the stored 52-week column. |
| williams_r(n) | 14, any | Like the stochastic, scaled −100 to 0. |
| dividend(years) | 1y, any | Rupees per share paid over the trailing N years by ex-date, split-adjusted to match close. Zero for a stock that paid nothing. |
| dividend_yield(years) | 1y, any | Dividends per share over the trailing N years as a percentage of close. Over one year this is the ordinary dividend yield. |
| dividend_growth(years) | 1y, any | Total change in trailing-year dividends per share against the trailing year that ended N years ago. Null when that base year paid nothing. |
| dividend_cagr(years) | 1y, any | Annualised growth in trailing-year dividends per share over N years. Over one year it equals the total change. |
| dividend_years(years) | any | How many of the trailing N years paid a dividend. `dividend_years(5y) == 5` is a stock that paid every year. |
Every indicator accepts any period: the listed ones read a precomputed column and anything else is computed at scan time, so ema(37), adx(7) and bb(50, 2.5) all work. Multi-output indicators take their full parameter list — macd(8, 21, 5), supertrend(14, 2) — and written bare, macd() keeps meaning the stored 12/26/9.
The fixed-window price fields work the same way. Every one of them is the stored period of an indicator that takes any window: high_52w is highest_high(1y), return_1m is return(1mo), and highest_high(3y), return(2y) or pct_from_low(6mo) are derived at scan time from the same bars. A period can be written as a duration — lowest_low(5y) — or as a bar count. The dividend functions count years instead; see Fundamentals.
ema(high, 20): a moving average of another series is a window function, avg(high, 20), which is computed at scan time over any expression.Multi-output indicators
Indicators producing more than one line take empty parentheses and a sub-field. Omitting the sub-field picks the one marked (default) — bb() is bb().mid.
where macd().line crosses above macd().signal
and close > bb().upper| Indicator | Sub-fields | Meaning |
|---|---|---|
| macd() | .line (default) .signal .hist | 12/26 EMA difference, with a 9-period signal line. |
| bb() | .upper .mid (default) .lower .pctb .width | 20-period SMA with two-standard-deviation bands. |
| stoch() | .k (default) .d | Slow stochastic oscillator. |
| supertrend() | .value (default) .dir | ATR-banded trailing stop. |
| donchian() | .upper .mid (default) .lower | The rolling 20-bar high/low envelope. |
| keltner() | .upper (default) .lower | A 20-EMA with ATR-scaled bands. |
| ichimoku() | .conversion .base (default) .span_a .span_b | Conversion, base and cloud edges. |
| aroon() | .up .down .osc (default) | How recently the window's extremes occurred. |
Window functions#
Rolling aggregates over any period, computed at scan time rather than read from a column.
where volume > 2x avg(volume, 20)
and close > max(high, 20 bars)[-1]| Function | Returns |
|---|---|
| avg(x, n) | Mean of x over the last n bars. |
| max(x, n) | Highest value of x over the last n bars. |
| min(x, n) | Lowest value of x over the last n bars. |
| sum(x, n) | Total of x over the last n bars. |
| stdev(x, n) | Population standard deviation of x. |
| median(x, n) | Median of x over the last n bars. |
The window takes a bar count or a duration — avg(volume, 20), max(high, 20 bars) and min(low, 52w) are all valid. The first argument is any expression, so avg(high - low, 10) gives the mean daily range.
avg(close, 50) and sma(50) agree exactly rather than differing on new listings.Math functions#
One bar at a time, so they compose with everything.
| Function | Returns |
|---|---|
| abs(x) | Distance of x from zero. |
| ceil(x) | x rounded up to a whole number. |
| floor(x) | x rounded down to a whole number. |
| round(x) | x rounded to the nearest whole number. |
| square(x) | x multiplied by itself. |
| sqrt(x) | Square root of x. Nothing where x is negative. |
| log(x) | Natural log of x. Nothing where x is zero or less. |
| log10(x) | Base-10 log of x. Nothing where x is zero or less. |
| greatest(a, b, …) | The largest of its values. |
| least(a, b, …) | The smallest of its values. |
where abs(close - open) > 2% above avg(abs(close - open), 20)Unlike a window these read a single bar, so one can sit inside a window — avg(abs(close - open), 20) is the mean candle body over twenty sessions. greatest and least take two or more values; the rest take one.
Counting bars#
How many of the last N — not all of them.
| Function | Returns |
|---|---|
| count(cond, n) | How many of the last n bars matched. `count(close > open, 10) >= 7` is seven up days in ten. |
| countstreak(cond, n) | How many matched in a row, counting back from the scan bar. Stops at the first that did not. |
where count(volume > 2x avg(volume, 20), 10) >= 3
and countstreak(close > sma(20), 30) >= 10These take a condition where everything else takes a value. has been … for N bars demands every bar; this is how you ask for most of them.
Time travel#
Past bars are negative. There is no future.
| Written | Means |
|---|---|
| close | This bar — the scan date. |
| close[-1] | The previous bar. |
| prev close | The previous bar, spelled out. `prev` takes a field, not an expression. |
| close[-5] | Five bars ago. |
| avg(volume, 20)[-1] | The 20-bar average as of yesterday. |
| max(high, 20 bars)[-1] | The 20-bar high excluding today — a breakout level. |
where open > high[-1] and close > openclose[1] would name a bar that has not happened, and silently treating it as the past is how look-ahead bias gets into a scan.Operators#
The usual comparisons and arithmetic, plus chaining.
| Operators | Notes |
|---|---|
| > >= < <= = != | `=` is accepted as `==`. |
| + - * / | Division by zero yields no value rather than an error. |
| and or not | `and` binds tighter than `or`. Parenthesise when mixing. |
| ( ) | Grouping, for both conditions and arithmetic. |
Chained comparisons
A chain means what it looks like — each neighbouring pair must hold.
where close > ema(21) > ema(50) > ema(200)That is exactly equivalent to writing the three comparisons out and joining them with and.
Sugar#
Shorthand for the arithmetic other screeners force you to write by hand.
| Written | Equivalent to |
|---|---|
| volume > 2x avg(volume, 20) | volume > 2 * avg(volume, 20) |
| close > 5% above ema(50) | close > ema(50) * 1.05 |
| close < 3% below sma(200) | close < sma(200) * 0.97 |
| close within 2% of high_52w | close between high_52w * 0.98 and high_52w * 1.02 |
| price between 50 and 5000 | price >= 50 and price <= 5000 |
| price is not between 50 and 5000 | not (price >= 50 and price <= 5000) |
| close up 3% over 5 bars | (close - close[-5]) / close[-5] >= 0.03 |
| close down 2% over 5 bars | (close[-5] - close) / close[-5] >= 0.02 |
| close up 3% from prev close | (close - close[-1]) / close[-1] >= 0.03 |
These are not approximations. Each form is checked against its longhand equivalent in the test suite and must select exactly the same stocks.
normalized field print one of them: has stayed above comes back as has been above, crosses … within n bars as crossed … within n bars, and up 3% from prev close as up 3% over 1 bars. The query you wrote and the query it prints select the same stocks.Event operators#
Crossings, persistence, runs and extremes — the reason the language exists.
Every one of these compiles to a bounded window expression. In a screener without them you would hand-roll the same thing out of offset arithmetic, and get it subtly wrong.
Crossings
where sma(50) crosses above sma(200)A crossing is defined on two bars: strictly across now, and not across on the bar before. Add a recency window to catch one that happened a few sessions ago.
where rsi(14) crossed above 30 within 3 bars
and close > sma(200)Persistence
Whether something has held for a stretch, rather than being true on one lucky day.
where close has been above ema(21) for 10 bars and adx > 25Monotonic runs
where volume rising for 3 bars and close > close[-3]Window extremes
is highest in compares against a window that includes the current bar, so it is true exactly when this bar sets the extreme.
where close is highest in 52w and volume > 1.5x avg(volume, 20)| Form | Example | True when |
|---|---|---|
| x crosses above y | sma(50) crosses above sma(200) | x is above y now and was at or below on the previous bar. |
| x crosses below y | close crosses below ema(21) | x is below y now and was at or above on the previous bar. |
| x crossed above y within n bars | rsi(14) crossed above 30 within 3 bars | That crossing happened on any of the last n bars. |
| x crosses above y within n bars | sma(50) crosses above sma(200) within 5 bars | The same thing — `crosses` and `crossed` both take a recency window. |
| x has been above y for n bars | close has been above ema(21) for 10 bars | x was above y on every one of the last n bars. |
| x has stayed above y for n bars | rsi(14) has stayed below 30 for 3 bars | `stayed` reads better after a level and means exactly `has been`. |
| x rising for n bars | volume rising for 3 bars | x increased on each of the last n bars. |
| x falling for n bars | close falling for 5 bars | x decreased on each of the last n bars. |
| x is highest in n | close is highest in 52w | No bar in the window has a higher x. |
| x is lowest in n | low is lowest in 20 bars | No bar in the window has a lower x. |
| x is not highest in n | close is not highest in 20 bars | The negation of the above — this bar does not set the extreme. |
| pattern is p | pattern is bullish_engulfing | The pattern was detected on the scan bar. |
| pattern is p within n bars | pattern is hammer within 2 bars | It was detected on any of the last n bars. |
| pattern is not p | pattern is not doji | Excludes a pattern rather than requiring one. |
Candlestick patterns#
Detected at build time and queried as a first-class value.
where pattern is bullish_engulfing
and close within 3% of sma(50)Add within n bars for recency, or pattern is not … to exclude one. Definitions use proportional tolerances, so they behave the same on a ₹30 stock and a ₹30,000 one.
| Pattern | Shape |
|---|---|
| doji | Open and close nearly equal — indecision. |
| hammer | Long lower wick, small body at the top. |
| shooting_star | Long upper wick, small body at the bottom. |
| marubozu | Almost no wicks — one side controlled the session. |
| bullish_engulfing | An up bar whose body swallows the previous down bar. |
| bearish_engulfing | A down bar whose body swallows the previous up bar. |
| bullish_harami | A small up bar contained inside the previous down bar. |
| bearish_harami | A small down bar contained inside the previous up bar. |
| morning_star | Down bar, pause, then a strong up bar through the midpoint. |
| evening_star | Up bar, pause, then a strong down bar through the midpoint. |
| three_white_soldiers | Three consecutive strong up bars. |
| three_black_crows | Three consecutive strong down bars. |
| inside_bar | Range contained entirely within the previous bar's. |
| outside_bar | Range containing the whole previous bar's. |
Numbers & literals#
Indian and Western magnitudes, both native.
| Written | Value |
|---|---|
| 1k | 1,000 |
| 5L | 5,00,000 — five lakh |
| 2.5m | 25,00,000 |
| 10cr | 10,00,00,000 — ten crore |
| 1b | 100,00,00,000 |
| ₹500 / $50 | 500 / 50 — the symbol is read and discarded |
| 20 bars / 52w / 3mo | A duration, converted to trading sessions |
| d / w / mo / y | A day, week, month and year: 1, 5, 21 and 252 sessions. |
where turnover > 10cr and volume > 5LDurations convert at 252 sessions a year — 21 a month, 5 a week — so 52w, 12mo and 1y are all 252 bars, and max(high, 52w) is exactly the stored high_52w. No term may reach back more than 1260 bars: 5y is the widest window there is, and 6y is refused. As an argument to a dividend function, a duration is read in whole years instead.
Universes#
Which stocks a scan considers, chosen in the toolbar rather than in the query.
| Tier | Contains |
|---|---|
| Top 100 | Most traded 100 stocks by 20-day turnover |
| Top 250 | Most traded 250 stocks by 20-day turnover |
| Top 500 | Most traded 500 stocks by 20-day turnover |
| Top 1000 | Most traded 1000 stocks by 20-day turnover |
| All equities | Every actively traded NSE equity |
marketcap field for conditions, but the tiers stay turnover-ranked because turnover is dense from day one while fundamentals coverage is still filling in.from fno narrows a scan to the roughly 200 stocks with listed futures & options — the set the derivatives fields cover.
from fno where fut_oi_change_pct > 10 and close up 2% over 1 barsErrors#
Every mistake is reported with the reason and the fix, before the scan runs.
The parser and analyser run in the browser as you type — they are pure and need no database — so the editor underlines a problem immediately, and the identical code validates again on the server.
| Written | Reported as | Hint |
|---|---|---|
| clos > 100 | Unknown field `clos` | Did you mean `close`? |
| sma > 100 | `sma` needs a period | Try `sma(5)`, `sma(10)`, `sma(20)`. |
| rsi(5000) > 50 | `rsi` period must be between 2 and 1260 | e.g. `rsi(14)`. |
| dividend(3mo) > 0 | `dividend` takes whole years, not 3mo | Write whole years, e.g. `dividend(1y)`. |
| close[1] > 100 | `[1]` looks like a future bar | Past bars are negative — write `[-1]` for 1 bar ago. |
| ema(high, 20) > 0 | `ema` is only precomputed over close | Write `ema(20)`, or use a window function such as `avg(high, 20)`. |
| min(low, 6y) > 0 | A 1512-bar window is too long | Windows reach back at most 1260 bars. |
| rsi(14) crosses 30 | `crosses` must be followed by `above` or `below` | |
| on 15m where close > 100 | Timeframe `15m` is not available | This dataset is end-of-day only, so `on daily` is the only timeframe. |
| from BSE where close > 100 | Universe `BSE` is not available | Available universes: `NSE` (all equities) and `fno` (stocks with listed futures & options). |
| top 10% by marketcap | Percentile limits are not supported | Use a plain count, e.g. `top 25`. |
| pattern is wibble | Unknown pattern `wibble` | |
| macd().wibble > 0 | `macd()` has no sub-field `wibble` | Available: line, signal, hist. |
Recipes#
Complete scans worth stealing. Each one runs.
52-week high breakout
A close at the highest level in a year, on volume half again its own 20-day average. The Darvas entry, written out.
where close is highest in 52w and rel_volume > 1.5Within 3% of the 52-week high
Within 3% of the yearly high and above the 50-day EMA. Plenty of traders prefer this to the breakout candle itself.
where close within 3% of high_52w and close > ema(50)Volume breakout
Twice the 20-day average volume with a gain over 3%. Something changed today.
where volume > 2x avg(volume, 20)
and change > 3
and close > sma(20)Volume shockers
Twice the ten-day average volume and a move of more than 5%.
where volume > 2x avg(volume, 10) and change > 520-day channel breakout
Price above yesterday's 20-day channel top on 1.5 times normal volume. The Turtle entry.
where close > donchian_upper[-1]
and volume > 1.5x avg(volume, 20)Bollinger band breakout
A close above the upper Bollinger band with volume 1.5 times its average.
where close > bb().upper and rel_volume > 1.5Keltner channel breakout
Clearing the upper Keltner channel, which is built on ATR and shifts more slowly than a Bollinger band.
where close > keltner_upper and rel_volume > 1.5Crossing above pivot R1
The close crossing above the first pivot resistance on 1.5 times average volume. Floor traders have watched R1 for decades.
where close crosses above pivot_r1 and rel_volume > 1.5The scan library has 142 of these, each testable against a year of history.
Data caveats#
What the newer data can and cannot honestly answer.
| Caveat | The honest version |
|---|---|
| F&O coverage | Roughly 200 NSE stocks have listed derivatives. Everywhere else the F&O fields are NULL and never match — `from fno` makes the scope explicit. |
| Fundamentals coverage | The filings backfill is in progress; a stock without parsed results has NULL P/E and growth, and never matches those conditions. Coverage rises weekly. |
| Institutional holdings | Promoter and public shares of equity are filed every quarter and are the two the shareholding view is built on. FII, DII, employee-trust and pledge percentages come from a separate filing stage whose backfill has reached about 1% of instruments — they are NULL almost everywhere, including for the largest companies on the exchange, and never match. |
| Quarterly results | The per-quarter profit and loss starts in 2017 and the shareholding pattern in 2015. NSE's quarterly filings carry a P&L only, so there is no balance sheet, cash flow, debt or return-on-capital figure anywhere in this dataset. |
| Sector history | Classification is today's snapshot — NSE publishes no history. A historical scan or hit-rate replay applies the current sector retroactively. |
| OI units | Open interest is in contracts, summed across expiries, both before and after a lot-size revision — the unit NSE actually publishes. |
Fundamentals are point-in-time: a scan sees the numbers as the market knew them on the scan date. A result filed after the 15:30 close becomes visible from the next session, and a restatement counts only from its own filing date — which is what keeps the hit-rate replay honest for fundamental screens.
Limits#
What a query is compiled against. Crossing one is an error with a span, never a silent truncation.
| Limit | What it bounds |
|---|---|
| 1260 bars of lookback | The furthest back any one term may reach — five years of sessions: a window length, a bar offset, an event window, or a window plus its offset. `5y` fits; `6y` does not. Dividend spans are counted in years against a separate table and are capped at 10. |
| 8,000 characters of source | The longest query the compiler accepts, in the browser and over the API alike. |
| 5,000 rows | The hard cap on `top`. A larger number is clamped rather than refused. |
| `top` defaults to 300 | And `sort by <expr>` with no direction sorts descending. |
| Indicator periods 2–1260 | Per-parameter bounds come from the catalog — `bb`'s standard deviation is 0.5–5, `supertrend`'s multiplier 0.5–10, and the recursive kernels (`stoch`, `keltner`, `aroon`) stop at 400. |
The lookback ceiling is the one real queries reach, and it is what makes a scan’s cost knowable before it runs: the analyser computes the deepest bar any term needs, so the engine reads that many and no more. min(low, 3y) is 756 bars and compiles; min(low, 6y) is 1512 and does not. A scan that reaches past a year of history reads proportionally more rows, and only that scan pays for it.
Not supported#
Parts of the language spec this dataset cannot honour, and why.
| Feature | Why not |
|---|---|
| on 15m / 1h / weekly | The dataset is end-of-day only. `on daily` is the only timeframe. |
| and on daily: … | Inline multi-timeframe scopes need a second timeframe to scope to, and there is only one. |
| from BSE / US / NIFTY500 | NSE only, and there is no index-constituent list to filter by. `from fno` is the one list-like universe. |
| from NSE except … / watchlist "…" | Set subtraction and user lists are not implemented; `except` is reserved but unused. |
| vwap | Needs intraday data, which end-of-day bars cannot provide. |
| ema(high, 20) | Indicators read close only. For another series use a window function — `avg(high, 20)`. |
| close 3 bars ago | The English offset was dropped for one spelling. Write `close[-3]`. |
| … as golden / … since golden | Event sequencing — one condition holding after another — is not implemented. `as` and `since` are reserved but unused. |
| scan "name" { … } | A query is the scan. Naming and saving one is an account action, not syntax. |
| per-strike option screening | F&O fields are per-stock daily aggregates. Strike-level OI is a chain view, not a screener column. |
| pattern is … forming | Geometric pattern detection with a confidence score is not built. Only completed candlestick patterns are available. |
| backtest { } / alert { } | Not implemented. The hit-rate panel on each scan is the nearest thing. |
| top 10% by … | Percentile limits are rejected; use a plain count. |
API & MCP#
Everything on this page works without the browser.
A scan is one POST with the Sift source in the body, and the MCP endpoint gives a coding agent this whole reference as a tool — so Claude Code or Codex can write and run scans against your account unaided. Keys are free on every plan.