Stale-Quote Detector
Install and import
npm install fintech-algorithmsimport { detectStaleQuotes } from "fintech-algorithms/market-data-engineering/cleaning-and-validation/stale-quote-detector";Signature
detectStaleQuotes(events, config)Separates the several distinct ways a quote can be stale: old at the source, delayed in transport, unchanged for too long, or arriving after a missed heartbeat. They have different causes and different remedies, so they are reported separately.
Parameters
| Name | Type | Notes |
|---|---|---|
events | QuoteEvent[] | Quote and heartbeat events with both source_event_ts and observed_ts, which is what makes source age and transport age separable. |
config | { max_source_event_age_ms: number; max_transport_age_ms: number; unchanged_threshold_ms: number; heartbeat_timeout_ms: number } | One budget per failure mode: how old the venue's own timestamp may be, how long transport may take, how long a quote may sit unchanged, and how long a heartbeat may be missing. |
Returns
Verdict[] · length same-as-input
One verdict per event naming which budget was breached. Returns one verdict per input row rather than throwing, so a single bad record cannot abort the batch — and cannot pass unnoticed either.
Errors
- When any threshold is negative — throws
Complexity: time O(n),
space O(n).
Worked example
executed Captured by running this function on the input its own test provides. Real output of real code — but not asserted against a published figure.
Input
[
{
"event_id": "E01",
"kind": "quote",
"source_event_ts": "2026-07-22T09:30:00.000Z",
"observed_ts": "2026-07-22T09:30:00.100Z",
"bid": 100,
"ask": 100.02,
"clock_sync_ok": true,
"session_id": "S1",
"session_state": "ACTIVE",
"activity_expected": true
},
{
"event_id": "E02",
"kind": "heartbeat",
"observed_ts": "2026-07-22T09:30:00.900Z",
"session_id": "S1",
"session_state": "ACTIVE",
"activity_expected": true
},
{
"event_id": "E03",
"kind": "check",
"observed_ts": "2026-07-22T09:30:01.200Z",
"session_id": "S1",
"session_state": "ACTIVE",
"activity_expected": true
}
]Showing 3 of 12 elements.
{
"max_source_event_age_ms": 1200,
"max_transport_age_ms": 250,
"unchanged_threshold_ms": 3000,
"heartbeat_timeout_ms": 1200
}Call
detectStaleQuotes(events, config)Returns
array of 12 objects
[
{
"event_id": "E01",
"kind": "quote",
"source_event_ts": "2026-07-22T09:30:00.000Z",
"observed_ts": "2026-07-22T09:30:00.100Z",
"bid": 100,
"ask": 100.02,
"clock_sync_ok": true,
"session_id": "S1",
"session_state": "ACTIVE",
"activity_expected": true,
"source_event_age_ms": 100,
"last_transport_age_ms": 100,
"unchanged_duration_ms": 0,
"heartbeat_age_ms": 0
},
{
"event_id": "E02",
"kind": "heartbeat",
"observed_ts": "2026-07-22T09:30:00.900Z",
"session_id": "S1",
"session_state": "ACTIVE",
"activity_expected": true,
"source_event_age_ms": 900,
"last_transport_age_ms": 100,
"unchanged_duration_ms": 800,
"heartbeat_age_ms": 0,
"source_event_age_exceeded": false,
"transport_late": false,
"unchanged_threshold_reached": false,
"heartbeat_lost": false
},
{
"event_id": "E03",
"kind": "check",
"observed_ts": "2026-07-22T09:30:01.200Z",
"session_id": "S1",
"session_state": "ACTIVE",
"activity_expected": true,
"source_event_age_ms": 1200,
"last_transport_age_ms": 100,
"unchanged_duration_ms": 1100,
"heartbeat_age_ms": 300,
"source_event_age_exceeded": false,
"transport_late": false,
"unchanged_threshold_reached": false,
"heartbeat_lost": false
}
]Showing 3 of 12 elements.
Diagrams
Calculation flow
Validation-before-mutation flow
flowchart TD
A["Quote, heartbeat, or check"] --> B{"Schema and receiver order valid?"}
B -->|No| R["Reject and preserve state"]
B -->|Yes| C{"Quote event?"}
C -->|Yes| D{"Clock sync verified, age nonnegative, source order valid?"}
D -->|No| R
D -->|Yes| E["Apply session reset if ID changed"]
C -->|No| E
E --> F["Quote or heartbeat updates last-message receipt"]
F --> G["Only changed quote updates last-change receipt"]
G --> H["Compute source, transport, unchanged, and heartbeat ages"]
H --> I["Apply active-session business policy"]
How it works
This page states the contract — how to call it correctly. The article explains the concept: why it works, and where it breaks.
References
- Nasdaq TotalView-ITCH 5.0 Specification — Nasdaq
- SoupBinTCP 4.0 — Nasdaq
- Post Mortem: NASDAQ Trading Halted in Tape C Securities — Nasdaq OMX
- UTP SIP Issue on Thursday, August 22, 2013 — UTP SIP / Nasdaq OMX
- Regulation Systems Compliance and Integrity — U.S. Securities and Exchange Commission
- Clock Synchronization — Financial Industry Regulatory Authority