Appearance
AlertEvaluationManager
Condition-checking engine for PRICE_CONDITION alerts. Evaluates registered alerts against prices each tick, triggers callbacks to UserPaperTradePortfolio, and self-heals zombie alerts.
High-Level Design
AlertEvaluationManager is the decision engine that determines which alerts have triggered. It is stateless — it holds no in-memory state and delegates all storage to AlertStorageManager. It only handles PRICE_CONDITION alerts (LIMIT_ORDER, STOP_LOSS, TAKE_PROFIT, LIQUIDATION_WATCH); PRICE_ARRIVAL alerts follow a completely separate path in the PriceAlert alarm loop.
Data Flow
PriceAlert alarm loop (1s ticks)
│
for each timestamp in cursor range:
│
┌───────────────┴─────────────────┐
│ if hasConditionAlerts: │
│ │
│ 1. evaluateAlertsSync(prices) │
│ ├─ storageManager │
│ │ .getAlertsBySymbol() │
│ ├─ match price per symbol │
│ └─ return triggered[] │
│ │
│ 2. processTriggeredAlerts() │
│ (sequential, one-by-one) │
│ └─ triggerAlert() per alert │
└───────────────┬─────────────────┘
│
triggerAlert()
│
▼
UserPaperTradePortfolio
.onAlert(alert, ts, prices)
│
┌─────────┼───────────┐
▼ ▼ ▼
Success POSITION_ Other
+ oneTime NOT_FOUND error
│ STALE_ALERT │
▼ │ keep alert
deregister ▼ (retry next tick)
alert deregister
(self-heal)Note: The alarm loop calls evaluateAlertsSync() and processTriggeredAlerts() as two separate steps — the manager does not orchestrate both internally. The loop also gates evaluation behind hasConditionAlerts to skip work when no condition alerts are registered.
Direction-Aware Trigger Logic
Each alert type uses direction (LONG/SHORT) to determine when to fire:
| Alert Type | LONG triggers when | SHORT triggers when |
|---|---|---|
LIMIT_ORDER | price <= target | price >= target |
STOP_LOSS | price <= target | price >= target |
LIQUIDATION_WATCH | price <= target | price >= target |
TAKE_PROFIT | price >= target | price <= target |
TAKE_PROFIT has inverted logic — a LONG take-profit fires when price rises above target, while the other three fire when price falls below. Unknown alert types log a warning and return false.
Sequential Alert Processing
Triggered alerts are processed one-by-one, not in parallel. This prevents overwhelming downstream UserPaperTradePortfolio DOs when multiple alerts fire on the same tick (e.g., a sharp price move triggering several stop-losses simultaneously).
Self-Healing Zombie Alerts
When a user closes a position, the associated alerts may still be registered. Rather than requiring upstream cleanup, this manager detects orphaned alerts reactively:
POSITION_NOT_FOUND— position was closed, alert is staleSTALE_ALERT— alert references outdated state
Both trigger permanent deregistration via storageManager.deregisterAlert(), preventing zombie alerts from accumulating and wasting evaluation cycles.
One-Time vs Persistent Alerts
Alerts with oneTime === true (e.g., limit orders) are deregistered after a successful trigger. Non-one-time alerts (e.g., liquidation watches) persist and continue evaluating on subsequent ticks until explicitly deregistered.
Transient Failure Resilience
Non-specific RPC errors (network issues, DO eviction) do not deregister the alert. It stays registered and will be re-evaluated on the next 1-second tick. RPC exceptions are caught and logged but never crash the alarm loop.
State & Storage
In-Memory State
None. This manager is stateless.
SQLite Tables
None directly. Delegates to AlertStorageManager for alert CRUD.
Interactions
Depends On
- AlertStorageManager (
durable.storageManager): readsgetAlertsBySymbol()every tick; callsderegisterAlert()for triggered/zombie alerts. - UserPaperTradePortfolio DO (via
getUserPaperTradePortfolioDO()): callsonAlert()RPC.
Depended By
- PriceAlert alarm loop: calls
evaluateAlertsSync()andprocessTriggeredAlerts()each tick when condition alerts exist.
Edge Cases
targetPriceis stored as a string for precision; parsed to float at evaluation time viaparseFloat().- Symbols with no price data in the current tick are silently skipped (no alert can trigger without a price).
- Self-healing prevents zombie alerts from accumulating after positions are closed or become stale.
- RPC failures are caught and logged but do not crash the alarm loop; the alert remains registered for retry on the next tick.
See Also
- AlertStorageManager — provides alert data and handles deregistration
- AlertCursorManager — determines which timestamps to evaluate
- PriceDataManager — supplies price data for evaluation
- Parent DO:
PriceAlert