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.
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.
Schema
What it holds
market
Price bars across providers and frequencies, including TimescaleDB hypertables such as market.prices_1min.
reference
Instruments, trading calendars, corporate actions, futures specifications and roll rules.
events
Earnings calendars and results, filings, news and ratings changes.
ops
Operational 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 A→revision B→revision CCode carries the change history. The database records its applied revision.
1
Compare
Read the installed database revision and the incoming revision graph. A deploy with no pending migration skips the snapshot requirement.
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
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
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 →
Terms
USD / index point
Applies to trade dates
Notice date
For this query
Before conversion
$100
Before 5 Dec 2016
21 Oct 2016
Selected
After conversion
$50
From 5 Dec 2016
21 Oct 2016
Outside 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.
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
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
Record the operation
Cleaning operations retain parameters, affected counts and before/after statistics. Quarantine links excluded bars to the operation that excluded them.
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
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 table
Chunk interval
Design consideration
prices_1sec
1 day
Dense live capture gets a shorter time partition.
prices_1min
30 days
Deep minute history needs fewer partitions than daily chunks would create.
prices_hourly
90 days
Coarser bars fit a longer span per chunk.
prices_daily
365 days
Long 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
Capture
Write timestamped backup artifacts and record their outcome. A failed job reports through the central alert path.
2
Verify the copy
Record checksums and compare the offsite bytes with the shipped artifacts. Integrity checks answer whether the copy survived transfer.
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
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.