Trade Engine — v1 Spec#
A front-to-back trading system built in Python + SQLAlchemy with a Flask front end. The schema is designed for multiple asset classes (FX, crypto, bonds and equities), but v1 rolls out with one simulated FX pair (``EURUSD``). v1 is the first release of a production-intent system: its scope is deliberately small, but its design (layering, schema, validation, tests) is meant to be built on rather than thrown away.
This file is the overview and the source of truth for scope, architecture and status. Detail lives in the documents below.
Documentation map#
Topic |
Document |
|---|---|
Data tiers, why SQLAlchemy, why four databases |
|
Connection settings, PostgreSQL, running Redis in Docker on Windows |
|
Schema conventions, cross-cutting decisions, indexing and scalability |
|
Reference data schema (instruments, books, traders) |
|
Market data schema (daily prices) |
|
Trade data schema (trades, EOD P&L) |
|
Risk data schema (limits, metrics, breaches) |
|
Extending the schema to FX, crypto, bonds and equities |
|
Schema changes required before real use |
|
Why and how data is simulated |
|
P&L method and the EOD batch |
|
Risk calculations (notional, VaR, limits) |
|
Reporting (the read side) |
|
Web layer (Flask API) |
|
How the repo, environment and GitHub repository were set up (GitHub CLI, venv) |
|
How to build, view and edit these docs (Sphinx, Book theme) |
Quality bar#
“Not a prototype” means every number can be explained, reproduced and traced. Status uses Implemented (in the code today) and Required (must be done before the system is relied on for real positions; see the roadmap and Known limitations).
Concern |
Commitment |
Status |
|---|---|---|
Correctness |
Formulas documented and unit-tested in isolation ( |
Implemented |
Correctness |
Money and quantities stored and computed as exact decimals |
Required |
Data labelling |
Every price row is explicitly |
Implemented |
Data labelling |
The origin label propagates to derived results (P&L, risk) |
Required |
Reproducibility |
EOD batches are idempotent per |
Implemented |
Reproducibility |
Every EOD run recorded (inputs, status) and re-creatable as-of a date |
Required |
Auditability |
Trades append-only; amendments and cancels are new records with who, when and why |
Required |
Data integrity |
Input validated at the boundary (CSV, requests, CLI) |
Implemented |
Data integrity |
Invariants enforced by the database, not only by Python |
Partly implemented (asset class, price multiplier, data origin) |
Security |
Web CSV loads restricted to |
Implemented |
Security |
Authentication and authorization, CSRF, TLS, secrets management |
Required |
Operability |
Logging; per-database connection strings from the environment |
Implemented |
Operability |
Scheduler with run locking, alerting, backups with tested restores, CI |
Required |
Change control |
Versioned schema migrations (Alembic) |
Required |
Scope (v1)#
Asset: one simulated FX pair, configurable (
EURUSDby default). The schema can represent FX, crypto, bonds and equities (Multi-asset design); only FX is implemented and seeded.Market data: daily OHLC loaded from CSV, staged raw in Redis, then validated and committed to the market database, and labelled
SIMULATEDorOBSERVEDat load time. The shippeddata/raw/EURUSD.csvis simulated (Simulated data: why and how). No live feed.Trade entry: manual, via the Flask API (you act as trader and booker).
P&L: end-of-day batch, weighted-average cost, realized plus unrealized, in the instrument’s quote currency (P&L and the EOD batch).
Risk: position and notional limits plus simple historical 1-day 95% VaR; breaches are logged. USD-quoted instruments only (Risk calculations).
Blotter: not a separate database; a reporting query over the trade database with reference data joined in (Reporting).
Architecture#
web / scripts -> services -> repositories -> models / db
|
+------> domain (pure logic: P&L, risk, validation)
Layer |
Package |
Responsibility |
|---|---|---|
Entry points |
|
Parse input, call services, present output. No business rules. |
Services |
|
Use cases: orchestrate repositories and domain logic across the databases. |
Domain |
|
Pure logic with no database or Flask imports; unit-tested directly. |
Repositories |
|
Data access, one per database. |
Persistence |
|
One engine and declarative base per database; SQLAlchemy models. |
Wiring |
|
Settings from the environment; composition root. No import-time side effects. |
Data and databases#
Market data moves through three stores, and data only flows forward:
Redis stages raw, unvalidated CSV batches.
PostgreSQL (SQLite files by default for local development and tests) holds the validated, typed, provenance-labelled data in four logical databases: reference, market, trade and risk.
DuckDB holds a read-only analytics snapshot exported from the clean data.
See Data architecture for the tiers and the reasons for the design, and Setup: configuration, PostgreSQL and Redis for configuration, PostgreSQL and running Redis in Docker on Windows.
Project layout#
Installable application code:
src/trade_engine/(domain/,db/,models/,repositories/,services/,staging/,analytics/,web/,quant/).Executable batch jobs and the Flask launcher:
scripts/.Original input files:
data/raw/; generated data:data/generated/. Note that the shippeddata/raw/EURUSD.csvis itself simulated.SQLite and DuckDB files:
db/(not in source control).PostgreSQL and Redis setup:
docker-compose.yml,docker-compose.redis.ymlanddocker/postgres/.Documentation:
docs/, with this file as the entry point.Tests:
tests/.
Batch jobs (scripts/)#
init_db.py: create all tables across the four databases and seed one instrument, book, trader and limit.load_market_data.py: stage a price CSV in Redis and load it into the market database;--origin OBSERVED|SIMULATEDis required.--stage-onlystops after staging.process_staging.py: validate and commit staged batches (one pass, or--watch SECONDSto keep polling); invalid batches go to the rejected stream.export_analytics.py: rebuild the DuckDB analytics file from the clean databases.seed_sample_trades.py: insert sample trades once.run_eod.py: compute and persist P&L and risk for a given (or the latest) date.run_app.py: start the Flask development server on localhost.
Known limitations (v1)#
Deliberate scope limits, recorded as decisions rather than shortcuts:
Only FX is implemented and seeded, with a single simulated pair. The schema supports crypto, bonds and equities, but their subtype tables, fees, cash flows and corporate actions are not built.
No currency conversion: P&L is per currency, and risk accepts USD-quoted instruments only.
Weighted-average-cost P&L only; no FIFO or lot-level and no intraday P&L.
Daily CSV prices only; no real-time or streaming market data.
One price series per symbol with an unstated basis. Bid, ask and mid are not stored separately, so fills, liquidation-value marks and spread costs cannot be modelled yet. The design is in Market database (market.db) (roadmap item 15).
Redis staging covers market data only (trades are written directly), uses a single consumer, and has no tooling to replay dead-lettered batches yet.
The analytics export is a full refresh, not incremental, and the DuckDB file allows only one writer at a time.
Single-asset historical VaR only; no parametric or Monte Carlo VaR, stress testing or VaR backtesting.
No authentication or authorization on the Flask app; JSON only, no HTML views.
No pre-trade limit checks.
Schema managed with
create_all; see the roadmap for migrations and the production-required schema changes.