Open standard · version 1.0.0 · 2026-10-31
Ora Rules v1
A small, exact language for questions about a price series: C > sma(C, 50), rsi(C, 14) < 30, engulfing_bull(), days_to_earnings <= 10. A rule answers with a belief between 0.05 and 0.95: how clearly it holds on the latest bar, measured against how much that quantity normally moves. This document specifies the language completely, so anyone can implement it.
Status: stable. Editor: KlickAnalytics (Ora). Reference implementation: the Ora engine behind oramodel.com.
License: this specification is published under Creative Commons Attribution 4.0 (CC BY 4.0). You may copy, implement, translate and extend it, commercially or not, as long as you credit it: “Ora Rules v1 by KlickAnalytics (oramodel.com), CC BY 4.0”. The license covers this text and the JSON at /spec/v1.json; it grants no rights to the Ora service, its models or its data.
The key words MUST, MUST NOT, SHOULD and MAY are used as in RFC 2119.
1. Data model
A rule is evaluated against one series of bars, oldest first. Each bar has five numbers: open O, high H, low L, close C and volume V, and MAY have a timestamp. The rule is about the latest bar (the evaluation bar) unless it looks back with an offset or a window. Daily bars SHOULD be split- and dividend-adjusted. An implementation MAY also expose named facts: single numbers known at the evaluation bar (section 8).
Every expression denotes a series aligned with the bars: C is the series of closes, sma(C, 50) the series of 50-bar averages. A value that cannot be computed at a bar (too little history, division by zero, a bar that does not exist) is not known at that bar.
2. Lexical rules
| Rule | Specification |
|---|---|
| Case | Keywords, function names and price letters are case-insensitive: AND, Sma(C, 50) and c are valid. |
| Numbers | Plain decimals: 70, 1.5, .97. No exponents, no thousands separators. A leading minus is the unary operator: willr(14) > -20. |
| Literals | true is 1 and false is 0. |
| Equality | = and == are the same operator. ! is the same as not. |
| Whitespace | Ignored between tokens: C>H[-1] and C > H[-1] are one rule. |
| Offsets | Only on the five price letters: C[-1] is the previous bar. A positive offset looks forward and is not known at the latest bar. For any other expression use ref(x, -n). |
| Names | A name that is not a price letter, keyword or function is a fact. An unknown name MUST be rejected (section 10). |
3. Grammar
rule := or
or := and ( "or" and )*
and := not ( "and" not )*
not := ( "not" | "!" ) not | compare
compare := sum ( (">" | ">=" | "<" | "<=" | "==" | "=" | "!=") sum )*
sum := product ( ("+" | "-") product )*
product := power ( ("*" | "/" | "%") power )*
power := unary ( "^" unary )*
unary := ("+" | "-") unary | primary
primary := "(" rule ")" | number | "true" | "false"
| price [ "[" offset "]" ] | function "(" [ args ] ")" | fact
args := rule ( "," rule )*
price := "O" | "H" | "L" | "C" | "V"
offset := [ "-" ] digits
number := digits [ "." digits ] | "." digits
fact := name (a name that is not a price, keyword or function)
name := letter ( letter | digit | "_" )*
Function arguments are full expressions, so windows take any series: avg(H - L, 20). Optional trailing arguments take the defaults shown in each signature (zlema(x, n=20)).
4. Precedence
| Level (tightest first) | Operators | Example |
|---|---|---|
| 1 | ( ), function calls, offsets | sma(C, 50), H[-1] |
| 2 | ^ | (C / C[-1]) ^ 2 |
| 3 | * / % | 1.5 * avg(V, 20) |
| 4 | + - | sma(C, 20) + 2 * atr(14) |
| 5 | > >= < <= == = != | C > O |
| 6 | not, ! | not C > sma(C, 50) |
| 7 | and | a and b |
| 8 | or | a or b |
and binds tighter than or: a or b and c is a or (b and c). Implementations SHOULD warn when the two are mixed without parentheses.
5. Evaluation
- Arithmetic is on series.
+ - * / % ^apply bar by bar. Division by zero is not known at that bar. - A comparison inside arithmetic or a function argument is 1 or 0 at each bar, so
sum(C > C[-1], 10)counts up bars. A comparison with a side that is not known is not known. - Only the outermost comparisons become beliefs: those joined by
and,orandnotat the top of the rule (section 6). - Windows such as
sma(x, n),max(x, n)andsum(x, n)cover the last n bars including the current one.highest(x, n)andlowest(x, n)cover the n bars before it, soC > highest(C, 20)is a breakout. - Events such as
crossup(a, b)are true only on the bar where they happen. - Patterns (
hammer(),engulfing_bull(), …) are macros: each is defined as a rule in the core language (section 9 lists the expansions) and MUST evaluate exactly as its expansion.
6. Beliefs
A rule does not answer true or false. It answers how far past the line the latest bar is, in units of how much that gap normally moves for this series, as a belief in [0.05, 0.95].
- Margin. For
a > banda >= b, the margin is a − b at the latest bar; for<and<=it is b − a. - Scale. The sample standard deviation (n − 1) of (a − b) over the last 60 bars, skipping bars where it is not known. A constant on one side does not change it, so
rsi(C, 14) > 30and> 70share one yardstick. With fewer than 8 known values, or a deviation of zero, the scale is the fact's declared scale (section 8) if one side is a fact, otherwise 10% of |a| at the latest bar. - Belief. With r = min(|margin| ÷ scale, 3): b = 0.5 + 0.45 × (1 − e−1.6 r) when the margin is positive (the rule holds), 1 − b when it is negative, and the tie value when it is exactly zero.
| Margin in scales | Belief if true | Belief if false |
|---|---|---|
| 0 | 0.55 (>=, <=) | 0.45 (>, <) |
| 0.25 | 0.6484 | 0.3516 |
| 0.5 | 0.7478 | 0.2522 |
| 1 | 0.8591 | 0.1409 |
| 2 | 0.9317 | 0.0683 |
| 3 or more | 0.9463 | 0.0537 |
| Case | Belief |
|---|---|
| Exactly on the line (a tie) | 0.55 for >= and <=; 0.45 for > and < |
| A side not known at the latest bar, or a fact not known | 0.50 |
A bare expression with no comparison (an event such as crossup(), between(), a literal) | 0.95 if non-zero at the latest bar, else 0.05; 0.50 if not known |
a == b | 1.05 − b(|a − b|), where b(·) is the belief formula above: 0.55 when equal, down to 0.1037 at three scales or more |
a != b | b(|a − b|): 0.50 when equal, up to 0.9463 |
a and b | the lower of the two beliefs |
a or b | the higher of the two |
not a | 1 − the belief |
A belief is not a probability. It describes how clearly a condition that is already decided holds. Implementations MUST NOT present rule beliefs as forecasts. Beliefs are rounded to 4 decimal places, so a comparison ranges from 0.0537 to 0.9463 and only a bare expression reaches 0.05 or 0.95.
7. Values and formulas
An implementation SHOULD offer a value evaluation that returns the number an expression has at the latest bar (sma(C, 50) → 612.4), and MAY accept formulas: named inputs written as "=expression" whose values are computed before a request is answered ("stop": "=lowest(L, 20)"). Inputs given as plain numbers MAY be read by name in rules, like facts (C > stop).
8. Facts
A fact is a named number known at the evaluation bar, point in time: it uses only what was known at that bar's close. A fact that is not known makes its comparison 0.50; "none on record" (a stock that never split) is the number 99999, so "split in the last year" is a clean no. Each fact declares its scale. Ora Rules v1 names 43 facts in three groups; an implementation that offers a group MUST use these names and meanings.
Earnings
| Fact | Units | Scale | Meaning |
|---|---|---|---|
days_to_earnings | days | 3 | Calendar days to the next scheduled earnings report (0 = reports today after the close or at an unknown time). Not known if no date is on the calendar. |
days_since_earnings | days | 3 | Calendar days since the last report whose result is known (0 = reported before today's open). |
next_report_bmo | flag | 0.5 | 1 if the next report is scheduled before the open, 0 if after the close; not known if the time is not given. |
next_report_amc | flag | 0.5 | 1 if the next report is scheduled after the close, 0 if before the open; not known if the time is not given. |
last_eps | $/share | measured | EPS of the last known report. |
last_eps_est | $/share | measured | Consensus EPS estimate for that report (as stored; estimates may have been revised). |
eps_surprise_pct | % | 5 | (EPS − estimate) / |estimate| × 100 for the last report. Not known if there is no estimate or it is 0. |
beat_eps_last | flag | 0.5 | 1 if the last EPS was above the estimate, else 0 (in line is not a beat). |
miss_eps_last | flag | 0.5 | 1 if the last EPS was below the estimate, else 0. |
last_revenue | $ | measured | Revenue of the last known report. |
revenue_surprise_pct | % | 2 | (revenue − estimate) / |estimate| × 100 for the last report. |
beat_revenue_last | flag | 0.5 | 1 if the last revenue was above the estimate, else 0. |
miss_revenue_last | flag | 0.5 | 1 if the last revenue was below the estimate, else 0. |
beat_streak | quarters | 1 | Consecutive EPS beats ending with the last report (0 if it was not a beat). Not known when a missing estimate interrupts the run. |
beats_last4 | count | 1 | EPS beats among the last 4 known reports (not known unless all 4 have an estimate). |
misses_last4 | count | 1 | EPS misses among the last 4 known reports (not known unless all 4 have an estimate). |
eps_year_ago | $/share | measured | EPS of the report about a year before the last one (365 ± 45 days). |
eps_growth_yoy | % | 10 | (last EPS − EPS a year ago) / |EPS a year ago| × 100. |
revenue_year_ago | $ | measured | Revenue of the report about a year before the last one. |
revenue_growth_yoy | % | 5 | (last revenue − revenue a year ago) / |revenue a year ago| × 100. |
last_earnings_move | % | 2 | Close-to-close return of the reaction session of the last report. Not known if the report time is blank. |
last_earnings_gap | % | 2 | Open of the reaction session against the close before it. Not known if the report time is blank. |
pre_earnings_close | price | measured | Close of the session before the last report's reaction session (same price basis as C). |
Dividends
| Fact | Units | Scale | Meaning |
|---|---|---|---|
pays_dividend | flag | 0.5 | 1 if an ex-date fell in the last 400 days or one is declared, else 0. |
div_ttm | $/share | measured | Sum of split-adjusted dividends with ex-dates in the last 365 days. |
div_yield_ttm | % | 0.5 | div_ttm / today's close × 100 (split-adjusted, not dividend-adjusted close). |
div_count_ttm | count | 1 | Number of ex-dates in the last 365 days. |
last_div_amount | $/share | measured | Split-adjusted amount of the last ex-date on or before today. |
prev_div_amount | $/share | measured | Split-adjusted amount of the ex-date before that. |
div_change_pct | % | 5 | (last / previous payment − 1) × 100. |
raised_last | flag | 0.5 | 1 if the last payment was more than 0.5% above the previous one, else 0. |
cut_last | flag | 0.5 | 1 if the last payment was more than 0.5% below the previous one, else 0. |
days_since_ex_div | days | 3 | Calendar days since the last ex-date (99999 if it never paid). |
days_to_ex_div | days | 3 | Calendar days to the next DECLARED ex-date (99999 if none is declared). |
next_div_amount | $/share | measured | Split-adjusted amount of the next declared dividend. |
years_of_growth | years | 1 | Consecutive calendar years, ending last year, whose total dividend was more than 0.5% above the year before. |
Splits
| Fact | Units | Scale | Meaning |
|---|---|---|---|
days_since_split | days | 30 | Calendar days since the last share split (99999 if none on record). |
last_split_ratio | ratio | 0.5 | Shares after / before for the last split: 10 = 10-for-1, 0.05 = 1-for-20 reverse. |
reverse_split_last | flag | 0.5 | 1 if the last split was a reverse split, 0 if forward; not known if it never split. |
days_since_reverse_split | days | 30 | Calendar days since the last reverse split (99999 if none on record). |
splits_5y | count | 1 | Number of splits in the last 5 years (1,825 days). |
days_to_split | days | 3 | Calendar days to the next announced split (99999 if none is announced). |
next_split_ratio | ratio | 0.5 | Ratio of the next announced split. |
9. Functions
322 functions in 18 groups: 63 core (required), 234 library (optional, each defined exactly) and 25 patterns (macros over the core). Arguments named x take any series; n is a whole number of bars. This list is generated from the reference engine's own catalog.
Windows
| Signature | Level | Definition | Example |
|---|---|---|---|
sum(x, n) | Core | Rolling sum of the last n bars. Summing a comparison counts the bars where it held. | sum(C > C[-1], 10) >= 6 |
avg(x, n) | Core | Another name for sma(). Same result. | V > avg(V, 20) |
mean(x, n) | Core | A third name for sma(). | C > mean(C, 100) |
sma(x, n) | Core | Simple moving average of the last n bars, including the current one. | C > sma(C, 50) |
min(x, n) | Core | Lowest value of the last n bars, INCLUDING the current one. | C < 1.02 * min(L, 20) |
max(x, n) | Core | Highest value of the last n bars, INCLUDING the current one. So C > max(C, n) can never be true. | C >= 0.98 * max(H, 20) |
highest(x, n) | Core | Highest value of the n bars BEFORE the current one. This is the breakout form. | C > highest(C, 20) |
lowest(x, n) | Core | Lowest value of the n bars before the current one. | C < lowest(C, 20) |
std(x, n) | Core | Standard deviation over the window (population form, like most charting tools). | std(C, 10) < 0.5 * std(C, 50) |
zscore(x, n) | Core | How many standard deviations the current value sits from its window mean. A flat window scores 0. | zscore(C, 20) < -2 |
ema(x, n) | Core | Exponential moving average, smoothing 2 / (n + 1). | C > ema(C, 21) |
roc(x, n) | Core | Percent change over n bars: roc(C, 20) = 5 means up 5% in 20 sessions. | roc(C, 20) > 5 |
wma(x, n) | Core | Linearly weighted moving average: recent bars count more. | wma(C, 10) > wma(C, 30) |
rma(x, n) | Core | Wilder's smoothing, 1 / n: what RSI and ATR are built from. | C > rma(C, 14) |
linreg(x, n) | Core | The least-squares line through the window, read at the current bar: a smoothed level, not the slope. | linreg(C, 20) > ref(linreg(C, 20), -5) |
median(x, n) | Core | Middle value of the window. Less pulled by one wild day than an average. | V > 2 * median(V, 20) |
percentrank(x, n) | Core | Where the current value ranks among the n − 1 bars before it, 0 to 100. | percentrank(V, 50) > 90 |
cum(x) | Core | Running total from the first bar loaded. | cum(C > C[-1]) > cum(C < C[-1]) |
highestbars(x, n) | Core | How many bars ago the window's high was set, 0 to n − 1 (0 = this bar). The window includes the current bar. | highestbars(H, 20) <= 3 |
lowestbars(x, n) | Core | How many bars ago the window's low was set (0 = this bar). | lowestbars(L, 20) >= 15 |
rvol(n) | Core | Relative volume: this bar's volume over the average of the n bars before it. | rvol(20) > 2 |
Indicators
| Signature | Level | Definition | Example |
|---|---|---|---|
rsi(x, n) | Core | Relative strength index, 0 to 100 (Wilder). rsi(C, 14) is the usual one. | rsi(C, 14) > 50 |
atr(n) | Core | Average true range over n bars, in price. | H - L > 2 * atr(14) |
atr_pct(n) | Core | ATR as a percent of the close: 3 means 3%. Comparable across symbols. | atr_pct(14) > 3 |
macd(x [, fast, slow, signal]) | Core | MACD line: ema(fast) − ema(slow). Defaults 12, 26, 9. | macd(C) > 0 |
macdsignal(x [, fast, slow, signal]) | Core | The MACD signal line: an EMA of the MACD line. | macd(C) > macdsignal(C) |
macdhist(x [, fast, slow, signal]) | Core | MACD histogram: line minus signal. | macdhist(C) > ref(macdhist(C), -1) |
bbupper(x, n [, up, dn]) | Core | Upper Bollinger band: sma + up × std. Default 2. | C > bbupper(C, 20, 2) |
bbmid(x, n) | Core | Middle band: the simple average. | C > bbmid(C, 20) |
bblower(x, n [, up, dn]) | Core | Lower Bollinger band: sma − dn × std (dn defaults to up). | C < bblower(C, 20, 2) |
adx(n) | Core | Trend strength, 0 to 100, with no direction. Above 25 is usually called trending. | adx(14) > 25 |
diplus(n) | Core | +DI: the upward directional movement ADX is built from. | diplus(14) > diminus(14) |
diminus(n) | Core | −DI: the downward directional movement. | diminus(14) > 25 |
stochk(n [, d, slow]) | Core | Stochastic %K, 0 to 100: where the close sits in the n-bar range, smoothed over slow (default 3). | stochk(14) < 20 |
stochd(n [, d, slow]) | Core | Stochastic %D: the d-bar average of %K (default 3). | stochk(14) > stochd(14) |
cci(n) | Core | Commodity channel index: typical price against its average, in units of mean deviation. | cci(20) > 100 |
willr(n) | Core | Williams %R, −100 to 0: 0 is the top of the n-bar range. | willr(14) > -20 |
obv() | Core | On-balance volume: adds the day's volume on an up close, subtracts it on a down close. | obv() > sma(obv(), 20) |
Maths
| Signature | Level | Definition | Example |
|---|---|---|---|
abs(x) | Core | Absolute value. | abs(C / C[-1] - 1) > 0.02 |
sqrt(x) | Core | Square root (negative input is not a number). | std(log(C / C[-1]), 20) * sqrt(252) > 0.4 |
log(x) | Core | Natural logarithm (zero or negative input is not a number). | log(C / C[-20]) > 0.05 |
sign(x) | Core | −1, 0 or 1. | sum(sign(C - C[-1]), 10) > 2 |
iif(cond, a, b) | Core | a where the condition holds, b otherwise. Use it to switch a threshold by regime. | rsi(C, 14) < iif(C > sma(C, 200), 40, 30) |
ref(x, -n) | Core | Any series n bars back: the offset for things that are not a price letter. | sma(C, 50) > ref(sma(C, 50), -10) |
between(x, a, b) | Core | 1 when x is inside [a, b] (inclusive, either order), else 0. | between(rsi(C, 14), 40, 60) |
least(a, b) | Core | The smaller of two series, bar by bar. | least(O, C) - L > 2 * abs(C - O) |
greatest(a, b) | Core | The larger of two series, bar by bar. | H - greatest(O, C) > 2 * abs(C - O) |
nz(x, default=0) | Library | x, with 'not known yet' (NaN) replaced by the default. | nz(ref(C, -300), C) > 0 |
na(x) | Library | 1 where x is not known yet (still warming up), 0 otherwise. | na(sma(C, 2000)) |
clamp(x, lo, hi) | Library | x limited to the range lo..hi. | clamp(rsi(C, 14), 30, 70) == 70 |
round(x, digits=0) | Library | x rounded to `digits` decimals (half away from zero). | round(rsi(C, 14)) >= 50 |
floor(x) | Library | The largest whole number at or below x. | floor(C) == floor(C[-1]) |
ceil(x) | Library | The smallest whole number at or above x. | ceil(C) > ceil(C[-1]) |
exp(x) | Library | e to the power x. | exp(log(C / C[-20])) > 1.1 |
pow(x, y) | Library | x to the power y (the same as x ^ y). | pow(C / C[-252], 0.5) > 1.1 |
log10(x) | Library | Base-10 logarithm. NaN for x <= 0. | log10(V) > 7 |
Prices
| Signature | Level | Definition | Example |
|---|---|---|---|
hl2() | Core | The middle of the bar: (H + L) / 2. | C > hl2() |
hlc3() | Core | The typical price: (H + L + C) / 3. | hlc3() > sma(hlc3(), 20) |
ohlc4() | Core | The average of the four prices: (O + H + L + C) / 4. | ohlc4() > sma(C, 10) |
tr() | Core | True range: the bar's high minus low, extended to include a gap from the previous close. | tr() > 1.5 * ref(atr(14), -1) |
gap() | Core | Percent from the previous close to this bar's open. | gap() > 2 |
Recursion
| Signature | Level | Definition | Example |
|---|---|---|---|
rec(seed, step) | Core | A series built from its own previous value: inside step, self is the previous bar's result (seed before the first bar). Only arithmetic, comparisons and abs/sqrt/log/sign/iif/least/greatest/between may touch self. | C > rec(C, self + 0.1 * (C - self)) |
Signals
| Signature | Level | Definition | Example |
|---|---|---|---|
crossup(a, b) | Core | 1 on the bar where a crosses above b (it was at or below b on the bar before), 0 otherwise. As a whole rule it answers 0.95 or 0.05. | crossup(C, sma(C, 50)) |
crossdown(a, b) | Core | 1 on the bar where a crosses below b. | crossdown(sma(C, 20), sma(C, 50)) |
barssince(cond) | Core | Bars since a condition was last true (0 = true on this bar). Not known until it has been true once. | barssince(C > highest(C, 20)) > 30 |
Intraday
| Signature | Level | Definition | Example |
|---|---|---|---|
vwap() | Core | Volume-weighted average price of the session so far (typical price), reset every session. Requires intraday bars: send state.bars with an interval such as "5m". | C > vwap() |
opening_range_high([m]) | Core | Highest price of the session's first m minutes (default 30). Requires intraday bars: send state.bars with an interval such as "5m". | C > opening_range_high(30) |
opening_range_low([m]) | Core | Lowest price of the session's first m minutes. Requires intraday bars: send state.bars with an interval such as "5m". | C < opening_range_low(30) |
minutes_since_open() | Core | Minutes since the session's first bar. Requires intraday bars: send state.bars with an interval such as "5m". | minutes_since_open() >= 30 |
time_of_day() | Core | The bar's clock time as HHMM (1030 = 10:30), in the timestamps' own time zone. Requires intraday bars: send state.bars with an interval such as "5m". | time_of_day() >= 1000 |
Price structure
| Signature | Level | Definition | Example |
|---|---|---|---|
swinghigh(n [, k]) | Core | The k-th most recent confirmed swing high (default k = 1): a high strictly above the n bars on each side. It is only known n bars later, so it never looks ahead. | C > swinghigh(5) |
swinglow(n [, k]) | Core | The k-th most recent confirmed swing low (default k = 1). | swinglow(3) > swinglow(3, 2) |
pp() | Library | Classic floor pivot from the previous bar: (H + L + C) / 3. | C > pp() |
r1() | Library | Classic resistance 1: 2 * pivot - previous low. | H > r1() |
r2() | Library | Classic resistance 2: pivot + previous range. | H > r2() |
r3() | Library | Classic resistance 3: previous high + 2 * (pivot - previous low). | H > r3() |
s1() | Library | Classic support 1: 2 * pivot - previous high. | L < s1() |
s2() | Library | Classic support 2: pivot - previous range. | L < s2() |
s3() | Library | Classic support 3: previous low - 2 * (previous high - pivot). | L < s3() |
fib_r1() | Library | Fibonacci pivot resistance 1: pivot + 0.382 * previous range. | H > fib_r1() |
fib_r2() | Library | Fibonacci resistance 2: pivot + 0.618 * range. | H > fib_r2() |
fib_r3() | Library | Fibonacci resistance 3: pivot + 1.0 * range. | H > fib_r3() |
fib_s1() | Library | Fibonacci support 1: pivot - 0.382 * range. | L < fib_s1() |
fib_s2() | Library | Fibonacci support 2: pivot - 0.618 * range. | L < fib_s2() |
fib_s3() | Library | Fibonacci support 3: pivot - 1.0 * range. | L < fib_s3() |
cam_r1() | Library | Camarilla R1: previous close + 1.1/12 * range. | C > cam_r1() |
cam_r2() | Library | Camarilla R2: previous close + 1.1/6 * range. | C > cam_r2() |
cam_r3() | Library | Camarilla R3: previous close + 1.1/4 * range. A fade level. | H > cam_r3() and C < cam_r3() |
cam_r4() | Library | Camarilla R4: previous close + 1.1/2 * range. A breakout level. | C > cam_r4() |
cam_s1() | Library | Camarilla S1: previous close - 1.1/12 * range. | C < cam_s1() |
cam_s2() | Library | Camarilla S2: previous close - 1.1/6 * range. | C < cam_s2() |
cam_s3() | Library | Camarilla S3: previous close - 1.1/4 * range. A fade level. | L < cam_s3() and C > cam_s3() |
cam_s4() | Library | Camarilla S4: previous close - 1.1/2 * range. A breakdown level. | C < cam_s4() |
gap_filled() | Library | 1 if today opened with a gap and traded back to the previous close (gap up and L <= C[-1], or gap down and H >= C[-1]). | gap_filled() |
insidebars(n=5) | Library | How many of the last n bars were inside bars (range inside the bar before). | insidebars(5) >= 2 |
outsidebars(n=5) | Library | How many of the last n bars were outside bars. | outsidebars(10) >= 3 |
nr(n=7) | Library | 1 if today's range (H - L) is the narrowest of the last n bars (NR7 = nr(7)). Ties count. | nr(7) |
wr(n=7) | Library | 1 if today's range is the widest of the last n bars (WR7 = wr(7)). Ties count. | wr(7) |
hhv(x, n=20) | Library | Highest value of the last n bars, including today (MetaStock's name for max()). | C >= 0.97 * hhv(H, 52) |
llv(x, n=20) | Library | Lowest value of the last n bars, including today (MetaStock's name for min()). | C <= 1.03 * llv(L, 52) |
body() | Library | Size of the candle body: |C - O|. | body() > 2 * sma(body(), 20) |
upperwick() | Library | Upper shadow: H - max(O, C). | upperwick() > 2 * body() |
lowerwick() | Library | Lower shadow: min(O, C) - L. | lowerwick() > 2 * body() |
barrange() | Library | The bar's range: H - L. | barrange() > 1.5 * atr(14) |
bodypct() | Library | Body as a percent of the bar's range (0 on a bar with no range). | bodypct() > 80 |
clv() | Library | Close location value, -1 to 1: ((C - L) - (H - C)) / (H - L). 1 = closed on the high. | clv() > 0.8 |
closepos() | Library | Where the close sits in the bar's range, 0-100 (100 = on the high). | closepos() > 90 |
pctfromhigh(n=252) | Library | Percent the close sits below the highest high of the last n bars (0 = at the high). | pctfromhigh(252) < 3 |
pctfromlow(n=252) | Library | Percent the close sits above the lowest low of the last n bars. | pctfromlow(252) < 5 |
rangepos(n=20) | Library | Where the close sits in the n-bar high-low range, 0-100. | rangepos(20) > 80 |
rangewidth(n=20) | Library | Width of the n-bar high-low range in percent: (max(H, n) / min(L, n) - 1) * 100. | rangewidth(20) < 10 |
Moving averages
| Signature | Level | Definition | Example |
|---|---|---|---|
dema(x, n=20) | Library | Double exponential moving average: 2*ema - ema(ema). Less lag than an ema of the same length. | C > dema(C, 20) |
tema(x, n=20) | Library | Triple exponential moving average: 3*e1 - 3*e2 + e3 over three nested emas. | C > tema(C, 20) |
trima(x, n=20) | Library | Triangular moving average: an sma of an sma, weighting the middle of the window most. | C > trima(C, 20) |
kama(x, n=10, fast=2, slow=30) | Library | Kaufman adaptive moving average: fast in a clean trend, nearly flat in chop (efficiency-ratio driven). | C > kama(C, 10) |
hma(x, n=20) | Library | Hull moving average: wma(2*wma(x, n/2) - wma(x, n), sqrt(n)). Very little lag. | hma(C, 20) > ref(hma(C, 20), -1) |
vwma(x, n=20) | Library | Volume-weighted moving average: sum(x*V, n) / sum(V, n). | C > vwma(C, 20) |
alma(x, n=9, offset=0.85, sigma=6) | Library | Arnaud Legoux moving average: a Gaussian-weighted window centred near the newest bar (offset 0.85). | C > alma(C, 20) |
zlema(x, n=20) | Library | Zero-lag ema: an ema of x + (x - x[-(n-1)/2]), which cancels most of the lag. | C > zlema(C, 20) |
t3(x, n=5, vfactor=0.7) | Library | Tillson T3: six chained emas blended by the volume factor. Smooth and responsive. | C > t3(C, 10) |
smma(x, n=14) | Library | Smoothed moving average (Wilder's, alpha 1/n). The same as rma(). | C > smma(C, 20) |
swma(x) | Library | Symmetric 4-bar weighted average: (x[-3] + 2*x[-2] + 2*x[-1] + x) / 6. | C > swma(C) |
vidya(x, n=20, cmo=9) | Library | Variable index dynamic average: an ema whose speed scales with |CMO(cmo)|/100, so it hugs price in strong moves. | C > vidya(C, 20) |
mcginley(x, n=14) | Library | McGinley dynamic: an average that speeds up when price runs away from it. | C > mcginley(C, 14) |
midpoint(x, n=14) | Library | Middle of the window's range: (max(x, n) + min(x, n)) / 2. | C > midpoint(C, 20) |
midprice(n=14) | Library | Middle of the n-bar high-low range: (max(H, n) + min(L, n)) / 2. | C > midprice(20) |
Oscillators
| Signature | Level | Definition | Example |
|---|---|---|---|
stochrsi(x, n=14, m=14, k=3) | Library | Stochastic RSI %K, 0-100: where rsi(x, n) sits in its own m-bar range, smoothed over k bars. | stochrsi(C) < 20 |
stochrsid(x, n=14, m=14, k=3, d=3) | Library | Stochastic RSI %D: the d-bar average of stochrsi(). | crossup(stochrsi(C), stochrsid(C)) |
mfi(n=14) | Library | Money flow index, 0-100: a volume-weighted RSI of the typical price. | mfi(14) < 20 |
cmo(x, n=14) | Library | Chande momentum oscillator, -100 to 100: (sum of gains - sum of losses) / their total, over plain n-bar sums (Chande's definition). | cmo(C, 14) > 50 |
tsi(x, long=25, short=13) | Library | True strength index, -100 to 100: double-smoothed momentum over double-smoothed absolute momentum. | tsi(C) > 0 |
uo(fast=7, mid=14, slow=28) | Library | Ultimate oscillator, 0-100: buying pressure over three windows, weighted 4:2:1. | uo() < 30 |
dpo(x, n=20) | Library | Detrended price oscillator: x minus the sma(x, n) of n/2+1 bars ago (the non-centred form, so it never looks ahead). | dpo(C, 20) > 0 |
ppo(x, fast=12, slow=26) | Library | Percentage price oscillator: (ema fast - ema slow) / ema slow * 100. A MACD you can compare across symbols. | ppo(C) > 0 |
pposignal(x, fast=12, slow=26, signal=9) | Library | Signal line of ppo(): its ema over `signal` bars. | crossup(ppo(C), pposignal(C)) |
ppohist(x, fast=12, slow=26, signal=9) | Library | ppo() minus its signal line. | ppohist(C) > 0 |
apo(x, fast=12, slow=26) | Library | Absolute price oscillator: ema(x, fast) - ema(x, slow), in price. | apo(C) > 0 |
kst(x) | Library | Know Sure Thing (Pring): weighted sum of four smoothed rates of change (10/15/20/30). | kst(C) > kstsignal(C) |
kstsignal(x) | Library | Signal line of kst(): its 9-bar sma. | crossup(kst(C), kstsignal(C)) |
trix(x, n=15) | Library | TRIX: 1-bar percent change of a triple-smoothed ema. | trix(C, 15) > 0 |
ao() | Library | Awesome oscillator (Bill Williams): sma(hl2, 5) - sma(hl2, 34). | ao() > 0 |
ac() | Library | Accelerator oscillator: ao() minus its 5-bar sma. | ac() > 0 |
mom(x, n=10) | Library | Momentum: x - x n bars ago, in the units of x. | mom(C, 10) > 0 |
rocr(x, n=10) | Library | Rate of change as a ratio: x / x n bars ago (1.05 = up 5%). | rocr(C, 20) > 1.05 |
bop() | Library | Balance of power, -1 to 1: (C - O) / (H - L). 0 on a bar with no range. | sma(bop(), 14) > 0 |
aroonup(n=25) | Library | Aroon up, 0-100: 100 when the n-bar high is today, falling as it ages. | aroonup(25) > 70 |
aroondown(n=25) | Library | Aroon down, 0-100: 100 when the n-bar low is today. | aroondown(25) > 70 |
aroonosc(n=25) | Library | Aroon oscillator, -100 to 100: aroonup - aroondown. | aroonosc(25) > 50 |
fisher(n=9) | Library | Ehlers Fisher transform of the bar's position in its n-bar range: sharp turning points. | fisher(9) > 1.5 |
rvgi(n=10) | Library | Relative vigor index: smoothed (C - O) over smoothed (H - L), summed over n bars. | rvgi(10) > rvgisignal(10) |
rvgisignal(n=10) | Library | Signal line of rvgi(): its symmetric 4-bar average. | crossup(rvgi(10), rvgisignal(10)) |
coppock(x, wma=10, long=14, short=11) | Library | Coppock curve: wma of the sum of two rates of change. A turn up from below zero is the classic long-term buy signal. | coppock(C) > ref(coppock(C), -1) |
crsi(x, rsi=3, streak=2, rank=100) | Library | Connors RSI, 0-100: the average of rsi(x, 3), the RSI of the up/down streak, and the percent rank of today's 1-day change among the previous 100. | crsi(C) < 10 |
smi(n=10, s1=3, s2=3) | Library | Stochastic momentum index, -100 to 100: the close against the middle of its n-bar range, double-smoothed. | smi(10) < -40 |
cfo(x, n=14) | Library | Chande forecast oscillator: percent gap between x and its n-bar linear-regression value. | cfo(C, 14) > 0 |
qstick(n=10) | Library | Qstick: the n-bar average of (C - O). Positive = mostly green bodies. | qstick(10) > 0 |
bullpower(n=13) | Library | Elder bull power: H - ema(C, n). | bullpower(13) > 0 |
bearpower(n=13) | Library | Elder bear power: L - ema(C, n). | bearpower(13) < 0 |
imi(n=14) | Library | Intraday momentum index, 0-100: an RSI built from bar bodies (C - O) instead of closes. | imi(14) > 70 |
adxr(n=14) | Library | ADX rating: (adx + adx n-1 bars ago) / 2. A slower trend-strength reading. | adxr(14) > 25 |
dx(n=14) | Library | Directional movement index: 100 * |DI+ - DI-| / (DI+ + DI-), the unsmoothed ADX. | dx(14) > 30 |
plusdm(n=14) | Library | Plus directional movement, Wilder-smoothed (a running sum). | plusdm(14) > minusdm(14) |
minusdm(n=14) | Library | Minus directional movement, Wilder-smoothed. | minusdm(14) > plusdm(14) |
wavetrend(channel=10, avg=21) | Library | WaveTrend (LazyBear) WT1: a CCI-like channel index of hlc3, ema-smoothed. Below -60 is oversold. | wavetrend() < -60 |
wavetrendsignal(channel=10, avg=21) | Library | WaveTrend WT2: the 4-bar sma of wavetrend(). | crossup(wavetrend(), wavetrendsignal()) |
Trend
| Signature | Level | Definition | Example |
|---|---|---|---|
supertrend(n=10, mult=3) | Library | Supertrend line: an ATR band that trails below price in an uptrend and above it in a downtrend. | C > supertrend(10, 3) |
supertrenddir(n=10, mult=3) | Library | Supertrend direction: 1 in an uptrend, -1 in a downtrend. | supertrenddir(10, 3) > 0 |
psar(step=0.02, max=0.2) | Library | Parabolic SAR (Wilder): a stop that accelerates toward price. Price above it = long side. | C > psar() |
tenkan(n=9) | Library | Ichimoku conversion line (tenkan-sen): the middle of the n-bar high-low range. | tenkan() > kijun() |
kijun(n=26) | Library | Ichimoku base line (kijun-sen): the middle of the 26-bar range. | C > kijun() |
senkou_a(tenkan=9, kijun=26, shift=26) | Library | Ichimoku leading span A as it stands under TODAY's bar: (tenkan + kijun) / 2 computed `shift` bars ago. No look-ahead. | C > senkou_a() |
senkou_b(n=52, shift=26) | Library | Ichimoku leading span B under today's bar: the 52-bar midprice from `shift` bars ago. | C > senkou_b() |
cloud_top() | Library | Top of the Ichimoku cloud under today's bar: greatest(senkou_a(), senkou_b()). | C > cloud_top() |
cloud_bottom() | Library | Bottom of the Ichimoku cloud under today's bar. | C < cloud_bottom() |
viplus(n=14) | Library | Vortex indicator VI+: upward vortex movement over true range, n bars. | viplus(14) > viminus(14) |
viminus(n=14) | Library | Vortex indicator VI-. | viminus(14) > viplus(14) |
linreg_slope(x, n=14) | Library | Slope of the n-bar least-squares line, in x units per bar. | linreg_slope(C, 20) > 0 |
linreg_intercept(x, n=14) | Library | Value of the n-bar regression line at the window's FIRST bar. | C > linreg_intercept(C, 20) |
linreg_angle(x, n=14) | Library | Angle of the regression line in degrees: atan(slope). Scale-dependent, so compare it on the same symbol. | linreg_angle(C, 20) > 0 |
linreg_r2(x, n=20) | Library | R-squared of the n-bar regression line, 0-1: how straight the trend is. | linreg_r2(C, 50) > 0.8 and linreg_slope(C, 50) > 0 |
tsf(x, n=14) | Library | Time series forecast: the regression line extended one bar ahead. | C > tsf(C, 14) |
chop(n=14) | Library | Choppiness index, 0-100: high (over 61.8) in a sideways market, low (under 38.2) in a trend. | chop(14) > 61.8 |
er(x, n=10) | Library | Kaufman efficiency ratio, 0-1: net move over the sum of bar-to-bar moves. 1 = a straight line. | er(C, 10) > 0.5 |
massindex(n=9, sum=25) | Library | Mass index: range expansion. A 'reversal bulge' rises above 27 then drops below 26.5. | massindex() > 27 |
vhf(x, n=28) | Library | Vertical horizontal filter: n-bar range over the sum of bar moves. High = trending. | vhf(C, 28) > 0.35 |
Volatility
| Signature | Level | Definition | Example |
|---|---|---|---|
natr(n=14) | Library | Normalized ATR: atr(n) / C * 100. The same as atr_pct(). | natr(14) > 3 |
kcupper(n=20, mult=2, atr=10) | Library | Upper Keltner channel: ema(C, n) + mult * atr(atr). | C > kcupper(20, 2) |
kcmid(n=20) | Library | Middle Keltner line: ema(C, n). | C > kcmid(20) |
kclower(n=20, mult=2, atr=10) | Library | Lower Keltner channel: ema(C, n) - mult * atr(atr). | C < kclower(20, 2) |
dcupper(n=20) | Library | Upper Donchian channel: highest high of the last n bars, including today. | H >= dcupper(20) |
dclower(n=20) | Library | Lower Donchian channel: lowest low of the last n bars, including today. | L <= dclower(20) |
dcmid(n=20) | Library | Middle of the Donchian channel. | C > dcmid(20) |
dcwidth(n=20) | Library | Donchian width as a percent of its middle: how wide the n-bar range is. | dcwidth(20) < 8 |
bbwidth(x, n=20, k=2) | Library | Bollinger band width as a percent of the middle band: (upper - lower) / mid * 100. | bbwidth(C, 20) < percentile(bbwidth(C, 20), 252, 10) |
bbpct(x, n=20, k=2) | Library | Bollinger %B: where x sits between the bands, 0 = lower band, 1 = upper band. | bbpct(C, 20) > 1 |
chaikinvol(n=10, roc=10) | Library | Chaikin volatility: percent change over `roc` bars of the ema of the high-low range. | chaikinvol(10, 10) > 20 |
ulcer(x, n=14) | Library | Ulcer index: root-mean-square of the percent drawdown from the n-bar high. Downside volatility only. | ulcer(C, 14) < 3 |
hv(x, n=20) | Library | Historical volatility: annualised sample standard deviation of n daily log returns, in percent. | hv(C, 20) > hv(C, 100) |
parkinson(n=20) | Library | Parkinson volatility from the high-low range, annualised percent. | parkinson(20) > 30 |
garmanklass(n=20) | Library | Garman-Klass volatility from open, high, low and close, annualised percent. | garmanklass(20) > 30 |
rogerssatchell(n=20) | Library | Rogers-Satchell volatility (drift-independent), annualised percent. | rogerssatchell(20) > 30 |
yangzhang(n=20) | Library | Yang-Zhang volatility: overnight, open-to-close and Rogers-Satchell parts combined, annualised percent. The most efficient OHLC estimator. | yangzhang(20) > 30 |
range_pct() | Library | Today's high-low range as a percent of the close. | range_pct() > 2 * adr(20) |
adr(n=20) | Library | Average daily range percent: the n-bar average of (H / L - 1) * 100. | adr(20) > 4 |
squeeze(n=20, bb=2, kc=1.5) | Library | TTM-style squeeze: 1 while the Bollinger bands (k=bb) sit inside the Keltner channel (sma mid, kc * atr(n)). | squeeze(20) |
chandelier_long(n=22, mult=3) | Library | Chandelier exit for longs: max(H, n) - mult * atr(n). | C < chandelier_long(22, 3) |
chandelier_short(n=22, mult=3) | Library | Chandelier exit for shorts: min(L, n) + mult * atr(n). | C > chandelier_short(22, 3) |
Volume
| Signature | Level | Definition | Example |
|---|---|---|---|
ad() | Library | Accumulation/distribution line: running total of volume weighted by where each bar closed in its range. | ad() > sma(ad(), 20) |
adosc(fast=3, slow=10) | Library | Chaikin oscillator: ema(ad, fast) - ema(ad, slow). | adosc() > 0 |
cmf(n=20) | Library | Chaikin money flow, -1 to 1: sum of money-flow volume over sum of volume, n bars. | cmf(20) > 0.1 |
force(n=13) | Library | Elder force index: ema of (C - C[-1]) * V. | force(13) > 0 |
eom(n=14) | Library | Ease of movement: n-bar average of 10000 * (hl2 change) * (H - L) / V. Positive = price rises on light volume. | eom(14) > 0 |
pvt() | Library | Price volume trend: running total of V times each bar's fractional change. | pvt() > sma(pvt(), 50) |
nvi() | Library | Negative volume index (starts at 1000): moves only on days volume falls. Above its 255-day ema is the classic bull signal. | nvi() > ema(nvi(), 255) |
pvi() | Library | Positive volume index (starts at 1000): moves only on days volume rises. | pvi() > ema(pvi(), 255) |
rvwap(n=20) | Library | Rolling VWAP over the last n bars, from the typical price. Works on daily bars. | C > rvwap(20) |
avwap(c) | Library | Anchored VWAP since the most recent bar where the condition was true (that bar included). Anchor on anything: an earnings gap, a crossover, a swing low. | C > avwap(crossup(C, sma(C, 50))) |
wvwap() | Library | VWAP anchored to the first bar of the week. Requires dated bars. | C > wvwap() |
mvwap() | Library | VWAP anchored to the first bar of the month. Requires dated bars. | C > mvwap() |
qvwap() | Library | VWAP anchored to the first bar of the quarter. Requires dated bars. | C > qvwap() |
yvwap() | Library | VWAP anchored to the first bar of the year. Requires dated bars. | C > yvwap() |
volosc(fast=5, slow=10) | Library | Volume oscillator: percent gap between a fast and a slow ema of volume. | volosc(5, 20) > 25 |
kvo(fast=34, slow=55) | Library | Klinger volume oscillator (simple form): ema(sv, fast) - ema(sv, slow), where sv is the volume signed by the direction of hlc3. | kvo() > ema(kvo(), 13) |
dollarvol(n=1) | Library | Average dollar volume (C * V) over n bars. | dollarvol(20) > 50000000 |
updownvol(n=20) | Library | Up/down volume ratio: volume on up closes over volume on down closes, n bars. | updownvol(50) > 1.5 |
wad() | Library | Williams accumulation/distribution: running total of the close's move against the true range edge (no volume). | wad() > sma(wad(), 20) |
Statistics
| Signature | Level | Definition | Example |
|---|---|---|---|
variance(x, n=20) | Library | Population variance over the window (the square of std()). | variance(roc(C, 1), 20) > 4 |
var(x, n=20) | Library | Another name for variance(). | var(roc(C, 1), 20) > 4 |
skew(x, n=20) | Library | Sample skewness of the window (bias-corrected). Negative = a long left tail. | skew(roc(C, 1), 60) < -0.5 |
kurt(x, n=20) | Library | Sample excess kurtosis of the window (bias-corrected). Above 0 = fatter tails than normal. | kurt(roc(C, 1), 60) > 3 |
corr(x, y, n=20) | Library | Pearson correlation of two series over n bars, -1 to 1. | corr(C, V, 20) < 0 |
covar(x, y, n=20) | Library | Sample covariance of two series over n bars. | covar(roc(C, 1), roc(V, 1), 20) > 0 |
percentile(x, n=252, p=50) | Library | The p-th percentile (0-100) of the last n values, linearly interpolated. | atr_pct(14) < percentile(atr_pct(14), 252, 10) |
mad(x, n=20) | Library | Mean absolute deviation from the window mean. | abs(C - sma(C, 20)) > 2 * mad(C, 20) |
drawdown(x, n=0) | Library | Percent below the running peak of x (0 = at the peak). n=0 uses all the history loaded; n > 0 the peak of the last n bars. | drawdown(C) > 20 |
maxdrawdown(x, n=252) | Library | Largest peak-to-trough fall inside the last n bars, in percent (a positive number). | maxdrawdown(C, 252) < 15 |
runup(x, n=252) | Library | Largest trough-to-peak rise inside the last n bars, in percent. | runup(C, 20) > 25 |
barssincehigh(n=252) | Library | Bars since the highest high of the last n bars (0 = today). Ties count the most recent. | barssincehigh(252) <= 5 |
barssincelow(n=252) | Library | Bars since the lowest low of the last n bars (0 = today). | barssincelow(252) > 100 |
streak(c) | Library | How many bars in a row the condition has held, ending today (0 = not today). | streak(C > sma(C, 20)) >= 10 |
upstreak() | Library | Consecutive higher closes ending today. | upstreak() >= 4 |
downstreak() | Library | Consecutive lower closes ending today. | downstreak() >= 4 |
count(c, n=20) | Library | How many of the last n bars the condition held. | count(C > O, 10) >= 7 |
freq(c, n=20) | Library | Percent of the last n bars on which the condition held. | freq(C > C[-1], 20) > 60 |
any(c, n=20) | Library | 1 if the condition held on at least one of the last n bars. | any(crossup(C, sma(C, 50)), 5) |
all(c, n=20) | Library | 1 if the condition held on every one of the last n bars. | all(C > sma(C, 50), 20) |
valuewhen(c, x, nth=0) | Library | The value of x on the nth most recent bar where the condition held (nth=0: the latest, today included). | C > valuewhen(crossup(C, sma(C, 50)), C) |
cross(x, y) | Library | 1 on the bar where x crosses y in either direction. | cross(C, sma(C, 50)) |
crossover(x, y) | Library | 1 on the bar where x crosses ABOVE y: the same as crossup(). | crossover(C, sma(C, 50)) |
crossunder(x, y) | Library | 1 on the bar where x crosses BELOW y: the same as crossdown(). | crossunder(C, sma(C, 50)) |
rising(x, n=3) | Library | 1 if x rose on each of the last n bars (n higher values in a row). | rising(sma(C, 20), 5) |
falling(x, n=3) | Library | 1 if x fell on each of the last n bars. | falling(sma(C, 20), 5) |
change(x, n=1) | Library | x minus its value n bars ago. | change(C, 1) > 0 |
normalize(x, n=20) | Library | Where x sits in its own n-bar range, 0-100 (a stochastic of any series). | normalize(rsi(C, 14), 100) > 90 |
cummax(x) | Library | Running maximum of x over all bars loaded. | C >= cummax(C) |
cummin(x) | Library | Running minimum of x over all bars loaded. | L <= cummin(L) |
autocorr(x, n=20, lag=1) | Library | Correlation of x with itself `lag` bars earlier, over n bars. Positive on returns = momentum, negative = mean reversion. | autocorr(roc(C, 1), 60) < -0.2 |
Returns and risk
| Signature | Level | Definition | Example |
|---|---|---|---|
ret(n=1) | Library | Simple return of the close over n bars, in percent. | ret(20) > 10 |
logret(n=1) | Library | Log return of the close over n bars, in percent (100 * ln(C / C[-n])). | logret(5) < -5 |
cagr(n=252) | Library | Compound annual growth rate of the close over the last n bars, percent (252 bars = a year). | cagr(756) > 15 |
sharpe(n=252) | Library | Annualised Sharpe ratio of daily returns over n bars (risk-free rate 0). | sharpe(252) > 1 |
sortino(n=252) | Library | Annualised Sortino ratio: mean daily return over downside deviation (target 0). | sortino(252) > 1.5 |
calmar(n=252) | Library | Calmar ratio: cagr(n) / maxdrawdown(C, n). | calmar(756) > 1 |
hvar(n=252, p=95) | Library | Historical value at risk: the daily loss, in percent, exceeded on only (100 - p)% of the last n days (a positive number). | hvar(252, 95) > 3 |
cvar(n=252, p=95) | Library | Conditional VaR (expected shortfall): the average daily loss, in percent, on the days at or beyond hvar(n, p). | cvar(252, 95) > 4 |
winrate(n=20) | Library | Percent of the last n bars that closed higher than the bar before. | winrate(20) > 60 |
omega(n=252) | Library | Omega ratio (threshold 0): total daily gains over total daily losses, n bars. | omega(252) > 1.2 |
gainloss(n=20) | Library | Average gain on up days over average loss on down days, n bars. | gainloss(50) > 1.2 |
Calendar
| Signature | Level | Definition | Example |
|---|---|---|---|
dayofweek() | Library | Day of the week: 1 = Monday ... 5 = Friday. Requires dated bars. | dayofweek() == 1 |
dayofmonth() | Library | Calendar day of the month, 1-31. Requires dated bars. | dayofmonth() <= 5 |
month() | Library | Month, 1-12. Requires dated bars. | month() == 12 |
quarter() | Library | Quarter, 1-4. Requires dated bars. | quarter() == 4 |
year() | Library | Calendar year. Requires dated bars. | year() >= 2026 |
dayofyear() | Library | Day of the year, 1-366. Requires dated bars. | dayofyear() > 350 |
weekofyear() | Library | ISO week number, 1-53. Requires dated bars. | weekofyear() >= 52 |
days_to_month_end() | Library | Weekdays left in the month after today (0 on the last weekday of the month). Exchange holidays are not counted out. Requires dated bars. | days_to_month_end() <= 2 |
is_month_end() | Library | 1 on the last weekday of the month. Requires dated bars. | is_month_end() |
is_month_start() | Library | 1 on the first bar of a new month. Requires dated bars. | is_month_start() |
is_week_start() | Library | 1 on the first bar of a new week. Requires dated bars. | is_week_start() |
is_week_end() | Library | 1 on the last weekday of the week (Friday). Requires dated bars. | is_week_end() |
is_quarter_end() | Library | 1 on the last weekday of the quarter. Requires dated bars. | is_quarter_end() |
is_quarter_start() | Library | 1 on the first bar of a new quarter. Requires dated bars. | is_quarter_start() |
is_year_end() | Library | 1 on the last weekday of the year. Requires dated bars. | is_year_end() |
is_year_start() | Library | 1 on the first bar of a new year. Requires dated bars. | is_year_start() |
tdom() | Library | Trading day of the month: 1 on the month's first bar, 2 on the second ... (not known for the first, partial month loaded). Requires dated bars. | tdom() <= 3 |
is_opex() | Library | 1 on the monthly options expiration day: the third Friday of the month. Requires dated bars. | is_opex() |
days_to_opex() | Library | Weekdays from today to the next monthly options expiration (0 on the day itself). Requires dated bars. | days_to_opex() <= 3 |
Relative strength
| Signature | Level | Definition | Example |
|---|---|---|---|
close_of(symbol) | Library | Another symbol's daily close (C), on the same dates, e.g. close_of('SPY'). Ora symbols only; a date the other symbol did not trade carries its last close. Requires dated daily bars (the symbol's, or state.bars with dates); reads another symbol's daily bars. | C / close_of('SPY') > ref(C / close_of('SPY'), -20) |
open_of(symbol) | Library | Another symbol's daily open (O), on the same dates, e.g. open_of('SPY'). Ora symbols only; a date the other symbol did not trade carries its last open. Requires dated daily bars (the symbol's, or state.bars with dates); reads another symbol's daily bars. | C > O and open_of('SPY') > 0 |
high_of(symbol) | Library | Another symbol's daily high (H), on the same dates, e.g. high_of('SPY'). Ora symbols only; a date the other symbol did not trade carries its last high. Requires dated daily bars (the symbol's, or state.bars with dates); reads another symbol's daily bars. | C > O and high_of('SPY') > 0 |
low_of(symbol) | Library | Another symbol's daily low (L), on the same dates, e.g. low_of('SPY'). Ora symbols only; a date the other symbol did not trade carries its last low. Requires dated daily bars (the symbol's, or state.bars with dates); reads another symbol's daily bars. | C > O and low_of('SPY') > 0 |
volume_of(symbol) | Library | Another symbol's daily volume (V), on the same dates, e.g. volume_of('SPY'). Ora symbols only; a date the other symbol did not trade carries its last volume. Requires dated daily bars (the symbol's, or state.bars with dates); reads another symbol's daily bars. | volume_of('SPY') > 1.5 * sma(volume_of('SPY'), 20) |
rs(n=63, symbol='SPY') | Library | Relative strength: percent the symbol out- or under-performed the benchmark over n bars, ((C / C[-n]) / (B / B[-n]) - 1) * 100. Requires dated daily bars (the symbol's, or state.bars with dates); reads another symbol's daily bars. | rs(63) > 0 |
rsline(symbol='SPY') | Library | Relative-strength line: C / the benchmark's close. Compare it with its own past. Requires dated daily bars (the symbol's, or state.bars with dates); reads another symbol's daily bars. | rsline() > max(ref(rsline(), -1), 252) |
beta(n=60, symbol='SPY') | Library | Beta of daily returns against the benchmark over n bars: cov(r, rb) / var(rb). Requires dated daily bars (the symbol's, or state.bars with dates); reads another symbol's daily bars. | beta(60) > 1.5 |
correl(n=60, symbol='SPY') | Library | Correlation of daily returns with the benchmark over n bars, -1 to 1. Requires dated daily bars (the symbol's, or state.bars with dates); reads another symbol's daily bars. | correl(60) < 0.3 |
alpha(n=252, symbol='SPY') | Library | Annualised alpha against the benchmark over n bars, percent: (mean r - beta * mean rb) * 252 * 100. Requires dated daily bars (the symbol's, or state.bars with dates); reads another symbol's daily bars. | alpha(252) > 5 |
Patterns
| Signature | Level | Definition | Example |
|---|---|---|---|
doji() | Pattern | Body at most 10% of the day's range.Expands to(H > L) and (abs(C - O) <= 0.1 * (H - L)) | doji() |
dragonfly_doji() | Pattern | Doji with a long lower wick and almost no upper wick.Expands to((H > L) and (abs(C - O) <= 0.1 * (H - L))) and ((H - greatest(O, C)) <= 0.1 * (H - L)) and ((least(O, C) - L) >= 0.6 * (H - L)) | dragonfly_doji() |
gravestone_doji() | Pattern | Doji with a long upper wick and almost no lower wick.Expands to((H > L) and (abs(C - O) <= 0.1 * (H - L))) and ((least(O, C) - L) <= 0.1 * (H - L)) and ((H - greatest(O, C)) >= 0.6 * (H - L)) | gravestone_doji() |
spinning_top() | Pattern | Small body (10-30% of range) with wicks on both sides.Expands to(H > L) and (abs(C - O) <= 0.3 * (H - L)) and (abs(C - O) > 0.1 * (H - L)) and ((H - greatest(O, C)) >= 0.25 * (H - L)) and ((least(O, C) - L) >= 0.25 * (H - L)) | spinning_top() |
marubozu() | Pattern | Body at least 90% of the range: almost no wicks.Expands to(H > L) and (abs(C - O) >= 0.9 * (H - L)) | marubozu() |
hammer() | Pattern | Long lower wick (2x the body), tiny upper wick, after a 5-bar decline.Expands to((H > L) and (abs(C - O) > 0) and ((least(O, C) - L) >= 2 * abs(C - O)) and ((H - greatest(O, C)) <= 0.1 * (H - L))) and (C < C[-5]) | hammer() |
hanging_man() | Pattern | Hammer shape after a 5-bar rise.Expands to((H > L) and (abs(C - O) > 0) and ((least(O, C) - L) >= 2 * abs(C - O)) and ((H - greatest(O, C)) <= 0.1 * (H - L))) and (C > C[-5]) | hanging_man() |
shooting_star() | Pattern | Long upper wick (2x the body), tiny lower wick, after a 5-bar rise.Expands to((H > L) and (abs(C - O) > 0) and ((H - greatest(O, C)) >= 2 * abs(C - O)) and ((least(O, C) - L) <= 0.1 * (H - L))) and (C > C[-5]) | shooting_star() |
inverted_hammer() | Pattern | Shooting-star shape after a 5-bar decline.Expands to((H > L) and (abs(C - O) > 0) and ((H - greatest(O, C)) >= 2 * abs(C - O)) and ((least(O, C) - L) <= 0.1 * (H - L))) and (C < C[-5]) | inverted_hammer() |
engulfing_bull() | Pattern | Green body that engulfs yesterday's red body.Expands to(C[-1] < O[-1]) and (C > O) and (O <= C[-1]) and (C >= O[-1]) and (abs(C - O) > abs(C[-1] - O[-1])) | engulfing_bull() |
engulfing_bear() | Pattern | Red body that engulfs yesterday's green body.Expands to(C[-1] > O[-1]) and (C < O) and (O >= C[-1]) and (C <= O[-1]) and (abs(C - O) > abs(C[-1] - O[-1])) | engulfing_bear() |
harami_bull() | Pattern | Small green body inside yesterday's red body.Expands to(C[-1] < O[-1]) and (C > O) and (abs(C - O) < abs(C[-1] - O[-1])) and (O >= C[-1]) and (C <= O[-1]) | harami_bull() |
harami_bear() | Pattern | Small red body inside yesterday's green body.Expands to(C[-1] > O[-1]) and (C < O) and (abs(C - O) < abs(C[-1] - O[-1])) and (O <= C[-1]) and (C >= O[-1]) | harami_bear() |
piercing_line() | Pattern | Opens below yesterday's close, closes above the middle of its red body.Expands to(C[-1] < O[-1]) and (C > O) and (O < C[-1]) and (C > (O[-1] + C[-1]) / 2) and (C < O[-1]) | piercing_line() |
dark_cloud() | Pattern | Opens above yesterday's close, closes below the middle of its green body.Expands to(C[-1] > O[-1]) and (C < O) and (O > C[-1]) and (C < (O[-1] + C[-1]) / 2) and (C > O[-1]) | dark_cloud() |
morning_star() | Pattern | Red bar, small bar below it, then a green bar closing above the middle of the first.Expands to(C[-2] < O[-2]) and (abs(C[-1] - O[-1]) <= 0.5 * abs(C[-2] - O[-2])) and (greatest(O[-1], C[-1]) <= C[-2]) and (C > O) and (C > (O[-2] + C[-2]) / 2) | morning_star() |
evening_star() | Pattern | Green bar, small bar above it, then a red bar closing below the middle of the first.Expands to(C[-2] > O[-2]) and (abs(C[-1] - O[-1]) <= 0.5 * abs(C[-2] - O[-2])) and (least(O[-1], C[-1]) >= C[-2]) and (C < O) and (C < (O[-2] + C[-2]) / 2) | evening_star() |
three_white_soldiers() | Pattern | Three green bars, each closing higher and opening inside the previous body.Expands to(C > O) and (C[-1] > O[-1]) and (C[-2] > O[-2]) and (C > C[-1]) and (C[-1] > C[-2]) and (O >= O[-1]) and (O <= C[-1]) and (O[-1] >= O[-2]) and (O[-1] <= C[-2]) | three_white_soldiers() |
three_black_crows() | Pattern | Three red bars, each closing lower and opening inside the previous body.Expands to(C < O) and (C[-1] < O[-1]) and (C[-2] < O[-2]) and (C < C[-1]) and (C[-1] < C[-2]) and (O <= O[-1]) and (O >= C[-1]) and (O[-1] <= O[-2]) and (O[-1] >= C[-2]) | three_black_crows() |
tweezer_bottom() | Pattern | Red then green with lows within 0.1%.Expands to(C[-1] < O[-1]) and (C > O) and (abs(L - L[-1]) <= 0.001 * C[-1]) | tweezer_bottom() |
tweezer_top() | Pattern | Green then red with highs within 0.1%.Expands to(C[-1] > O[-1]) and (C < O) and (abs(H - H[-1]) <= 0.001 * C[-1]) | tweezer_top() |
inside_bar() | Pattern | Today's range inside yesterday's.Expands to(H <= H[-1]) and (L >= L[-1]) | inside_bar() |
outside_bar() | Pattern | Today's range beyond yesterday's high and low.Expands to(H > H[-1]) and (L < L[-1]) | outside_bar() |
gap_up() | Pattern | Today's whole range above yesterday's high (unfilled gap up).Expands toL > H[-1] | gap_up() |
gap_down() | Pattern | Today's whole range below yesterday's low (unfilled gap down).Expands toH < L[-1] | gap_down() |
10. Errors
A rule that does not parse, calls an unknown function, passes the wrong number of arguments, puts an offset on anything but a price letter, or names an unknown fact MUST be rejected, not answered. Over HTTP, Ora returns 422 with one entry per problem, pointing at the question:
{
"detail": [
{
"loc": [
"body",
"questions",
"trend",
"ask"
],
"type": "value_error",
"msg": "Unknown function `smaa`. Did you mean `sma`?"
}
]
}
11. Conformance
| Level | An implementation must |
|---|---|
| Core | Parse the grammar (section 3) with its precedence; evaluate prices, offsets and the core functions; produce beliefs exactly as section 6, to 4 decimal places. |
| Library | Core, plus any library functions it offers, under these names and definitions. It MUST reject (not reinterpret) library names it does not offer. |
| Patterns | Core, plus every pattern evaluated exactly as its expansion. |
| Facts | Core, plus a fact group with these names, units, scales and point-in-time meaning. |
Say which levels you implement, for example “Ora Rules v1: Core + Patterns”. Test vectors: the belief table in section 6 is normative; the rule library shows real answers on real symbols for comparison.
12. Versioning
This is Ora Rules v1 (1.0.0). Minor versions (1.1, 1.2) only add functions, facts or patterns; they never change what an existing rule means. A change to the meaning of an existing rule or to the belief formula is a new major version with its own URL. Each version stays published at /spec/v<major> and /spec/v<major>.json.
13. Implementing it
- Start from /spec/v1.json: the grammar, belief constants and every function with its signature, definition and an example.
- Check your beliefs against Ora's on the same bars: send your own bars to
POST /v1/systemone(bring your own bars) and compare. - Agents can call Ora directly over MCP, and share any rule as a public link from the Playground.
Ora Rules v1 by KlickAnalytics (oramodel.com), licensed CC BY 4.0. Information, not investment advice.