Real-time monitoring is the continuous process of evaluating signal states during live trading execution. The system uses an infinite loop with periodic sleep intervals to check signal status, emit events, collect performance metrics, and handle errors without interrupting execution. This page covers the monitoring loop architecture, tick evaluation, event emission, and state-specific monitoring behavior.
For information about the overall live execution flow, see 10.1. For crash recovery mechanisms, see 10.2. For interval-based throttling to prevent signal spam, see 10.4.
The monitoring loop is implemented in LiveLogicPrivateService as an infinite while(true) loop that continuously evaluates signal status. Each iteration creates a real-time timestamp with new Date(), calls tick() to evaluate signal state, emits events, and sleeps for TICK_TTL before the next iteration.
Key Components:
| Component | Type | Purpose |
|---|---|---|
TICK_TTL |
const number |
Sleep interval between ticks: 1 * 60 * 1_000 + 1 (just over 1 minute) |
when |
Date |
Real-time timestamp created with new Date() for each iteration |
result |
IStrategyTickResult |
Discriminated union returned by tick() method |
sleep() |
Function | Async delay from functools-kit to pause between iterations |
Loop Behavior:
getStopped() check when idle or after signal closesTICK_TTL except when breakingThe tick() method evaluates the current signal state by checking for pending signals, validating scheduled signals, or generating new signals via getSignal(). The evaluation is wrapped in an execution context containing symbol, timestamp, and backtest flag.
Validation Chain:
The signal validation process runs multiple checks sequentially before allowing a signal to be created:
| Validation Type | Service | Checks |
|---|---|---|
| Schema Existence | StrategyValidationService |
Strategy is registered via addStrategy() |
| Risk Existence | RiskValidationService |
Risk profiles exist if specified |
| Signal Type | ClientStrategy |
position is "long" or "short" |
| Price Logic | ClientStrategy |
TP > priceOpen > SL for long, SL > priceOpen > TP for short |
| Price Distance | ClientStrategy |
TP/SL meet minimum distance requirements |
| Time Validity | ClientStrategy |
minuteEstimatedTime is positive |
| Risk Checks | ClientRisk.checkSignal() |
Portfolio limits, custom validations |
The monitoring system emits events through RxJS Subjects for external observers to track execution without coupling to internal logic. Events are emitted at multiple points during tick evaluation and are processed sequentially via queued() wrapper.
Event Types and Payloads:
| Emitter | Contract Type | Emitted When | Key Fields |
|---|---|---|---|
signalEmitter |
IStrategyTickResult |
Every tick result | action, signal, currentPrice, symbol |
signalLiveEmitter |
IStrategyTickResult |
Live mode only | Same as signalEmitter |
performanceEmitter |
PerformanceContract |
Every tick | metricType: "live_tick", duration, timestamp |
errorEmitter |
Error |
Tick fails | message, stack |
partialProfitSubject |
PartialProfitContract |
Profit milestone | level, data, currentPrice, backtest |
partialLossSubject |
PartialLossContract |
Loss milestone | level, data, currentPrice, backtest |
riskSubject |
RiskContract |
Signal rejected | symbol, pendingSignal, activePositionCount, comment |
Listener Pattern:
All listener functions use the queued() wrapper to ensure sequential processing:
// From src/function/event.ts
export function listenSignalLive(fn: (event: IStrategyTickResult) => void) {
return signalLiveEmitter.subscribe(queued(async (event) => fn(event)));
}
The queued() wrapper guarantees that:
Each tick iteration emits performance metrics to track execution duration and detect bottlenecks. The metrics include operation type, duration, timestamp, and delta from the previous event.
Performance Contract Fields:
| Field | Type | Purpose |
|---|---|---|
timestamp |
number |
Current event timestamp in milliseconds |
previousTimestamp |
number | null |
Previous event timestamp for delta calculation |
metricType |
"live_tick" |
Operation type being measured |
duration |
number |
Execution time in milliseconds (from performance.now()) |
strategyName |
string |
Strategy being executed |
exchangeName |
string |
Exchange being used |
symbol |
string |
Trading pair symbol |
backtest |
boolean |
Always false for live mode |
Metric Types:
For live trading, the primary metric is:
"live_tick": Time to complete one monitoring iteration including tick evaluation, event emission, and state checksThe previousTimestamp field enables calculating time between events for monitoring system throughput.
The monitoring loop implements graceful error handling to ensure that transient failures do not stop live trading execution. Errors are logged, emitted for external observers, and followed by a sleep before retry.
Error Handling Strategy:
| Step | Action | Purpose |
|---|---|---|
| Console Warning | console.warn() with context |
Immediate visibility in logs |
| Logger Service | loggerService.warn() with errorData() |
Structured logging with stack trace |
| Error Emission | errorEmitter.next(error) |
External observers can react |
| Sleep | sleep(TICK_TTL) |
Prevent tight error loops |
| Continue | continue keyword |
Skip to next iteration without breaking loop |
Error Context:
The warning message includes full context for debugging:
backtestLogicPrivateService tick failed when=${when.toISOString()}
symbol=${symbol} strategyName=${strategyName} exchangeName=${exchangeName}
Retry Behavior:
Date() timestampThe monitoring loop handles each signal state differently, with varying sleep durations and yield behavior. Understanding state-specific behavior is critical for efficient monitoring.
State Behavior Summary:
| State | Yielded? | Sleep After? | Stop Check? | Purpose |
|---|---|---|---|---|
idle |
No | Yes | Before sleep | No signal exists, safe to stop |
scheduled |
No | Yes | No | Waiting for price activation, keep monitoring |
active |
No | Yes | No | Position open, keep monitoring TP/SL |
opened |
Yes | Yes | No | Notify consumer of new position |
closed |
Yes | Yes | After yield | Notify consumer of PNL, allow graceful stop |
cancelled |
Yes | Yes | No | Notify consumer of cancelled scheduled signal |
Yield Behavior:
Only opened, closed, and cancelled results are yielded to the async generator consumer. This means:
Stop Check Timing:
The getStopped() check occurs at strategic points:
The monitoring behavior differs significantly between live and backtest modes. Live monitoring operates on real-time data with sleep intervals, while backtest monitoring fast-forwards through historical data without delays.
| Aspect | Live Monitoring | Backtest Monitoring |
|---|---|---|
| Loop Type | while(true) infinite loop |
while (i < timeframes.length) finite loop |
| Timestamp Source | new Date() real-time |
timeframes[i] historical array |
| Sleep Intervals | sleep(TICK_TTL) between ticks |
No sleep, continuous iteration |
| Timeframe Skipping | No skipping, monitors every minute | Skips to closeTimestamp after signal opens |
| Signal Processing | tick() only, monitors one signal |
tick() for idle, backtest() for opened |
| Persistence | Writes after every tick | No disk I/O during backtest |
| Performance Focus | Minimize latency for real-time response | Maximize throughput for historical analysis |
| Stop Behavior | Breaks loop when idle or closed | Breaks loop at any idle state |
| Event Emission | signalLiveEmitter |
signalBacktestEmitter |
Key Architectural Difference:
Live monitoring prioritizes reliability and real-time response:
Backtest monitoring prioritizes speed and efficiency:
backtest() method