πŸš€ πŸš€ Launch Offer β€” Courses starting at β‚Ή1499 (Limited Time)
CortexCookie

Stock Exchange System Design

First let's understand a Stock Exchange System:

  • A stock exchange is a marketplace where investors buy and sell shares of companies.
  • A stock (share) represents partial ownership in a company.
  • A stock exchange is a regulated marketplace for buying and selling securities like stocks.
  • A buy order means you want to purchase shares; a sell order means you want to exit ownership.
  • Market orders execute immediately at the current price; limit orders execute only at your chosen price.
  • Prices change constantly due to supply and demand.
  • Profit or loss depends on the difference between buying and selling price.

Here we will try to decode the system design behind such Stock Exchange Systems.

Functional Requirements

The system must support these functional requirements:

  • Users can place buy/sell orders
  • Users specify price and quantity
  • Support both:
    • LIMIT Orders (price constrained)
    • MARKET Orders (best available price)
  • System matches buy and sell orders
  • Partial order matching is allowed
  • Unmatched orders remain active until:
    • Matched
    • Expired at end of trading day - usually 4 PM
  • Orders expire after market closure
  • Matching results in trade execution

Out of Scope

  • Listing / delisting stocks
  • Advanced order types (stop loss, iceberg, etc.)

Non-Functional Requirements

  • Scalability – System must handle large numbers of concurrent orders and users.
  • Availability – Browsing stocks and order books must remain available. Platform should remain accessible with minimal downtime.
  • Reliability – Orders must not be lost, duplicated, or corrupted.
  • Consistency & Integrity – Highly consistent for executing matches. Buy/sell matching and trade records must remain accurate.

Core Concepts

LIMIT Order: Executed only at specified price or better.

MARKET Order: Executed immediately at best available price.

Partial Matching: Orders may be filled in parts, instead of completely at once.

You place a buy order to buy 1,000 shares of XYZ at β‚Ή100

But in the market:

  • 400 shares available at β‚Ή100
  • 300 shares available at β‚Ή100
  • No more sellers at β‚Ή100

So what happens?

  • βœ” 700 shares get executed
  • ❌ 300 shares remain pending

That’s a partial match.

APIs

Place Order

POST /orders

{
  "type": "BUY | SELL",
  "stockId": "ABC",
  "quantity": 100,
  "price": 250.00,
  "orderType": "LIMIT | MARKET"
}

Execute Match (Internal API)

POST /execute
 
{
  "buyOrderId": "O123",
  "sellOrderId": "O987",
  "quantity": 5,
  "executionPrice": 249.50
}

Get Order Status

GET /orders/{id}

Returns:

  • OPEN
  • PARTIALLY_FILLED
  • FILLED
  • EXPIRED

High Level Architecture

At a very High Level a stock exchange service will look like below.

image

Actors

  • User β†’ places buy/sell orders
  • Company Admin β†’ lists/delists stocks

Core Service

Stock Exchange Service (central logic)

Storage Models

  • Stock
  • Order Request
  • Matched Order

Scale Estimation

Assumptions:

  • 100 million users
  • 10% daily active users β†’ 10 million daily active users
  • 5000 tradable stock companies
  • 500 million orders / day

TPS:

For simplicity assume approximately there are 10510^5 seconds in a day.

500β€…β€Šmillion(24βˆ—60βˆ—60)\frac{500 \;million} {(24 * 60 * 60)} orders per second β‰ˆ Β 500Γ—106105~\frac{500 \times 10^6}{10^5} orders per second β‰ˆ 5000 orders per second (TPS is very high)

Peak traffic may be 5–10Γ— higher, requiring the system to sustain:

  • 5k – 50k orders/sec
  • Extremely high write contention

Storage Estimation

Consider Stock details takes around 1 kb storage

1β€…β€ŠkbΓ—50001 \; kb \times 5000 stocks = 5 mb

1β€…β€ŠkbΓ—100001\; kb \times 10000 stocks = 10 mb

500 million orders/day and 100Bytes/order

=> 500Γ—106Γ—100500 \times 10^6 \times 100 Bytes/order = 5Γ—10105 \times 10^10 bytes/day = 50 GB per day

=> 5Γ—1010Γ—3655 \times 10^10 \times 365 bytes/year => 5Γ—1010Γ—4005 \times 10^10 \times 400 bytes/year (approx) = 20 TB per year

Historical Stock Prices Storage

We are considering 5000 stocks.

We will be storing price for every minute of Trading Hours.

Trading hours β‰ˆ 6.5 hours/day = 390 minutes/day β‰ˆ 400 minutes / day (assumed for simplicity)

Price record β‰ˆ 100 Bytes (timestamp, price, few other fields)

So storage per day for historical data of 5000 stocks β‰ˆ 5000Γ—400Γ—1005000 \times 400 \times 100 bytes = 200 mb per day

Yearly storage = 200β€…β€ŠmbΓ—365200 \; mb \times 365 = 200β€…β€ŠmbΓ—400200 \; mb \times 400 (approx) = 80000 mb = 80 GB

Fairness in a Stock Exchange System

Fairness in a stock exchange is achieved through deterministic and rule-based matching logic rather than subjective decisions.

Price Priority

The primary rule for fairness is price priority:

  • A buy order should match with the lowest available sell price.
  • A sell order should match with the highest available buy price.

Examples:

  • If a buyer is willing to pay 50, any seller offering at ≀ 50 is a fair match.
  • If a seller is asking 50, any buyer bidding at β‰₯ 50 is a fair match.

This guarantees economically fair outcomes for both sides.

Time Priority

When multiple orders exist at the same price:

  • The earliest order gets execution priority.

This prevents manipulation and ensures predictable behavior.

Deep Dive - Microservice Architecture

Now let's move towards a Microservice Architecture. In a stock exchange system, microservices allow independent scaling, fault isolation, and specialized optimization of order intake, matching, settlement, and market data components to handle high throughput and low-latency requirements.

API Gateway

Acts as the entry point for all client requests. It handles authentication, rate limiting, request validation, and routes traffic to the appropriate internal services.

Stock Service

Manages stock metadata such as listing, delisting, and browsing. Company Stock admins can list/delist stocks. Other users can browse stocks. It interacts with the Stock DB and is primarily read-heavy, serving market information to users. We need to have a Relational Database (SQL) for Stock Service as we have structured data, low write frequency, and consistency needed for list/delist stocks.

Order Service

Receives buy/sell orders from users, performs basic validation, persists the order, and publishes it to a queue for asynchronous processing. It ensures durability and decouples order intake from matching. We can use a High-write optimized NoSQL DB (Key-Value or document DB) for orders, as we will have high order volume, need horizontal scaling and orders are mostly immutable. Order DB can partitioned by Order Id or Stock Id.

Matcher Service

Core matching logic component that consumes orders from the queue and matches them using price-time priority. After matching it stores the matched orders and produces match events for the downstream services. We can have a relational DB for matched order.

Match Execution Service

Finalizes matched trades by updating order status, recording trade details. It also interacts with Payment Service
and handles payment or settlement. We need a Relational Database here as well as we need ACID compliance and strong consistency.

image

We also must ensure idempotency in this design because:

  • Event could be delivered twice
  • Downstream service must not process same raw orders or matched orders again
  • So each trade should have idepmotent id (like match_id). Downstream service must check if already processed.

Together, this architecture separates responsibilities, supports scalability, and enables low-latency order matching with reliable post-trade execution.

Separation of Matching and Execution Services

Matching and execution are segregated because they solve fundamentally different problems.

Matching Service Responsibilities

  • Order evaluation
  • Price-time priority logic
  • High-throughput comparisons
  • Compute-intensive operations

Execution Service Responsibilities

  • Database updates
  • Order state transitions
  • Balance / settlement updates
  • Side effects (payments, confirmations)

Deep Dive - Matching

In our current design Order Service all types of orders in the queue. However, let's improve this considering the matching logic in mind. Orders are routed into symbol-specific buy and sell queues to isolate each stock’s order flow and maintain independent order books. Since matching requires to match buy and sell orders of same symbols, partitioning by symbol guarantees deterministic processing without cross-symbol interference.

image

Flow in Your Current Design (No Order Book)

  • A user places a buy/sell order via the API Gateway.
  • The Order Service validates the request and stores the order in the Order DB (NoSQL) for durability
  • Routes the order to the appropriate symbol-specific queue (e.g., Meta Buy, Meta Sell, Apple Buy,..).
  • Matcher Service consumes orders from the symbol-specific buy and sell queues
  • Applies price-time matching logic and creates Matched Order
  • Matcher Service stores matched trades in the Match DB
  • Matcher Service emits a matched event to the matched-order queue
  • The Match Execution Service consumes the event handles payment and settlement

Deep Dive - Matching Logic

At its core, a stock exchange is a matching engine problem, not a database problem. Hence, matching logic has to run in memory. Let's formularize how we can perform matching.

Order Book

Each symbol maintains an in-memory order book with price levels and FIFO queues.

Sell Book Example

sellBook[TSLA] = {
    101 β†’ Queue[order4, order6, order9]
    102 β†’ Queue[order2, order5, order8]
}
  • 101 and 102 are price levels
  • Each level contains orders in FIFO order (order4 arrived before order6 which came before order 9, and all three has come with same price 101)
  • Ensures time priority within the same price

Buy Book Example

buyBook[AAPL] = {
    180 β†’ Queue[order1, order3]
    181 β†’ Queue[order7, order11]
}

Logical View of Order Book

SELL SIDE

Price   Orders (FIFO)
----------------------
101     order4, order6, order9
102     order5, order8
103     order10

BUY SIDE

Price   Orders (FIFO)
----------------------
105     order21, order24
104     order18
103     order10, order11, order15
102     order7

Matching Condition

Matching always checks the best available price first.

Example 1

  • Incoming SELL @ 104
  • Best Buy Price = 105
  • 105 β‰₯ 104 β†’ βœ… Match possible

Example 2

  • Incoming SELL @ 106
  • Best Buy Price = 105
  • 105 < 106 β†’ ❌ No match

Priority Rules

The order book enforces two-tier priority:

Price Priority

  • Higher buy price first
  • Lower sell price first

Time Priority (FIFO)

  • Earlier orders at same price execute first

In-memory structure

As we have to fetch highest buy orders and lowest sell orders every time, we can use Heap Data Structures in memory for that.

  • Buy side β†’ Max Heap (highest price first)
  • Sell side β†’ Min Heap (lowest price first)

OR better:

  • we can have Price Level Map. And for each price we will have FIFO queue of orders (based on arrival time)

Sharding Strategy

All orders for a symbol must go to the same matcher shard.

Example:

  • Matcher Shard 1 β†’ AAPL, TSLA, NVDA
  • Matcher Shard 2 β†’ MSFT, AMZN, META
  • Matcher Shard 3 β†’ GOOG, NFLX

Each shard maintains:

  • In-memory order books
  • Matching logic
  • Deterministic execution
  • No cross-shard coordination

Deployment Example

  • Matcher Node 1 β†’ TSLA, NVDA
  • Matcher Node 2 β†’ AAPL, MSFT

This guarantees:

  • Strict ordering per symbol
  • No cross-symbol contention
  • Horizontal scalability

Deep Dive - Match Execution Service

The Match Execution Service is responsible for turning a matched trade into a financially final transaction.

While the Matcher Service decides who trades with whom, the Execution Service ensures the trade actually happens.

Core Responsibilities

  • Consume Matched Events from matched order topic. Each event contains:

    • buy_order_id
    • sell_order_id
    • symbol
    • quantity
    • price
    • match_id
  • Validates Trade

    • Check buyer balance
    • Check seller holdings
    • Ensure trade not already processed (idempotency)
  • Validate trade integrity

    • Perform Atomic Settlement. This is the critical part. It must:

      • Debit buyer funds
      • Credit seller funds
      • Transfer stock ownership
      • Mark trade as settled

      All of this must be ACID compliant.

  • Store Settlements in Settlement DB (Strong Consistency Required). This DB should:

    • Be relational (PostgreSQL / MySQL)
    • Support transactions
    • Maintain immutable settlements
      • match_id
      • buyer_id
      • seller_id
      • amount
      • settlement_status (PENDING, SETTLED, FAILED)
      • timestamps
  • Idempotency & Reliability - Because it consumes events, it must handle duplicate events safely and must support retries.

  • Interaction with Payment Service - In real exchanges funds are often pre-blocked. However, in simplified design Execution Service:

    • β†’ Calls Payment Service
    • β†’ On success, commits settlement
    • β†’ On failure, emits failure event

Instead of heavy distributed transactions (2PC), modern systems often use:

  • Saga pattern
  • Idempotent operations
  • Compensating actions

That was a free preview lesson.