Appearance

Personalize your experience

Mode
Accent Color
Direction
Expressions & Operators

Expressions & Operators

Coding

How an expression is parsed and evaluated: precedence, operand types, boolean folding, the operators available, and the mistakes that produce a silently wrong condition.

What an expression is

An expression is anything that resolves to a single on a given bar. That covers a bare , an indicator call, arithmetic over both, and a function applied to the result. The does not distinguish between them — it for a value and gets one.

all of these are expressions
Close                              # a price field
RSI(14)                            # an indicator call
Close - Open                       # arithmetic
Abs(Close - EMA(21))               # a function over arithmetic
(High - Low) / Close * 100         # a compound expression
ATR(14) / ATR(14)[20]              # arithmetic across two bars

Operator precedence

Standard mathematical precedence, then comparison, then boolean. Parentheses override, and are worth using even where they are redundant — a condition someone has to work out the precedence of is a condition that will eventually be misread.

LevelTypeDescription
1 — grouping( )Innermost first. Always wins.
2 — unary- xNegation.
3 — powerPow(x, n)Exponentiation, expressed as a function rather than an operator.
4 — multiplicative* / ModLeft to .
5 — additive+ -Left to .
6 — comparison> < >= <= == !=Produces a boolean. Both operands are evaluated first.
7 — crossovercrossesAbove crossesBelowProduces a boolean, and reads the previous bar as well as this one.
8 — booleanNOT, then AND, then ORNOT binds tightest, OR loosest — which is where most precedence surprises come from.
precedence in practice
# these are the same expression
Close - Open * 2
Close - (Open * 2)

# and these are NOT the same
A AND B OR C            # → (A AND B) OR C
A AND (B OR C)          # → what people usually mean

# the second reading is almost always the intent:
# "trigger fired AND (confirmation A OR confirmation B)"
!
Caution:AND binds tighter than OR. Written flat, “ oversold AND MACD bullish OR up” means “(oversold AND bullish) OR (Supertrend up)” — so a Supertrend flip alone satisfies the whole condition, with no oversold requirement at all. That is rarely the intent and it does not look wrong. Parenthesise every mixed expression.

Operand types

Three kinds of operand, and any of them may appear on either side of a comparison. That symmetry is what makes indicator-against-indicator conditions possible without syntax.

TypeTypeDescription
constantnumberA literal. 30, 1.5, −0.02. The only operand with no bar dependency.
priceseriesA field from the candle — Close, High, Volume, HLC3 — optionally indexed backwards.
indicatorseriesA named indicator with parameters. (14) and RSI(28) are distinct operands, not one operand configured two ways.
★
Pro tip:Comparing two indicators is usually stronger than comparing an indicator to a constant. Volume > Mean(Volume, 20) * 1.5 adapts to the ; Volume > 100000 is a number that was true of one in one year. The first is a relationship, the second is a memory.

Comparison operators

OperatorTypeDescription
>valueStrictly greater. Holds for as long as the relationship holds.
<valueStrictly less.
>=valueGreater or equal. Prefer over > when a boundary should count.
<=valueLess or equal.
==valueExact equality. Safe on values — a , a state, an integer. Unreliable on anything computed, because floating-point arithmetic is not exact.
!=valueInequality. Carries the same floating-point caveat.
crossesAbovetransitionFires on the bar where the left operand goes from below the to at-or-above it. Requires a previous bar, so it can never fire on bar 0.
crossesBelowtransitionFires on the bar where the left operand goes from above the to at-or-below it.
what each one compiles to
// a value comparison reads one barconst gt = (i: number) => left[i] > right[i];// a transition reads two, and needs the previous one to existconst crossesAbove = (i: number) =>  i > 0 &&  left[i - 1] <= right[i - 1] &&   // was at or below  left[i]     >  right[i];          // is now above// which is why two transitions ANDed almost never fire:// both must make their crossing on the very same barconst both = (i: number) => crossesAboveA(i) && crossesAboveB(i);

Boolean folding

A folds its children into one boolean. AND requires all, OR requires one, and both short-circuit — they stop as soon as the answer is determined.

how a group evaluates
function evaluate(group: Group, i: number): boolean {  if (group.operator === 'AND') {    for (const child of group.children) {      // the first false settles it — remaining children are never evaluated      if (!resolve(child, i)) return false;    }    return true;  }  for (const child of group.children) {    if (resolve(child, i)) return true;   // the first true settles it  }  return false;}
  • An empty AND group is true — there is nothing to fail. An empty OR group is false. This matters if a group can be constructed programmatically: an accidentally-empty AND is a condition that always fires.
  • Short-circuiting is observable in performance, not in . Ordering cheap conditions first means expensive indicators are only computed on bars that already passed everything else.
  • A comparison against an undefined operand — during warm-up, or past the start of the — is false, never an error. An AND group containing one therefore fails silently.
★
Pro tip:Order conditions by cost: time filters and price comparisons first, single indicators next, multi-line systems like Ichimoku last. On a long backtest this is frequently the difference between seconds and minutes, and it costs nothing to get .

Guarding arithmetic

Expressions meet you did not : a halted where high equals low, an illiquid option with zero volume, the first bar after a gap. Division is where that bites.

unguarded and guarded
# fragile — a doji bar makes the denominator zero
Abs(Close - Open) / (High - Low)  >  0.7

# guarded — the epsilon costs nothing and cannot divide by zero
Abs(Close - Open) / (High - Low + 0.0001)  >  0.7

# also fine, and clearer about intent
AND {
  High - Low  >  0
  Abs(Close - Open) / (High - Low)  >  0.7
}
!
Caution:A zero denominator does not raise an error — it produces infinity or NaN, and comparisons against those are false. So the condition simply stops firing on exactly the bars you were most interested in, with nothing in the log to say why.

Normalising for comparability

A raw price difference is not comparable across instruments or across time. Normalising is what lets one condition work on a ₹50 stock and a ₹5,000 one, and keep working after a year of .

PatternTypeDescription
x / Close * 100percent of price a price distance into a . (14) / Close * 100 is volatility as a percentage, comparable anywhere.
x / ATR(14)multiples of rangeExpresses a distance in units of normal movement. "Two ATRs from the mean" adapts to the regime; "ten rupees" does not.
ZScore(x, N)standard deviationsDimensionless. Beyond ±2 is statistically unusual regardless of or price level.
x / Mean(x, N)ratio to own averageVolume / Mean(Volume, 20) is the canonical form — a of 2 means twice normal , on any .
★
Pro tip:Reach for Z-score whenever a threshold is doing the work in a condition. It is the difference between a rule and a number that happened to fit last year.

Reading a condition back

See also

Related articles

  • Evaluation Performance

    Coding / Performance

    Why a backtest is slow and what actually fixes it: condition ordering, indicator reuse, incremental computation, and the difference between an O(n) and an…

  • Strategy DSL Format

    Automation / Strategy Builder / Reference

    The engine JSON a strategy saves as. The form, node and JSON editors all edit this one document, and the C++ engine runs it unchanged in backtests and live.

  • Building Custom Indicators

    Coding / Custom Indicators

    Composing new indicators from existing ones, the recursive shapes that need care, warm-up handling, and how to check a custom indicator is actually correct.

  • Built-in Indicators & Overlays

    Coding / IDE / Reference

    Quatick ships with a full library of pre-built indicators and overlays. You can use these out of the box, or study their implementations as reference for…

  • Coding for Trading — Fundamentals

    Coding / Fundamentals

    The concepts every strategy rests on, whether you write it in the condition builder or in code: series, bar indexing, the difference between a value and a…

  • Condition Recipes

    Coding / Recipes

    Patterns worth knowing by heart: regime detection, confirmation, divergence, volatility gating, session control, and the idioms that express each one cleanly.

  • Debugging Strategies

    Coding / Debugging

    A method for diagnosing strategies that do nothing, do too much, or do the wrong thing — plus the specific symptoms that have specific causes.

  • IDE Keyboard Shortcuts

    Coding / IDE / Keyboard

    Quatick IDE supports extensive keyboard shortcuts inspired by VS Code. Shortcuts are organized by area. On macOS, replace Ctrl with ⌘ and Alt with ⌥ where…

  • Python Scripting with Pyodide

    Coding / IDE / Python

    Quatick strategies run in Python 3.11 via Pyodide — a full CPython compiled to WebAssembly. This means your strategies execute entirely in the browser, with…

Browse all knowledge base articles →