Conditionals with cond()
Most scanners force you to think in AND/OR logic: this condition AND that condition. But trading decisions often have an "it depends" quality. You want to apply different rules based on context.
The cond() function brings if-then-else logic to your expressions:
cond(condition, true_value, false_value)
Context-dependent filtering
Say you only care about a stock's price when it's trading on above-average volume. Low-volume moves are noise - you want to ignore them entirely.
x = cond(volume > sma(volume, 20), close, 0);
x > 50;
When volume is below average, x becomes zero and the stock gets filtered out. The close price only "counts" when volume confirms it.
Building scoring systems
You can nest multiple cond() calls to build simple scoring systems - assigning points based on various conditions, then filtering on the total score. Binary classification is the simplest version:
trend_strength = cond(close > ema(close, 50), 1, -1);
trend_strength > 0;
Rolling sums with sum()
Moving averages smooth data. But sometimes you want the raw accumulation over a window - how much total volume traded, how much price moved, how many up days occurred.
sum(field, period)
Comparing accumulation periods
Volume spikes matter, but so does sustained accumulation. This scan finds stocks where recent 5-day volume exceeds what you'd expect from the 20-day pattern:
sum(volume, 5) > sum(volume, 20) / 4;
You're comparing actual recent accumulation against the proportional slice of a longer window. A simple ratio that standard volume indicators don't capture directly.
Cumulative sums with cumsum()
While sum() gives you a rolling window, cumsum() accumulates from the start of available data. This is the building block behind On-Balance Volume - arguably the oldest accumulation indicator in technical analysis.
OBV works by signing each day's volume (positive on up days, negative on down days) and keeping a running total:
obv = cumsum(cond(close > close[1], volume, 0 - volume));
The idea: volume precedes price. If OBV is rising while price is flat, accumulation is happening under the surface. We exposed cumsum() so you can build OBV variants with different logic for how volume gets signed.
RSI building blocks: gain() and loss()
RSI is calculated by separately averaging gains and losses, then computing their ratio. Most platforms give you the final RSI number. We're exposing the components:
gain(field) -- positive changes only (zeros for down days)
loss(field) -- negative changes as positive values (zeros for up days)
Why would you want these? Standard RSI uses the same lookback period for both gains and losses. But gains and losses aren't symmetric in markets - momentum tends to behave differently in each direction. With the primitives exposed, you can apply different smoothing, weighting, or filtering to each side.
If you're happy with standard RSI, you don't need these. They're here for when you're not.
Putting it together
These functions are most interesting in combination. Here's a scan for stocks with volume-confirmed momentum - only counting gains that happen on above-average volume days:
vol_confirmed_gain = cond(volume > sma(volume, 20), gain(close), 0);
confirmed_momentum = sum(vol_confirmed_gain, 10);
confirmed_momentum > 5;
The logic: gains on low volume might be noise. Gains on high volume suggest real buying pressure. By combining cond(), gain(), and sum(), you can express "show me stocks that have gained at least $5 on high-volume days over the past two weeks."
Here's another: a filtered OBV that ignores low-volume days entirely.
signed_vol = cond(close > close[1], volume, 0 - volume);
significant_vol = cond(volume > sma(volume, 20), signed_vol, 0);
filtered_obv = cumsum(significant_vol);
Standard OBV treats a 100-share day the same as a 10-million-share day in terms of direction. This variant only accumulates when volume is above average - focusing on the days that actually matter. You won't find this indicator in your charting package, but now you can scan for it.
All five functions are available now. Check the StonQL reference documentation for the full syntax details.