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
myVar and myvar are different variables.
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.
1D, 2D, 3D, 4D, 5D, 1W, 2W, 3W, 4W, 1M, 2M, 3M, 4M, 6M, 12M
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