π Comprehensive Platform Documentation & Architectural Specifications are published on the Emporia GitHub Wiki!
Emporia is an enterprise-grade, distributed stock trading platform built with Java 21, Spring Boot 4.0.7, React 19, Apache Kafka, gRPC, and PostgreSQL. Its deployable boundaries strictly follow business capabilities: static data, user preferences, market data, order command handling, and order management are independent services, with execution routing isolated behind its own service.
For in-depth architectural guides, domain design patterns, microservice deep dives, and trading business logic formulas, explore the Emporia GitHub Wiki:
| Section | Description |
|---|---|
| π Trading Terminology Glossary | Financial terms: Order types (Limit, Market, Stop, TIF), BBO/NBBO, SOR, VWAP, P&L. |
| π Architecture & Order Flow | System architecture flow, port matrix, REST vs. Kafka EDA, database ownership. |
| π¨ Order Command Service | REST intake, listing snapshot validation, KafkaCommandGateway result correlation. |
| βοΈ Order Management Service | State machine authority, OrderCommandHandler, ExecutionCommandHandler, idempotency. |
| π― Execution Service | Algorithmic routing (DMA, SMART NBBO selector, VWAP slicer), venue gateways. |
| π Order Lifecycle & Invariants | State machine invariants, tick/lot size checks, late fill accounting. |
| π§ Order Routing & Execution | Smart Order Routing (SOR) venue splitting & VWAP time-slicing logic. |
| π Market Data & Pricing | L1/L2 order books, Price-Time priority matching, micro-price formulas. |
| πΌ Portfolio & Risk Controls | Long/short positions, cost basis, Mark-to-Market P&L, fat-finger price collars. |
| π§© Design Patterns Catalog | CQRS, Event-Driven Architecture, Saga, Strategy, State Machine patterns. |
| π¦ Microservices Overview | Deep-dive into all 9 microservices, Gateway, and OAuth2 Authorization. |
| β‘ Exchange-Core Integration | Ultra-low latency LMAX Disruptor ring-buffer matching engine integration. |
| π§ͺ Testing & Verification | 91.95% JaCoCo coverage, Testcontainers PostgreSQL specs, Fray concurrency tests. |
| π οΈ Deployment & Operations | Environment prerequisites, Docker Compose, Maven builds, React UI startup. |
flowchart TD
Browser[React :3001] -->|OIDC + PKCE / Bearer token| Gateway[Spring Cloud Gateway :8082]
Gateway --> Auth[Authentication :9000]
Gateway --> Static[Static data :8081]
Gateway --> Preferences[User preferences :8083]
Gateway --> Market[Market data :8084]
Gateway --> OrderCommands[Order command service :8085]
Gateway --> Orders[Order management :8086]
Execution[Execution service :8087] -->|client credentials| Auth
ExchangeCore[exchange-core simulation] -->|risk seed + durable snapshots| Portfolio[Portfolio service :8088]
ExchangeCore -->|bearer token| Auth
Preferences -->|listing snapshots| Static
Market -->|listing snapshots| Static
Market -->|client credentials| Auth
Fix[FIX simulator gRPC sources] -->|incremental books| Market
Alpaca[Alpaca IEX] -->|snapshot + WebSocket| Market
OrderCommands -->|validate listing| Static
OrderCommands -->|CREATE / MODIFY / CANCEL / CANCEL_ALL| Commands[[emporia.order.commands.v1]]
Commands --> Orders
Orders -->|correlated outcome| Results[[emporia.order.results.v1]]
Results --> OrderCommands
Orders -->|immutable state events| OrderLog[[emporia.orders.v1]]
OrderLog --> Execution
Execution -->|SMART/VWAP child CREATE| Commands
Execution -->|FILL / REJECT / venue CANCEL| ExecutionCommands[[emporia.execution.commands.v1]]
ExecutionCommands --> Orders
Execution -->|recover active parents and children| Orders
Execution -->|same-instrument listings| Static
Execution -->|venue quotes| Market
Auth --> AuthDb[(PostgreSQL\nemporia_authentication)]
Static --> StaticDb[(PostgreSQL\nemporia_static_data)]
Preferences --> PreferencesDb[(PostgreSQL\nemporia_client_config)]
Orders --> OrderDb[(PostgreSQL\nemporia_order_data)]
Execution --> ExecutionDb[(PostgreSQL\nemporia_execution)]
Portfolio --> PortfolioDb[(PostgreSQL\nemporia_portfolio)]
The browser sees one /api surface. The gateway routes requests by path and
HTTP method to the service that owns each business capability.
| Directory | Port | Owns |
|---|---|---|
authentication |
9000 | OAuth2/OIDC login, users, tokens |
static-data-service |
8081 | Instruments and exchange listings |
user-preferences-service |
8083 | Per-user watchlists and persisted workspace layouts |
market-data-service |
8084 HTTP / 50551 gRPC | Simulated, Alpaca IEX, or FIX-simulator market data; venue/composite books; REST, SSE, and gRPC distribution |
order-command-service |
8085 | Authenticated create, modify, cancel, and cancel-all command boundary |
order-management-service |
8086 | Order lifecycle, state, history, executions, and command idempotency |
execution-service |
8087 internal | DMA venue access, best-venue SMART routing, scheduled VWAP child orders, and execution reports |
portfolio-service |
8088 internal | Fully funded cash/equity balances and idempotent exchange snapshot receipts |
gateway |
8082 | Browser security boundary and routing |
frontend |
3001 | React trading workspace |
trading-contracts |
not deployed | Versioned Java/Kafka contracts shared at build time |
fix-simulator-contracts |
not deployed | Generated FIX-simulator protobuf/gRPC contracts, consumed by market-data-service and fix-market-simulator |
fix-market-simulator |
9876 FIX / 50051 gRPC / 8501 REST | Standalone FIX/gRPC market simulator (QuickFIX/J + Guice + Jetty), an optional data source for market-data-service's FIX_SIMULATOR_CONNECTIONS mode |
No running service reads or writes another Emporia service's PostgreSQL database
or schema.
When a service needs listing data, it calls static-data-service and forwards
a bearer token. Orders store an immutable listing snapshot instead of a
cross-schema foreign key.
order-command-servicecreates a versioned command with a uniquecommandIdand publishes it toemporia.order.commands.v1.order-management-serviceconsumes the command, validates the transition, and updates its PostgreSQL projection in a transaction.- The command result is stored in
processed_order_command. A redelivered Kafka command therefore returns the same result instead of applying the change twice. order-management-servicepublishes the state transition toemporia.orders.v1and a correlated response toemporia.order.results.v1.order-command-servicecompletes the waiting browser request. If Kafka or the order processor does not answer within eight seconds, it returns a timeout or service error rather than pretending the order succeeded.
Commands are keyed by order ID (or user subject for cancel-all). The six Kafka partitions can process independent orders in parallel while maintaining the order of commands for one key.
- DMA sends the order directly to the configured venue gateway. Local development uses a deterministic delayed-fill gateway.
- SMART loads every listing for the instrument and walks executable opposite-side depth in price/time order. It creates deterministic DMA children across venues, observes the parent limit, and waits/retries when liquidity is temporarily unavailable.
- VWAP supports absolute start/end seconds and a configurable bucket count. It emits increment-aligned catch-up children from the persisted cumulative target, parent fills, and active child exposure.
- Child partial and final fills roll up to every parent ancestor atomically with weighted-average fill accounting.
- Cancellation is venue-confirmed. A command records a pending
targetStatus=CANCELLED; the venue acknowledgement finalizes it, while a racing execution remains valid. EXECUTION_VENUE_MODE=fixenables the built-in FIXT 1.1 / FIX 5.0 SP2 source adapter for new, modify, cancel, and execution-report messages.- New execution consumers start at the latest order event, so introducing the service cannot accidentally execute retained historical orders.
- On restart, execution rebuilds direct-order and SMART/VWAP runtimes from the order-management PostgreSQL projection rather than replaying creates.
See the DMA, SMART, and VWAP execution guide for strategy behavior, order examples, cancellation, recovery, and current boundaries. Service-level configuration is collected in execution-service/README.md.
- Java 21 or newer
- Maven 3.9+
- Node.js and npm
- PostgreSQL running at
localhost:5432for non-Docker local runs - Docker with Compose for Docker-managed infrastructure or full-stack deployment
- Exchange-Core Engine: Clone and install
exchange-core(mvn clean install) into your local Maven repository before building Emporia.
Non-Docker local PostgreSQL settings:
- Database:
emporia - Username: your OS username by default (the role Homebrew's
postgresqlformula creates), notpostgresβscripts/run-local.shandscripts/seed-portfolio-client.shdefaultDB_USERNAMEaccordingly; overrideDB_USERNAMEif your local Postgres uses a different role - Password:
admin123
Flyway creates these service-owned schemas in the local emporia database:
emporia_authentication, emporia_static_data, emporia_client_config,
emporia_order_data, emporia_execution, and emporia_portfolio.
| Mode | Spring services run in | PostgreSQL runs in | PostgreSQL layout |
|---|---|---|---|
| Local | Host JVM | Local PostgreSQL on localhost:5432 |
One emporia database with service-owned schemas |
| Infrastructure-only Docker | Host JVM | Docker containers exposed on 5433-5438 |
One PostgreSQL database/container per service that owns persistent data |
| Full Docker | Docker containers | Docker containers | One PostgreSQL database/container per service that owns persistent data |
See Start locally below for how each mode is brought up.
scripts/run-local.sh (Mode 1) and scripts/run-infra-docker.sh (Mode 2)
build the reactor, start every service in the background, wait for each
/actuator/health to report up, and print the frontend URL. This is the
supported way to bring up the stack β don't start services one-by-one by
hand. Full Docker mode already has scripts/local-deploy.sh and the
compose commands in the Docker Deployment section below.
Run all commands from the repository root.
-
Clone and install the
exchange-coredependency into your local Maven repository:git clone https://github.com/nvxtien/exchange-core.git cd exchange-core mvn clean install cd ..
-
Start the stack. For Mode 1 (Local), first confirm the shared non-Docker PostgreSQL database is running on
localhost:5432(see Local prerequisites above):scripts/run-local.sh
For Mode 2 (Infrastructure-only Docker), no local PostgreSQL is needed β the script starts the per-service containers itself:
scripts/run-infra-docker.sh
-
Open
http://localhost:3001and sign in withadmin/admin123once the script prints "stack is up".
Both scripts default execution-service to EXECUTION_VENUE_MODE=exchange-core
with EXCHANGE_CORE_ACCOUNTING_MODE=full-equity-risk, and automatically seed
a USD portfolio balance for the bootstrap admin so it can receive an
exchange-core risk seed. market-data-service defaults to
MARKET_DATA_PROVIDER=simulated; export MARKET_DATA_PROVIDER=alpaca-iex
plus APCA_API_KEY_ID/APCA_API_SECRET_KEY before running either script to
use live Alpaca IEX data instead:
MARKET_DATA_PROVIDER=alpaca-iex \
APCA_API_KEY_ID='your-alpaca-key-id' \
APCA_API_SECRET_KEY='your-alpaca-secret-key' \
scripts/run-local.shπ Registering Alpaca API Credentials:
- Sign up for a free account at alpaca.markets.
- Open the Paper Trading dashboard (free sandbox).
- Click Generate New API Key in the right-hand panel.
- Copy your API Key ID (
APCA_API_KEY_ID) and Secret Key (APCA_API_SECRET_KEY).
To instead consume incremental order books from one or more FIX simulator
gRPC sources, export MARKET_DATA_PROVIDER=fix-simulator and
FIX_SIMULATOR_CONNECTIONS; see the
market-data service runbook for the
connection string format and behavior.
Each script logs every service to .local-run/logs/<service>.log and tracks
its pid in .local-run/pids/<service>.pid.
Health check every service (all default to GET /actuator/health without a
token):
for pair in "authentication:9000" "static-data-service:8081" "user-preferences-service:8083" \
"market-data-service:8084" "order-command-service:8085" "order-management-service:8086" \
"execution-service:8087" "portfolio-service:8088" "gateway:8082"; do
name="${pair%%:*}"; port="${pair##*:}"
echo "$name: $(curl -fsS http://localhost:$port/actuator/health)"
done
curl -fsS -o /dev/null -w 'frontend: %{http_code}\n' http://localhost:3001Other useful checks:
cat .local-run/pids/*.pid # pid recorded per running service
tail -f .local-run/logs/execution-service.log # live logs for one service
docker compose ps kafka # Kafka container healthStop everything either script started, including the Kafka and/or per-service PostgreSQL containers:
scripts/stop-services.shIf you create additional trading users after the stack is up (e.g. through the admin user-management UI), seed their exchange-core portfolio balance the same way the bootstrap admin is seeded:
scripts/seed-portfolio-client.sh <username>Running one service by hand (for example under a debugger) is still
supported. Kafka must already be running (docker compose up -d kafka), and
authentication should start before any service that validates its own OAuth
tokens. Each service's own README documents its environment variables and
mvn spring-boot:run / npm run dev command:
authentication,
static-data-service,
user-preferences-service,
market-data-service,
order-command-service,
order-management-service,
execution-service,
portfolio-service,
gateway, and frontend.
Every service supports GET /actuator/health without a token. Kafka is healthy
when docker compose ps kafka reports healthy.
The full verification runbook lives in the
Testing & Verification wiki.
It covers Maven verify, PMD reports, frontend checks, property tests,
PostgreSQL integration tests, Fray concurrency checks, TLA+ model checking, and
the OIDC/Kafka smoke test.
Run scripts/install-git-hooks.sh once per clone to enable local CI: a
pre-push hook that runs the same mvn verify + frontend checks before code
leaves your machine. See docs/CI_CD.md for what it checks
and the on-demand local deploy script.
In addition to running services locally on your host machine, Emporia supports containerized deployment with Docker and Docker Compose. As with Start locally above, use the provided scripts rather than starting containers or services by hand.
This is Mode 2 from Start locally: scripts/run-infra-docker.sh
starts one PostgreSQL 16 container per service that owns persistent data
(ports 5433-5438) plus Apache Kafka 4.3.1 (9092), then runs every Spring
Boot service and the frontend on your host JVM against those containers.
| Service | Host port | Database | Schema |
|---|---|---|---|
authentication |
5433 |
emporia_authentication |
emporia_authentication |
static-data-service |
5434 |
emporia_static_data |
emporia_static_data |
user-preferences-service |
5435 |
emporia_user_preferences |
emporia_client_config |
order-management-service |
5436 |
emporia_order_management |
emporia_order_data |
execution-service |
5437 |
emporia_execution |
emporia_execution |
portfolio-service |
5438 |
emporia_portfolio |
emporia_portfolio |
scripts/run-infra-docker.shTo bring up just the containers, for example to run one service manually against them (see Manual, per-service startup):
docker compose up -d
docker compose psTo launch all 9 microservices, API Gateway, React UI, service-owned PostgreSQL
instances, and Kafka in containers, build the Maven jars first β each
Dockerfile copies a pre-built target/*.jar rather than building from
source β then run scripts/local-deploy.sh:
# 1. Build and install exchange-core into your local Maven repo
git clone https://github.com/nvxtien/exchange-core.git && cd exchange-core && mvn clean install && cd ..
# 2. Build local Maven JAR artifacts
mvn clean install -DskipTests
# 3. Build images and start the full-stack containers (default: simulated market data)
scripts/local-deploy.sh
# Or with live Alpaca IEX market data:
MARKET_DATA_PROVIDER=alpaca-iex \
APCA_API_KEY_ID='your-alpaca-key-id' \
APCA_API_SECRET_KEY='your-alpaca-secret-key' \
scripts/local-deploy.shOne command stops everything regardless of which mode you used β host-JVM
processes from run-local.sh/run-infra-docker.sh, the infra containers
from docker-compose.yml, and the full-stack containers from
docker-compose.full.yml:
scripts/stop-services.shDo not add -v to the docker compose commands inside it unless you
intentionally want to delete the local per-service database volumes and the
Kafka volume.