Docs / Rule language
Rule language
Rule library
86 worked rules, grouped by idea. Each has the idea in plain English, the rule, why it is written that way, and its real belief on SPY, NVDA, TSLA, XLE for the session of 2026-10-30. Copy one into "ask", or press Try it.
Each chip is one real answer: XLE 0.9 means the rule clearly holds on XLE, SPY 0.05 that it clearly does not, and values near 0.50 that the reading sits close to the line. Run the same rule over your watchlist to turn it into a scan.
Price relations
Closed above yesterday's high
C > H[-1]
Offsets count back from the latest bar, so H[-1] is yesterday's high. Comparing the close (not the high) asks that the strength held into the end of the day.
Opened below yesterday's low
O < L[-1]
An opening break of the prior range. Use O for the open; the rest of the bar does not matter to this rule.
Closed higher than yesterday
C > C[-1]
The simplest relation. The belief grows with the size of the move measured against this symbol's normal daily change.
Closed above its open
C > O
An intraday gain, regardless of where yesterday closed.
Higher high and higher low
H > H[-1] and L > L[-1]
Both halves must hold; and takes the weaker of the two, so a barely higher low keeps the answer near 0.50.
Lower high and lower low
H < H[-1] and L < L[-1]
The mirror image: a down-trending bar.
Two higher closes in a row
C > C[-1] and C[-1] > C[-2]
Chain offsets to walk back bar by bar.
Closed in the top quarter of the day's range
(C - L) / (H - L) > 0.75
Where the close sits in the bar, 0 at the low and 1 at the high. A bar with no range divides by zero and answers 0.50.
Closed in the bottom quarter of the day's range
(C - L) / (H - L) < 0.25
Selling into the close.
Gaps
Opened more than 1% above yesterday's close
O > C[-1] * 1.01
Multiply instead of subtracting to express the gap as a percentage.
Opened more than 1% below yesterday's close
O < C[-1] * 0.99
The down-side version.
An unfilled gap up
L > H[-1]
The whole bar stayed above yesterday's high. Same as gap_up().
Gapped up but closed under yesterday's close
O > C[-1] and C < C[-1]
A failed gap: two conditions on the same bar.
Opened under yesterday's low, closed up on the day
O < L[-1] and C > C[-1]
Sellers had the open; buyers had the close.
Gap bigger than half an ATR
abs(O - C[-1]) > 0.5 * ref(atr(14), -1)
ref(atr(14), -1) is yesterday's ATR, so today's own wide bar does not inflate the yardstick it is measured against.
Ranges
An inside day
H < H[-1] and L > L[-1]
Strictly inside. inside_bar() allows equal highs or lows.
Narrowest range of the last seven sessions (NR7)
(H - L) < lowest(H - L, 6)
lowest() looks at the six bars before today, so today plus six is the seven-bar window.
Range more than twice the 20-day average range
(H - L) > 2 * avg(H - L, 20)
Window functions take any expression, not just a price.
True range 1.5× yesterday's ATR
tr() > 1.5 * ref(atr(14), -1)
tr() counts an overnight gap as part of the range, unlike H − L.
In the lower third of its 20-day range
C < lowest(L, 20) + (highest(H, 20) - lowest(L, 20)) / 3
Build the range from highest and lowest of the previous 20 bars, then place the close in it.
A tight 10-day base (under 5% of price)
(max(H, 10) - min(L, 10)) / C < 0.05
max and min include today, which is what a base measured up to now should do.
Trend
Above the 50-day average
C > sma(C, 50)
The everyday trend filter.
More than 10% above the 200-day
C > sma(C, 200) * 1.10
Scale one side to ask "by how much", not just "above".
50-day above the 200-day
sma(C, 50) > sma(C, 200)
Compares two averages; the belief is scaled by how much their gap normally moves.
Stacked averages: close > 20 EMA > 50 > 200
C > ema(C, 20) and ema(C, 20) > sma(C, 50) and sma(C, 50) > sma(C, 200)
Four links in one chain; the weakest link sets the answer.
The 50-day average is rising
sma(C, 50) > ref(sma(C, 50), -10)
ref() is how you look back at an indicator; sma(C, 50)[-10] is not valid.
The 200-day average is rising
sma(C, 200) > ref(sma(C, 200), -20)
A slow average needs a longer look-back to show a slope.
Trending (ADX above 25)
adx(14) > 25
ADX has no direction; pair it with diplus/diminus for one.
Trending up (ADX over 20 and +DI over −DI)
adx(14) > 20 and diplus(14) > diminus(14)
Strength and direction as two separate conditions.
Up on at least six of the last ten days
sum(C > C[-1], 10) >= 6
A comparison is 1 or 0 inside a window, so sum() counts the days it held.
Momentum
RSI above 50
rsi(C, 14) > 50
The midline: more up pressure than down.
RSI above 70
rsi(C, 14) > 70
The classic overbought line. The measured scale means 71 answers near 0.55 and 85 near 0.9.
MACD above its signal line
macd(C) > macdsignal(C)
Defaults 12, 26, 9; pass periods to change them: macd(C, 8, 21, 5).
MACD histogram rising
macdhist(C) > ref(macdhist(C), -1)
Momentum of momentum, read one bar back with ref().
Up more than 5% in 20 sessions
roc(C, 20) > 5
roc() is in percent, so 5 means 5%.
Higher than three months ago
C > C[-63]
About 63 sessions in three months.
Stochastic %K under 20
stochk(14) < 20
Near the bottom of the 14-day range.
CCI above 100
cci(20) > 100
A strong push above the typical price's average.
Williams %R above −20
willr(14) > -20
Negative numbers work as written.
Volatility
A typical day moves more than 3%
atr_pct(14) > 3
Percent of price, so one line works across cheap and expensive stocks.
ATR up 20% in two weeks
atr(14) > 1.2 * ref(atr(14), -10)
Compare an indicator with its own past to see a regime change.
Bollinger bands narrower than 6% of price
(bbupper(C, 20, 2) - bblower(C, 20, 2)) / bbmid(C, 20) < 0.06
Band width as a fraction of the middle band: a squeeze measure.
Annualized 20-day volatility above 40%
std(log(C / C[-1]), 20) * sqrt(252) > 0.4
Daily log returns, their deviation, scaled by the square root of 252 sessions.
Swings less than half their 50-day size
std(C, 10) < 0.5 * std(C, 50)
Short window against long window of the same statistic.
A two-sigma down day
zscore(C / C[-1] - 1, 20) < -2
zscore() of the daily return, against its own last 20 sessions.
Volume
Volume above its 20-day average
V > avg(V, 20)
The average includes today; see the next rule for the stricter form.
Volume more than twice the previous 20-day average
V > 2 * ref(avg(V, 20), -1)
ref(…, -1) keeps today's spike out of the average it is compared with.
Up on heavy volume
C > C[-1] and V > 1.5 * ref(avg(V, 20), -1)
Price and participation together.
Down on heavy volume
C < C[-1] and V > 1.5 * ref(avg(V, 20), -1)
Distribution: selling with size.
On-balance volume above its 20-day average
obv() > sma(obv(), 20)
Window functions accept another function as input.
Volume under 60% of normal
V < 0.6 * avg(V, 20)
Quiet sessions, often inside a base.
Volume in the top tenth of 50 sessions
percentrank(V, 50) > 90
A rank needs no threshold in shares, so it works on any symbol.
Breakouts
20-day closing breakout
C > highest(C, 20)
highest() excludes today: the form that can actually be true.
Took out the 20-day high intraday
H > highest(H, 20)
Highs against highs: an intraday break that may not have held.
New 52-week high
H > highest(H, 252)
About 252 sessions in a year.
Within 2% of the 52-week high
C >= 0.98 * max(H, 252)
max() includes today, so a stock at its high still qualifies.
55-day closing breakout
C > highest(C, 55)
A slower channel breakout.
Closed under the 20-day low
C < lowest(L, 20)
Close against the lows of the previous 20 bars.
20-day breakout on 1.5× volume
C > highest(C, 20) and V > 1.5 * ref(avg(V, 20), -1)
Ask the parts as separate questions too: they show which half held it back.
Set its 20-day high in the last four sessions
highestbars(H, 20) <= 3
highestbars() is 0 on the bar that set the high.
Crossed above the 50-day today
crossup(C, sma(C, 50))
An event on this bar only: 0.95 when it happened, 0.05 otherwise.
Closed above the upper Bollinger band
C > bbupper(C, 20, 2)
A volatility-scaled breakout.
Mean reversion
Two-day RSI under 10
rsi(C, 2) < 10
A very short RSI reacts to two or three down days.
Closed under the lower Bollinger band
C < bblower(C, 20, 2)
Two deviations under the 20-day mean.
Two deviations under the 20-day mean
zscore(C, 20) < -2
The same idea as the band, as one number.
Pullback in an uptrend: above the 200-day, RSI under 40
C > sma(C, 200) and rsi(C, 14) < 40
A trend filter plus an oversold trigger.
Stretched: more than 2 ATR above the 20-day
C > sma(C, 20) + 2 * atr(14)
Distance in ATR units is comparable across symbols.
Three lower closes while above the 200-day
C < C[-1] and C[-1] < C[-2] and C[-2] < C[-3] and C > sma(C, 200)
Short-term weakness inside long-term strength.
Crossed back above the lower band
crossup(C, bblower(C, 20, 2))
The re-entry signal after a band break.
Patterns
Hammer at a 20-day low
hammer() and L <= 1.01 * lowest(L, 20)
A pattern is a rule, so it combines with and like any other.
Bullish engulfing above the 50-day
engulfing_bull() and C > sma(C, 50)
Context matters more than the candle.
Morning star or bullish engulfing
morning_star() or engulfing_bull()
or takes the stronger of the two.
Shooting star at a 20-day high
shooting_star() and H > highest(H, 20)
A reversal shape where it matters.
Doji after a 10% month
doji() and roc(C, 20) > 10
Indecision after a run.
Multi-condition setups
Uptrend, under the 20-day, RSI still above 40
C > sma(C, 200) and C < sma(C, 20) and rsi(C, 14) > 40
A pullback that has not broken momentum.
Squeeze, then a breakout
std(C, 20) < 0.7 * std(C, 100) and C > highest(C, 20)
Quiet first, then a break: both must hold today.
Momentum with participation
roc(C, 20) > 5 and V > avg(V, 50) and C > sma(C, 50)
Three independent pieces of evidence.
Undercut the 20-day low and closed back above it
L < lowest(L, 20) and C > lowest(L, 20)
The same level on both sides of one bar: a spring.
Oversold, with the line set by the trend
(C > sma(C, 200) and rsi(C, 14) < 40) or (C < sma(C, 200) and rsi(C, 14) < 25)
Two regimes joined by or, each with its own threshold. iif() can do the same in one comparison.
Chandelier exit: 3 ATR under the 22-day high
C < highest(H, 22) - 3 * atr(22)
A volatility stop written as a rule: true means the stop is hit.
Scanning ideas
More than 20% off the 52-week high
C < 0.8 * max(H, 252)
Run it on a watchlist to find names in a drawdown.
Within 3% of the 200-day
abs(C / sma(C, 200) - 1) < 0.03
abs() makes it two-sided: just above or just below.
No 20-day breakout for 30 sessions
barssince(C > highest(C, 20)) > 30
barssince() turns an event into an age.
This week's low is above last week's
min(L, 5) > ref(min(L, 5), -5)
ref() shifts a whole window back: the five sessions before these five.
This week's range is 1.5× last week's
(max(H, 5) - min(L, 5)) > 1.5 * ref(max(H, 5) - min(L, 5), -5)
Any expression can go inside ref(), here a whole five-day range.
Up more than 10% in a quarter, near the high
roc(C, 63) > 10 and C >= 0.95 * max(H, 63)
Strength that has not faded. Rules read one symbol's own history; to compare against an index, rank the answers in your code.
Strategy pairs (an entry and an exit) are in Examples → Strategies; multi-question requests in Examples → Advanced.
Information, not investment advice. Beliefs and probabilities on this page are real responses for the session shown and change with every session.