Trading  ·  BraivIQ AI Engineering Playbook

Building A Real-Time Alerts Engine For Trading Charts In Code: Evaluating Thousands Of Price And Indicator Conditions On Every Tick

Every charting platform offers alerts - tell me when the price crosses this level, when RSI goes above seventy, when the fast average crosses the slow one - and traders set them by the thousand. Behind that simple feature is an engine that must evaluate every rule against every relevant tick or bar, across every instrument, without lag, without missing a crossing that happened between two updates, without firing the same alert twenty times as price oscillates around a level, and without losing alerts when a connection drops. A naive implementation - loop over all rules on every tick - works for a hundred alerts and collapses at a hundred thousand. This playbook, from a team that specialises in trading charts and automation, is a code-side account of building an alerts engine that holds up: compiling user rules into typed predicates, indexing them so a tick checks only the rules it could possibly trigger, the surprisingly subtle semantics of a crossing, evaluating on bar close versus every tick, feeding indicator-based conditions from the incremental indicator engine, deduplication and cooldowns, reliable delivery with idempotency, catch-up after outages, and testing by deterministic replay.

 ·  13 min read  ·  By BraivIQ Engineering

Building A Real-Time Alerts Engine For Trading Charts In Code: Evaluating Thousands Of Price And Indicator Conditions On Every Tick

Rules × ticks - The naive engine loops every rule on every tick - fine at a hundred alerts, hopeless at a hundred thousand  ·  O(log n) per tick - Index rules by instrument and sorted threshold so a price move touches only the rules it could trigger  ·  A crossing is an event - Not a state - direction, once-only versus re-arm, and hysteresis decide whether an alert fires once or flaps  ·  At-least-once, deduped - Deliver reliably with idempotency keys so a retry never sends the same alert twice

Alerts are the feature traders set and forget, and that is exactly why they are hard to build. Tell me when the price crosses this level. Tell me when RSI goes above seventy on the hourly. Tell me when the fast moving average crosses the slow one. Each is a sentence to the user and a rule to the engine, and a popular platform holds hundreds of thousands of them across thousands of instruments and timeframes, every one of which must be checked against the stream of ticks and bars as it arrives. The requirements are unforgiving in the way trading software always is: no lag, because a late alert is a useless one. No missed crossings, even when the price jumped across a level between two updates. No duplicate firing as the price oscillates around the level for an hour. No alerts lost when a user's connection or the engine itself goes down, and complete explainability, because a trader who asks why an alert fired - or did not - deserves an exact answer. The naive implementation, a loop over every rule on every tick, satisfies none of this at scale. This is a domain we specialise in, and this playbook is a code-side account of how an alerts engine that holds up is actually built.

Compiling Rules And Indexing Them

The first design decision is to treat a user's alert not as a string to be interpreted on every tick but as a rule to be compiled once into a typed predicate over a well-defined input. A rule has a subject (an instrument and timeframe), a left operand (price, or an indicator value such as RSI-14 or a moving average), a comparison (crosses above, crosses below, is above, is below, equals within a tolerance), a right operand (a constant threshold or another series, as in a crossover of two averages), and a policy (fire once then disable, or re-arm after the condition clears). Compiling it means validating these at creation time, resolving the indicator references to the incremental indicator instances that will supply their values, and producing a predicate the engine can evaluate without parsing. The second decision - and the one that makes scale possible - is indexing. Rules are grouped by subject, so a tick for one instrument on one timeframe reaches only the rules that watch it. That alone turns a global scan into a per-instrument one. Within a subject, threshold rules against a single series are placed in a sorted structure keyed by threshold, so that when the price moves from one value to another the engine finds every threshold between the old and new values with a logarithmic range query and touches nothing else - a hundred thousand price alerts across the market cost a handful of comparisons per tick. Rules whose left and right operands are both series, such as moving-average crossovers, cannot be threshold-indexed and are grouped by the pair of series they watch, evaluated when either updates. The invariant the index maintains is that the set of rules touched on a tick is exactly the set whose outcome could have changed, and that invariant is what keeps evaluation cost proportional to activity rather than to the number of rules.

  • Compile once - validate the rule at creation, resolve indicator references to live instances, produce a typed predicate. Never parse on the hot path.
  • Group by subject - rules keyed by instrument and timeframe so a tick reaches only the rules that watch it.
  • Index thresholds in a sorted structure - a price move from old to new finds every threshold in between with a logarithmic range query.
  • Group series-vs-series rules by the pair they watch - crossovers evaluate when either series updates.
  • Keep the invariant - the rules touched per tick are exactly those whose outcome could have changed.

The Semantics Of A Crossing

The subtlest part of an alerts engine is also the one users notice most, because getting it wrong produces alerts that fire twenty times or never. A crossing is an event, not a state: 'price crosses above 100' means the previous evaluated value was at or below 100 and the current one is above it, which requires the engine to keep, per rule, the last evaluated value and to compare transitions rather than levels. Three refinements follow from real behaviour. Direction must be explicit, because a user who asks for a cross above does not want to be told about the cross back below, and a user who asks for 'crosses' in either direction wants both - these are different rules. Re-arming must be a policy: a once-only alert fires and disables itself. A repeating alert must not fire again until the condition has cleared, and the definition of 'cleared' is where hysteresis enters - without a dead band, a price oscillating a tick either side of the level fires on every oscillation, so a repeating rule re-arms only when the value has moved a configurable distance back through the level, or after a minimum interval, or both. And the gap case must be handled: if the price jumps from 99 to 103 in one update, a rule at 100 and a rule at 102 have both been crossed even though neither value was ever observed, which is exactly what the sorted-threshold range query catches and a naive equality check misses. Encoding these semantics explicitly in the rule's policy, and storing the per-rule transition state durably, is what makes an alert fire once, on the crossing, every time - and it is also what lets the engine explain afterwards exactly which transition triggered it.

Bar Close Versus Tick, Indicator Conditions, And Reliable Delivery

Two further decisions determine whether users trust the engine. The first is when a rule is evaluated. Price rules can evaluate on every tick, because the price is final the moment it prints. Indicator rules and bar-based rules face the forming-bar trap: an RSI computed on the current, incomplete hourly bar is provisional and may cross back before the bar closes, so an alert that fires on a provisional value fires on a reading that then disappears from the chart. The engine therefore offers, and defaults sensibly between, evaluation on bar close - the indicator's committed value, the trader's usual intent - and evaluation on every tick with the provisional value, clearly labelled, for those who want the earlier and noisier signal. This is the same provisional-versus-committed contract the incremental indicator engine exposes, and the alerts engine simply subscribes to the right stream. The second decision is delivery, which must be reliable in the face of everything that fails. An alert, once triggered, is written durably with an idempotency key derived from the rule, the triggering transition and the bar or tick time before any attempt to deliver it, so that the engine crashing after triggering but before sending loses nothing on restart. Delivery - push notification, email, webhook to a user's own automation - is at-least-once with retries and backoff, and the idempotency key lets every channel deduplicate so a retry never produces a second notification. Cooldowns per rule and rate limits per user stop a volatile market from drowning someone in alerts. And catch-up matters: when a user's device reconnects or the engine recovers from an outage, it must replay the alerts that fired in the gap, in order, from the durable record, rather than silently dropping them - and it must evaluate rules against the ticks it missed, using the same transition logic, so a crossing that happened during the outage is still detected. Finally, testing: the whole engine - index, crossing semantics, forming-bar handling, delivery - is validated by deterministic replay of recorded tick and bar streams against a set of rules with known expected alerts, so that a change to any part is measured against exactly which alerts fired, when, and why.

The Bottom Line

Alerts look like the simplest feature on a trading chart and are one of the most demanding engines behind it, because hundreds of thousands of rules must be evaluated against every relevant tick and bar without lag, missed crossings, flapping, duplicates or loss. Building one that holds up is concrete engineering: compile each rule once into a typed predicate. Index rules by subject and by sorted threshold so a tick touches only the rules whose outcome could have changed, with a logarithmic range query that also catches crossings the price gapped over. Treat a crossing as a transition with explicit direction, once-only or re-arm policy, and hysteresis against flapping. Evaluate price rules per tick and indicator rules on bar close by default, subscribing to the indicator engine's committed stream to avoid phantom alerts on forming bars. Write every triggered alert durably with an idempotency key before delivering it at-least-once with deduplication, cooldowns and rate limits. Replay missed alerts and re-evaluate missed ticks after any outage, and prove all of it by deterministic replay against known expected alerts. An engine built this way fires once, promptly, with an explanation, every time the condition is met and never otherwise - which is the whole contract, and exactly the specialism we bring to trading charts and automation.

References & Further Reading

  • TradingView - automatic candlestick pattern detection and alert conditions documentation (user-facing alert semantics): https://www.tradingview.com/support/solutions/43000584462-automatic-candlestick-pattern-detection/
  • TradingView - lightweight-charts documentation: series and real-time updates (the forming-bar contract): https://tradingview.github.io/lightweight-charts/docs
  • QuestDB - real-time time-series processing for financial data: https://questdb.com/docs/
  • Wikipedia - hysteresis in control systems (dead bands against oscillation): https://en.wikipedia.org/wiki/Hysteresis
  • Open Web Solutions - trading dashboard development in 2026 with real-time charting and WebSockets: https://openwebsolutions.in/blog/high-performance-trading-dashboard-react-websockets/