London DataTrading systems technology

Nexus engineering · Deep dives

Database design &
data management.

A market-data store has to preserve what a value means, where it came from and when it could have been known. Nexus combines relational modelling, controlled schema changes, time-series storage and recovery procedures to make that data usable by research and trading.

Model the factsVersion the changesPreserve the historyProve recovery

Two access paths. One database technology.

The core platform uses hand-written SQL. Hermes, the alert service, uses SQLAlchemy's object-relational mapper and has its own PostgreSQL deployment.

Core platform

Data, research & trading services

Hand-written SQL · psycopg2

Queries, bulk writes and explicit transaction boundaries.

SQL sent through the database driver

PostgreSQL

Core databases

market_data / database

marketPrice bars in time-series tables
TimescaleDB extensionHypertables · chunks · compression
referenceInstruments & calendars
eventsEarnings, filings & news
opsWork board & deployment records

Separate databases hold service state:

nexus_sim · researchnexus_trader · executionnexus_pms · portfoliosnexus_risk · risk

Each service connects with its own database role and permissions.

Alert service

Hermes

SQLAlchemy ORM

Python models, relationships and sessions.

ORM-generated SQL · psycopg 3

PostgreSQL

Hermes store
  • messagesAccepted alert requests
  • deliveriesAttempts and delivery state
  • outboxWork waiting to be sent

Hermes owns this separate store. Its ORM models also support SQLite for local use.

Direct SQL keeps bulk upserts, time-series functions and locking clauses visible in the data path. Hermes's ORM models suit messages and their related deliveries, and support SQLite for local use as well as PostgreSQL. SQLAlchemy can also run SQL without the ORM; the choice is about how each service expresses its work.

Schemas give the data a home.

A database contains schemas, and a schema contains tables. These are four examples within market_data; nexus_sim and nexus_trader are separate databases. Schemas group related objects, while database grants control access.

SchemaWhat it holds
marketPrice bars across providers and frequencies, including TimescaleDB hypertables such as market.prices_1min.
referenceInstruments, trading calendars, corporate actions, futures specifications and roll rules.
eventsEarnings calendars and results, filings, news and ratings changes.
opsOperational records, including agent work items, leases and deployment history.
Identity & integrity

Define what makes a fact unique.

A price bar's identity includes its ticker, timestamp, price side, provider and feed. Conflict rules determine whether a repeat is ignored or an allowed update. Foreign keys connect datasets, cleaning operations and derived series; constraints protect the meaning of a row.

Meaning & ownership

Keep units and provenance attached.

Currency, price convention, provider and calendar matter as much as the numeric value. Services write through their interfaces or allow-listed jobs with scoped database roles. A schema groups objects; permissions decide who can change them.

03 · Change management

Alembic records how the database evolves.

The data service's migrations are hand-written and reviewed with the application code. Each revision names its predecessor; Alembic compares that history with the revision recorded in the database and applies the pending changes.

revision Arevision Brevision CCode carries the change history.
The database records its applied revision.
  1. 1

    Compare

    Read the installed database revision and the incoming revision graph. A deploy with no pending migration skips the snapshot requirement.

  2. 2

    Establish recovery

    For a migration-bearing update of the existing market store, the data-api preflight requires a recent pre-migration backup artifact before proceeding.

  3. 3

    Apply at deploy

    Run the pending revisions through Alembic and SQLAlchemy's database engine. Schema changes run in the deployment path, outside normal API requests.

  4. 4

    Check and serve

    Record the applied revision, restart the service through the deploy process and check its health against the new schema.

Bound the disruption

A lock wait is an operational decision.

The data service applies a 30s default DDL lock timeout from connection establishment. A waiting schema change can otherwise queue later readers behind it. This bounds the wait to acquire a lock; it is not a thirty-second limit on the migration's execution.

Separate histories

Each component owns its revisions.

The market-data service has its own revision chain. Research components, including the simulation ledger, audit log, job queue and model registry, use separate Alembic version tables. Their deployment applies the chains in order and stops if one fails.

A concrete example: the minute-bar migration widened future chunks from daily to monthly intervals. It explicitly left existing chunks alone; reorganising old history is a separate maintenance operation. Alembic versions that decision, while a backup protects the data. A downgrade() function alone cannot recover deleted rows.

Where SQLAlchemy fits in the migration path

The data service's Alembic environment uses a SQLAlchemy engine with the psycopg2 driver and target_metadata=None. Migrations express deliberate SQL changes rather than deriving the production schema automatically from ORM models. The snapshot and lock controls described here are the data service's controls; each service has its own migration setup.

04 · Historical knowledge

Keep when it applied separate from when it was known.

A contract specification can be announced weeks before it takes effect, or corrected after the fact. A historical query needs both an effective date and a knowledge cutoff. Reproducing what Nexus itself had captured adds a third clock.

Effective time

When was it true?

valid_from · valid_to

The interval in which the fact applies. The end is exclusive; an open end is stored explicitly.

Source knowledge

When was it knowable?

published_at · available_at

Publication and availability are recorded separately. The as-known query bounds available_at.

Captured vintage

When did we have it?

observed_at

An independent cutoff can restrict the result to vintages Nexus had actually captured by then.

Explore the query · a documented market event

Russell 2000: $100 becomes $50 per index point.

ICE Futures U.S. · Russell 2000 futures · symbol TF. ICE announced the multiplier reduction on 21 October 2016, effective for trade date 5 December 2016. The same index-point move then had half the dollar value per contract.

Russell 2000 is in Nexus's enabled IG market universe as RTY=F. This event concerns the historical ICE contract. CME's separate RTY contract launched in July 2017 with a $50 multiplier; these exchange multipliers do not set IG's stake per point.

Scroll the table to compare the terms and their effective dates →

TermsUSD / index pointApplies to trade datesNotice dateFor this query
Before conversion$100Before 5 Dec 201621 Oct 2016Selected
After conversion$50From 5 Dec 201621 Oct 2016Outside effective date
ICE TF · effective 2 Dec 2016 · known by 5 Dec 2016$100 / index point

The 2 December trade date still uses $100 per index point, even when viewed after the conversion. Applying the later $50 multiplier would halve the historical per-contract exposure incorrectly.

Existing positions were doubled at conversion: one old contract became two new contracts, preserving the exposure at a given index level. Position history and specification history therefore have to agree.

Sources: ICE announcement, 21 October 2016; clearing conversion notice, 26 October 2016; rule filing, 8 November 2016; CME RTY launch notice, July 2017.

This example uses the terms in the dated ICE announcement. Before that notice, it has no eligible evidence; it does not infer what earlier sources would have said. The announcement was subject to regulatory review. The controls compare dates before or after publication, without inventing an intraday release time. No historical Nexus capture timestamp is evidenced here, so a captured-vintage replay remains unproven.

Versioned facts

Append a correction. Preserve the prior fact.

The futures-specification structure records source documents, document and content hashes, revision sequence and the previous revision's identifier. A database trigger rejects updates and deletes. Correcting a fact creates a new revision with its own provenance.

Constrained reads

Select eligible revisions, then resolve them.

The reader filters by effective time and knowledge time, with an optional captured-vintage cutoff. It selects the latest eligible revision for each sourced interval and reports missing coverage, overlap, deletion, poor quality or disagreement instead of silently substituting today's value.

How the production reader filters historical records
valid_from <= :as_of
AND (valid_to IS NULL OR :as_of < valid_to)
AND available_at <= :known_at
AND observed_at <= :vintage_at  -- when replaying captured knowledge

Filtering is followed by revision selection and coverage checks. “Latest” is evaluated within the eligible history, not across every revision now in the database.

05 · Data management

A clean series should be explainable and rebuildable.

The cleaning model preserves the source bars and records how a usable series was produced. Source selection, validation, correction and derivation are distinct operations with their own evidence.

  1. 1

    Identify the source

    Keep provider, feed, ticker, frequency and price side attached to each dataset. Repeated ingestion follows the table's explicit conflict rules.

  2. 2

    Record the operation

    Cleaning operations retain parameters, affected counts and before/after statistics. Quarantine links excluded bars to the operation that excluded them.

  3. 3

    Rebuild the clean layer

    Materialised clean tables are derived from raw data and the recorded operations. Corrections and exclusions can be reversed through the cleaning workflow.

  4. 4

    Trace the derivation

    Registered lineage connects child datasets to their parents and resampling rules, so downstream series can be regenerated when their inputs change.

Auditability

Make a change answerable.

Which source supplied the bar? Which operation excluded or corrected it? Which derived series depends on it? Dataset identities, operation records, quarantine and lineage give those questions a place in the data model.

Historical semantics

A timestamp needs a defined meaning.

A row's ingest timestamp is not automatically its publication time or a complete revision history. An append, an in-place update and a rebuilt series can give the same timestamp column different meanings. Historical consumers must use the semantics of their actual writer.

The deeper data-quality workflow is covered in Validate and clean; instrument identity, calendars and corporate actions are covered in Reference data.

06 · Physical design

Chunk for the workload. Compress by the series.

TimescaleDB keeps time-series tables inside PostgreSQL. The physical design uses different time intervals for different bar densities, with compression grouped around the series that queries read.

1.28 billion price barsFrom one-second capture to daily history
105 GB on diskThe whole market-data database
10.7 times overallCompression of the compressed price chunks

Scale figures measured on 28 October 2026 and maintained in the site's figure register. Bid, mid and offer are separate bars. The database footprint and compressed-chunk ratio measure different scopes.

Price tableChunk intervalDesign consideration
prices_1sec1 dayDense live capture gets a shorter time partition.
prices_1min30 daysDeep minute history needs fewer partitions than daily chunks would create.
prices_hourly90 daysCoarser bars fit a longer span per chunk.
prices_daily365 daysLong history stays in a manageable number of partitions.
Compression policy

Group by provider and ticker.

The inspected price tables segment compressed data by provider_code, ticker and order it by ts DESC. Their compression policy uses a seven-day threshold. Eligibility is based on the chunk's time range, so a bar in a still-recent chunk is not necessarily compressed on its eighth day.

Query & write discipline

Keep the touched data bounded.

Time predicates help exclude irrelevant chunks. Too many small chunks increase catalogue and lock overhead; broad updates of compressed history can require substantial decompression. Backfills and repairs use bounded ranges and transaction sizes appropriate to the dataset.

Chunk intervals and compression keys above were checked in the production catalogue on 2 November 2026. Changing a chunk interval affects new chunks; existing history needs an explicit maintenance plan. Compression saves space while retaining data, and is separate from any decision to archive or delete it.

07 · Recovery

Protect the copy. Exercise the restore.

The backup service takes online PostgreSQL dumps with daily, weekly and monthly retention, with more frequent protection for selected trading and capture data. Copies are shipped offsite and checked there; scheduled restores exercise the path back into a database.

  1. 1

    Capture

    Write timestamped backup artifacts and record their outcome. A failed job reports through the central alert path.

  2. 2

    Verify the copy

    Record checksums and compare the offsite bytes with the shipped artifacts. Integrity checks answer whether the copy survived transfer.

  3. 3

    Restore in isolation

    A weekly database smoke restore and a monthly full TimescaleDB restore use scratch targets. The Timescale path pins the extension version and runs its pre- and post-restore hooks.

  4. 4

    Inspect the result

    The Timescale drill compares hypertable and chunk inventories with the source. These structural checks provide evidence beyond a successful file copy or process exit.

Extension-aware recovery

A hypertable's rows live in its chunks.

A dump selecting only the parent table can omit the rows held in child chunks. The market-store recovery procedure therefore restores the full database through the Timescale-aware path and checks the restored inventory.

State the scope of the proof

Integrity, structure and service are different checks.

A checksum verifies bytes. A restore verifies that the artifact can be loaded; an inventory checks its structure. Application queries, roles and permissions still need checking before a recovered service is ready to run.

Historical as-of queries reconstruct knowledge from stored versions. Disaster recovery reconstructs a database from backups; its recovery granularity follows the available backup artifacts. The operating schedule and retention policy are detailed in Production: backups.