fintech-algorithms

Crossed/Locked Market Detector

Install and import

bash
npm install fintech-algorithms
ts
import { classifyMarkets } from "fintech-algorithms/market-data-engineering/cleaning-and-validation/crossed-locked-market-detector";

Signature

classifyMarkets(quotes, options)

Classifies a quote as normal, locked (bid equals ask) or crossed (bid above ask). Crossed markets are usually a stale or misordered feed rather than a real arbitrage, which is exactly why they must be caught before anything downstream trusts the spread.

Parameters

NameTypeNotes
quotesQuote[]Quotes carrying instrument, market_scope, feed, event_time, receive_time, sequence, bid and ask.
options{ tolerance_ticks?: number; relative_tolerance_ppm?: number }Absolute tolerance in ticks and relative tolerance in parts per million, so a one-tick rounding artefact is not reported as a crossed book.
optional

Returns

Verdict[] · length same-as-input

One classification per quote with the margin by which it locked or crossed. 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 a tolerance 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

quotes
[
  {
    "instrument": "SYNTH",
    "market_scope": "SYNTHETIC_CONSOLIDATED_TOP",
    "feed": "SYNTH-SIP",
    "event_time": "2026-07-20T13:30:00.000Z",
    "receive_time": "2026-07-20T13:30:00.003Z",
    "sequence": 1001,
    "bid_source": "VENUE-A",
    "ask_source": "VENUE-B",
    "bid": 100,
    "ask": 100.02,
    "tick_size": 0.01
  },
  {
    "instrument": "SYNTH",
    "market_scope": "SYNTHETIC_CONSOLIDATED_TOP",
    "feed": "SYNTH-SIP",
    "event_time": "2026-07-20T13:30:00.100Z",
    "receive_time": "2026-07-20T13:30:00.104Z",
    "sequence": 1002,
    "bid_source": "VENUE-A",
    "ask_source": "VENUE-B",
    "bid": 100.01,
    "ask": 100.01,
    "tick_size": 0.01
  },
  {
    "instrument": "SYNTH",
    "market_scope": "SYNTHETIC_CONSOLIDATED_TOP",
    "feed": "SYNTH-SIP",
    "event_time": "2026-07-20T13:30:00.200Z",
    "receive_time": "2026-07-20T13:30:00.207Z",
    "sequence": 1003,
    "bid_source": "VENUE-A",
    "ask_source": "VENUE-C",
    "bid": 100.03,
    "ask": 100.02,
    "tick_size": 0.01
  }
]

Showing 3 of 8 elements.

options
{
  "tolerance_ticks": 0,
  "relative_tolerance_ppm": 0
}

Call

classifyMarkets(quotes, options)

Returns

array of 8 objects

[
  {
    "instrument": "SYNTH",
    "market_scope": "SYNTHETIC_CONSOLIDATED_TOP",
    "feed": "SYNTH-SIP",
    "event_time": "2026-07-20T13:30:00.000Z",
    "receive_time": "2026-07-20T13:30:00.003Z",
    "sequence": 1001,
    "bid_source": "VENUE-A",
    "ask_source": "VENUE-B",
    "bid": 100,
    "ask": 100.02,
    "tick_size": 0.01,
    "index": 0,
    "spread": 0.01999999999999602,
    "spread_ticks": 1.999999999999602
  },
  {
    "instrument": "SYNTH",
    "market_scope": "SYNTHETIC_CONSOLIDATED_TOP",
    "feed": "SYNTH-SIP",
    "event_time": "2026-07-20T13:30:00.100Z",
    "receive_time": "2026-07-20T13:30:00.104Z",
    "sequence": 1002,
    "bid_source": "VENUE-A",
    "ask_source": "VENUE-B",
    "bid": 100.01,
    "ask": 100.01,
    "tick_size": 0.01,
    "index": 1,
    "spread": 0,
    "spread_ticks": 0
  },
  {
    "instrument": "SYNTH",
    "market_scope": "SYNTHETIC_CONSOLIDATED_TOP",
    "feed": "SYNTH-SIP",
    "event_time": "2026-07-20T13:30:00.200Z",
    "receive_time": "2026-07-20T13:30:00.207Z",
    "sequence": 1003,
    "bid_source": "VENUE-A",
    "ask_source": "VENUE-C",
    "bid": 100.03,
    "ask": 100.02,
    "tick_size": 0.01,
    "index": 2,
    "spread": -0.010000000000005116,
    "spread_ticks": -1.0000000000005116
  }
]

Showing 3 of 8 elements.

Diagrams

Crossed/Locked Market Detector — article hero
Crossed/Locked Market Detector — spread geometry

Calculation flow

Geometry-to-review flow
flowchart LR
    A["Snapshot plus scope, feed, and clocks"] --> B{"Context and values valid?"}
    B -->|No| C["Retain INVALID plus reason"]
    B -->|Yes| D["Compute spread, ticks, and explicit boundary"]
    D --> E{"Compare spread with boundary"}
    E -->|Above| F["NORMAL observation"]
    E -->|Within| G["LOCKED observation"]
    E -->|Below| H["CROSSED observation"]
    F --> I["Attach lineage and warnings"]
    G --> I
    H --> I
    I --> J["Separate review or policy layer"]

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.

Read the article →

References