Skip to content

Latest commit

 

History

190 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Emporia Trading Platform

Documentation JaCoCo Coverage Java 21 Spring Boot React 19 Discord

📖 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, gRPC, and PostgreSQL. Its deployable boundaries strictly follow business capabilities: static data, user preferences, market data, order management, and portfolio are independent services; execution routing runs in-process inside order-management to remove the network hop between order intake and venue submission.


📚 Documentation & GitHub Wiki

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, service boundaries, database ownership.
⚙️ Order Management Service State machine authority, OrderCommandHandler, ExecutionCommandHandler, idempotency.
🎯 Execution Routing Algorithmic routing (DMA, SMART NBBO selector, VWAP slicer), venue gateways — in-process inside the OMS.
No Blocking DB on Hot Path Why the order hot path makes no blocking PostgreSQL call, the five mechanisms, and the three constraints accepted for it.
📜 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 7 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.

Architecture

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 -->|POST/PUT /api/orders| Orders[Order Management Service :8086]
    Gateway -->|GET /api/orders| Orders
    ExchangeCore[exchange-core simulation] -->|risk seed + durable snapshots| Portfolio[Portfolio :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
    Orders -->|validate listing| Static
    Orders -->|same-instrument listings| Static
    Orders -->|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 & emporia_execution)]
    Portfolio --> PortfolioDb[(PostgreSQL\nemporia_portfolio)]
Loading

The browser sees one /api surface. The gateway routes requests by path and HTTP method to the service that owns each business capability. Mutating order calls (POST/PUT /api/orders/**) go directly to order-management, which handles them on an in-process LMAX Disruptor ring buffer and dispatches order domain events to an in-process sharded dispatcher. The execution engine, SMART/VWAP algorithmic strategies, and venue gateways run directly in-process within order-management-service for zero network-hop execution.

Service ownership

Directory Port Owns
authentication 9000 OAuth2/OIDC login, users, tokens
static-data 8081 Instruments and exchange listings
user-preferences 8083 Per-user watchlists and persisted workspace layouts
market-data 8084 HTTP / 50551 gRPC Simulated, Alpaca IEX, or FIX-simulator market data; venue/composite books; REST, SSE, and gRPC distribution
order-management 8086 Order command hot path (Disruptor), lifecycle, state, history, executions, sharded in-process SOR routing (DMA, SMART, VWAP), and venue gateways
portfolio 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 contracts shared at build time
fix-simulator-contracts not deployed Generated FIX-simulator protobuf/gRPC contracts, consumed by market-data 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'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 and forwards a bearer token. Orders store an immutable listing snapshot instead of a cross-schema foreign key.

Order command flow

Gateway hot path (browser)

  1. Gateway forwards POST/PUT /api/orders/** to order-management.
  2. OMS validates the listing snapshot, builds an OrderCommand, and submits it to the single-writer Disruptor pipeline.
  3. OrderCommandHandler applies the transition against the in-memory order cache, enqueues write-behind persistence, and returns the correlated result to the waiting HTTP request.
  4. OMS passes order domain events directly to the in-process ShardedOrderDispatcher, partitioning events by order ID hash across dedicated shard worker threads without network overhead or broker dependency.

No step above makes a blocking database call. The two idempotency lookups are answered from an in-memory deduplication index, and the row reaches PostgreSQL on a batched flush after the 201 is returned, with a memory-mapped write-ahead log covering the window. That trade has terms - one instance may accept orders, and a lost durable write is silent - which are set out in docs/NO_BLOCKING_DB_HOT_PATH.md.

In-Process 3-Leg Direct Pipeline

The execution pipeline operates completely in-process via three direct zero-hop legs, with no message broker on any of them:

  1. Leg 1 (order → execution): ShardedOrderDispatcher dispatches order events to ExecutionEventConsumer.processEvent(event) on single-threaded shard workers.
  2. Leg 2 (execution → child order): SMART venue splits and VWAP time slices generated by ExecutionEventConsumer submit child OrderCommands directly to the DisruptorOrderPipeline for single-writer WAL persistence.
  3. Leg 3 (fill/reject/cancel → order management): Venue gateway fills, rejections, and cancels are passed directly to ExecutionCommandHandler.handle(command) in-memory.

ShardedOrderDispatcher's shard workers (default 8, emporia.execution.dispatcher.shards) process independent orders in parallel while maintaining strict in-order processing for commands sharing the same order ID.

Execution flow

  • 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=fix enables 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. Execution runs in-process inside order-management-service (see order-management/README.md) rather than as its own deployable.

Gateway routing, order-route circuit breaker and rate limiter behavior, and the internal service-account token policy are documented in gateway/README.md.

Local prerequisites

  • Java 21 or newer
  • Maven 3.9+
  • Node.js and npm
  • PostgreSQL running at localhost:5432 for 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 postgresql formula creates), not postgresscripts/run-local.sh and scripts/seed-portfolio-client.sh default DB_USERNAME accordingly; override DB_USERNAME if 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.

Run modes

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.

Start locally

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.

  1. Clone and install the exchange-core dependency into your local Maven repository:

    git clone https://github.com/nvxtien/exchange-core.git
    cd exchange-core
    mvn clean install
    cd ..
  2. 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
  3. Open http://localhost:3001 and sign in with admin / admin123 once the script prints "stack is up".

Both scripts default execution 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 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:

  1. Sign up for a free account at alpaca.markets.
  2. Open the Paper Trading dashboard (free sandbox).
  3. Click Generate New API Key in the right-hand panel.
  4. 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 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.

Check service status

Health check every service (all default to GET /actuator/health without a token):

for pair in "authentication:9000" "static-data:8081" "user-preferences:8083" \
            "market-data:8084" "order-management:8086" \
            "portfolio: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:3001

Other useful checks:

cat .local-run/pids/*.pid                                      # pid recorded per running service
tail -f .local-run/logs/order-management-service.log              # live logs (includes execution routing)

Stop everything either script started, including the per-service PostgreSQL containers:

scripts/stop-services.sh

If 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>

Manual, per-service startup

Running one service by hand (for example under a debugger) is still supported. authentication should start before any service that validates its own OAuth tokens. order-management-service now includes execution routing in-process (see Order command flow above) - there is no separate execution service to start. Each service's own README documents its environment variables and mvn spring-boot:run / npm run dev command: authentication, static-data, user-preferences, market-data, order-management (includes execution), portfolio, gateway, and frontend.

Every service supports GET /actuator/health without a token.


Verify

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 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.


🐳 Docker Deployment

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.

1. Infrastructure-Only Docker Setup

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), then runs every Spring Boot service and the frontend on your host JVM against those containers. order-management-service connects to two of the databases below directly (order-management for its own state, execution for venue/execution state) since execution now runs in-process inside it rather than as its own service.

Database Host port Schema Owned by
emporia_authentication 5433 emporia_authentication authentication
emporia_static_data 5434 emporia_static_data static-data
emporia_user_preferences 5435 emporia_client_config user-preferences
emporia_order_management 5436 emporia_order_data order-management
emporia_execution 5437 emporia_execution order-management (execution routing)
emporia_portfolio 5438 emporia_portfolio portfolio
scripts/run-infra-docker.sh

To 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 ps

2. Full-Stack Docker Container Deployment

To launch all 7 microservices, API Gateway, React UI, and service-owned PostgreSQL instances 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.sh

3. Stop Docker

One 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.sh

Do not add -v to the docker compose commands inside it unless you intentionally want to delete the local per-service database volumes.

About

Emporia is a microservices-based trading platform built with Spring Boot, React, Kafka, and PostgreSQL, featuring real-time market data, order management, execution strategies, and OAuth2 security.

Topics

Resources

Stars

47 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages