StonQL Reference

Securities Temporal ObservatioN Query Language

What is StonQL?

StonQL (pronounced "Stonk-QL") is a domain-specific language for filtering stocks based on price, volume, and technical indicators.

A basic scan:

close > 50;

This returns all stocks with a closing price above $50.

A more practical example:

close > sma(close, 50);

This returns stocks trading above their 50-day simple moving average.

Statements

A statement is a single condition that must be true for a stock to appear in your results. Every statement ends with a semicolon.

volume > 1M;

This statement filters for stocks with volume greater than 1 million shares.

Comments

Add notes to your scans using comments. Comments are ignored when the scan runs.

// Single-line comment
volume > 1M; // Comment at end of line

/* Multi-line comment
   spanning multiple lines */
close > open;

Combining Statements

When you write multiple statements, a stock must satisfy all of them to appear in results. Think of it as "AND" logic.

close > 10;           // Price above $10
close < 50;           // Price below $50
volume > 500K;        // Volume above 500,000

This scan returns only stocks where all three conditions are true: price between $10-$50 with decent volume.

Need "OR" logic? Use the or operator within a single statement:

rsi(close, 14) < 30 or rsi(close, 14) > 70;

This finds stocks that are either oversold (RSI below 30) or overbought (RSI above 70).

Fields

Fields are the data points you use in your scans. They represent price, volume, and other stock information.

close

Closing price for the period

close > 100;
open

Opening price for the period

close > open;
high

Highest price during the period

high > high[1];
low

Lowest price during the period

low > high[1];
volume

Number of shares traded during the period

volume > 1M;
typical_price (alias: hlc3)

(high + low + close) / 3

close > typical_price;
ohlc4

(open + high + low + close) / 4

sma(ohlc4, 20);
first_trade_date

The date the stock first started trading

months_since(first_trade_date) < 6;
is_etf

True if the instrument is an ETF

is_etf == true; close > 100;
is_stock

True if the instrument is a regular stock (not an ETF)

is_stock == true; rsi(close, 14) > 70;

Variables

Use variables to name your calculations. This makes complex scans easier to read and lets you reuse values.

avg_vol = sma(volume, 20);
volume > avg_vol * 2;  // Volume is 2x the 20-day average

Variables are especially helpful when you need the same calculation multiple times:

ma50 = sma(close, 50);
ma200 = sma(close, 200);
close > ma50;      // Price above 50-day MA
ma50 > ma200;      // 50-day MA above 200-day MA (bullish alignment)

Number Formats

StonQL supports several number formats for convenience:

Format Example Equals
K (thousands) 500K 500,000
M (millions) 1.5M 1,500,000
B (billions) 2B 2,000,000,000

Operators

Comparison Operators

Operator Description Example
> Greater than close > 50; // stocks above $50
>= Greater than or equal to rsi(close, 14) >= 70; // RSI at or above overbought level
< Less than close < sma(close, 200); // price below 200-day MA
<= Less than or equal to rsi(close, 14) <= 30; // RSI at or below oversold level
== Equal to (exact match) volume == 0; // no trading activity
!= Not equal to close != open; // price moved during the period
~= Approximately equal (0.1% tolerance) close ~= high; // closed near the high

Logical Operators

Operator Description Example
and Both conditions must be true close > 10 and volume > 1M; // above $10 with high volume
or Either condition can be true rsi(close, 14) < 30 or rsi(close, 14) > 70; // oversold or overbought
not Inverts a condition not close < 10; // equivalent to close >= 10

Arithmetic Operators

Operator Description Example
+ Addition typical = (high + low + close) / 3; // typical price
- Subtraction range = high - low; // trading range for the period
* Multiplication volume > sma(volume, 20) * 2; // 2x average volume
/ Division change = (close - open) / open; // price change ratio
\ Floor division (rounds down to integer) whole_dollars = close \ 1; // drops decimal portion
% Modulo (remainder) cents = (close * 100) % 100; // cents portion of price

Historical Reference

Access values from previous periods. The number in brackets indicates how many periods back.

Operator Description Example
[n] Value from n periods ago close > close[1]; // today's close above yesterday's

Multi-Timeframe Analysis

Use mtf() blocks to combine conditions across multiple timeframes in a single scan. Filter stocks that meet criteria on daily, weekly, and monthly charts simultaneously.

Filter Mode

When a mtf() block contains boolean conditions, stocks must pass all conditions in that timeframe to appear in results.

// Daily scan with weekly and monthly filters
close > sma(close, 20);        // Daily: above 20-day MA

mtf("1W") {
    close > open;              // Weekly: green candle
    rsi(close, 14) > 50;       // Weekly: bullish momentum
}

mtf("1M") {
    close > sma(close, 3);     // Monthly: uptrend
}

Stocks must pass daily conditions AND weekly conditions AND monthly conditions.

Value Extraction Mode

Assign a mtf() block to a variable to extract values from another timeframe for use in comparisons.

// Extract previous week's high for breakout detection
prev_weekly_high = mtf("1W") { high[1]; };

// Daily breakout above last week's high
close > prev_weekly_high;
volume > sma(volume, 20) * 1.5;

The [1] gets the previous week's value, avoiding same-bar comparisons.

Scope Isolation

mtf() blocks are completely isolated. Variables defined outside cannot be used inside, and vice versa.

// CORRECT: Self-contained mtf() block
mtf("1W") {
    avg = sma(close, 20);      // 'avg' only exists inside this block
    close > avg;
}

// CORRECT: Value extraction creates outer variable
weekly_rsi = mtf("1W") { rsi(close, 14); };
rsi(close, 14) > weekly_rsi;   // Compare daily RSI to weekly RSI

// WRONG: Cannot reference outer variables in mtf() blocks
threshold = 50;
mtf("1W") { rsi(close, 14) > threshold; };  // ERROR!
// VALID: Check if today made the monthly high (equality)
monthly_high = mtf("1M") { high; };
high == monthly_high;          // Valid: detecting monthly high bar

// WRONG: Inequality comparison to containing period
monthly_high = mtf("1M") { high; };
high > monthly_high;           // NEVER true - daily high can't exceed monthly high it's part of!

// CORRECT: Use [1] for inequality comparisons
prev_monthly_high = mtf("1M") { high[1]; };
close > prev_monthly_high;     // Meaningful: breaking above last month's high

The rule: Equality checks (==, ~=) against current period values are valid for detecting extremes. Inequality checks (>, <) are faulty because the daily value is part of the aggregate. Use [1] for inequality comparisons.

Combining Indicators Across Timeframes

// Multi-timeframe momentum alignment
prev_weekly_rsi = mtf("1W") { rsi(close, 14)[1]; };
prev_monthly_rsi = mtf("1M") { rsi(close, 14)[1]; };

// Daily RSI stronger than both higher timeframes
rsi(close, 14) > prev_weekly_rsi;
rsi(close, 14) > prev_monthly_rsi;
rsi(close, 14) > 50;           // And above neutral

Functions

Click on any function to view detailed documentation with parameters, formulas, and examples.

Quick Index (A-Z)

abs() add() all_time_high() all_time_low() and() approx() aroon_down() aroon_osc() aroon_up() atr() bb_lower() bb_upper() bb_width() cci() change() cond() count() cross() crossover() crossunder() cumsum() days_since() dev() deviation() div() ema() eq() falling() floordiv() gain() gt() gte() highest() highest_bars_ago() hma() kc_lower() kc_middle() kc_upper() kcw() loss() lowest() lowest_bars_ago() lt() lte() macd_histogram() macd_line() macd_signal() max() mean() median() mfi() min() mod() mom() months_since() mul() ne() obv() offset() or() pct_change() percentrank() pivothigh() pivotlow() pow() range() rising() roc() rsi() sma() sqrt() stdev() stoch_d() stoch_k() streak() sub() sum() swma() true_range() universe_percentile() variance() vwma() weeks_since() wilderma() williams_r() wma() years_since()

All Functions

Function Description
abs(a) Absolute value operator
add(a, b) Addition operator
all_time_high(source) All-Time High (Maximum value from inception)
all_time_low(source) All-Time Low (Minimum value from inception)
and(left, right) Logical AND
approx(value, target, tolerance) Approximate equality with absolute tolerance
aroon_down(period) Aroon Down - measures time since lowest low
aroon_osc(period) Aroon Oscillator - difference between Aroon Up and Aroon Down
aroon_up(period) Aroon Up - measures time since highest high
atr(periods, matype="wilder") Average True Range
bb_lower(source, period, num_stdev, matype="sma") Bollinger Bands Lower Band (MA - num_stdev * stdev)
bb_upper(source, period, num_stdev, matype="sma") Bollinger Bands Upper Band (MA + num_stdev * stdev)
bb_width(source, period, num_stdev, matype="sma") Bollinger Bands Width (upper_band - lower_band)
cci(period) Commodity Channel Index
change(source, length) Change (Difference from historical value)
cond(condition, if_true, if_false) Conditional/ternary operator (if-then-else)
count(condition, period) Count total occurrences of condition in lookback window
cross(source1, source2) Cross detection (either crossover or crossunder)
crossover(source1, source2) Crossover detection (source1 crosses above source2)
crossunder(source1, source2) Crossunder detection (source1 crosses below source2)
cumsum(source) Running total from start of data
days_since(date) Calendar days since given date
dev(source, length) Mean Absolute Deviation
deviation(source, length) Mean Absolute Deviation
div(a, b) Division operator
ema(source, periods) Exponential Moving Average
eq(left, right) Equality comparison (==)
falling(source, length) Falling trend detection (consecutively decreasing values)
floordiv(a, b) Floor division operator (//)
gain(source) Positive price change (gain)
gt(left, right) Greater than comparison (>)
gte(left, right) Greater than or equal comparison (>=)
highest(source, period) Highest value of field over period
highest_bars_ago(source, period) Bars ago when highest value occurred
hma(source, period) Hull Moving Average
kc_lower(source, length, mult, matype="ema", atr_length=..., atr_matype="wilder", bandstyle="atr") Keltner Channels Lower Band
kc_middle(source, length, matype="ema") Keltner Channels Middle Line (MA)
kc_upper(source, length, mult, matype="ema", atr_length=..., atr_matype="wilder", bandstyle="atr") Keltner Channels Upper Band
kcw(source, length, mult, matype="ema", atr_length=..., atr_matype="wilder", bandstyle="atr") Keltner Channels Width (percentage)
loss(source) Negative price change (loss)
lowest(source, period) Lowest value over period
lowest_bars_ago(source, period) Bars ago when lowest value occurred
lt(left, right) Less than comparison (<)
lte(left, right) Less than or equal comparison (<=)
macd_histogram(source, fast_period, slow_period, signal_period, fast_matype="ema", slow_matype="ema", matype="ema") MACD Histogram (MACD line - Signal line)
macd_line(source, fast_period, slow_period, signal_period, fast_matype="ema", slow_matype="ema") MACD Line (fast EMA - slow EMA)
macd_signal(source, fast_period, slow_period, signal_period, fast_matype="ema", slow_matype="ema", matype="ema") MACD Signal Line (EMA of MACD line)
max(a, b) Maximum operator
mean(source, period) Statistical Mean (same as SMA)
median(source, period) Statistical Median
mfi(period) Money Flow Index (Volume-weighted momentum oscillator)
min(a, b) Minimum operator
mod(a, b) Modulo operator
mom(source, length) Change (Difference from historical value)
months_since(date) Months since given date (truncated)
mul(a, b) Multiplication operator
ne(left, right) Not equal comparison (!=)
obv(source) On-Balance Volume (Cumulative volume indicator)
offset(source, amount) Offset (LAG) function for time-shifting values
or(left, right) Logical OR
pct_change(current, base) Percent Change between two values
percentrank(source, length) Percent Rank
pivothigh(source, leftbars, rightbars) Detects pivot high (local peak) points in a data series
pivotlow(source, leftbars, rightbars) Detects pivot low (local trough) points in a data series
pow(a, b) Power operator
range(max_source, min_source, period) Difference between max_field and min_field over period
rising(source, length) Rising trend detection (consecutively increasing values)
roc(source, periods) Rate of Change
rsi(source, periods, matype="wilder") Relative Strength Index
sma(source, periods) Simple Moving Average
sqrt(a) Square root operator
stdev(source, period) Standard Deviation
stoch_d(k_period, d_period, matype="sma") Stochastic %D (Slow Stochastic - MA of %K)
stoch_k(period) Stochastic %K (Fast Stochastic)
streak(condition, period) Count consecutive bars where condition is true
sub(a, b) Subtraction operator
sum(source, periods) Total over rolling N-period window
swma(source) Symmetrically Weighted Moving Average (fixed 4-period)
true_range() True Range (maximum range including gaps)
universe_percentile(field) Universe Percentile (cross-stock ranking)
variance(source, period) Statistical Variance
vwma(source, periods) Volume Weighted Moving Average
weeks_since(date) Weeks since given date (truncated)
wilderma(source, periods) Wilder Moving Average (EMA with α = 1/N)
williams_r(period) Williams %R momentum oscillator
wma(source, periods) Weighted Moving Average
years_since(date) Years since given date (truncated)

Ready to put this knowledge to use?

Try these functions in the scanner to build powerful stock scans

Open Scanner