Machine Coding Problem

Stock Brokerage System

maco60macoAllfintechcommand-patternorder-matching-enginepriority-queuesobserver-patternthread-safe-order-books
Commonly Asked By:RobinhoodCoinbaseGoldman Sachs

Functional Scope (In-Scope)

  • Sorted Bid/Ask Order Books: Manage isolated order books per ticker symbol using double sorted priority queues (Price-time priority matching).
  • Price-Time Execution Engine: Match orders deterministically when Bid limit price >= Ask limit price.
  • Command Transactions (Command Pattern): Encapsulate order actions (PLACE, CANCEL, MODIFY) systematically for unified auditing and transaction history.
  • Observer Notifications: Enable observers to listen to matched trade executions and notify users dynamically.

Explicit Boundaries (Out-of-Scope)

  • No Real Exchange Connectivity (FIX Protocol): Bypasses direct network socket links to NASDAQ, NYSE, or external clearing firms.
  • No Advanced Margin Lending Algorithms: Bypasses complex margin interest rates or automatic collateral liquidation sweeps.

Clean reference designs demonstrating order-matching engines in Java and Python:

// ─── JAVA BLUEPRINT ──────────────────────────────────────────────────────────
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;

enum OrderType { MARKET, LIMIT }
enum OrderSide { BUY, SELL }

class Order {
    private final String id;
    private final String userId;
    private final String symbol;
    private final OrderType type;
    private final OrderSide side;
    private int quantity;
    private final double limitPrice;
    private final long timestamp;

    public Order(String id, String userId, String symbol, OrderType type, OrderSide side, int quantity, double limitPrice) {
        this.id = id;
        this.userId = userId;
        this.symbol = symbol;
        this.type = type;
        this.side = side;
        this.quantity = quantity;
        this.limitPrice = limitPrice;
        this.timestamp = System.nanoTime();
    }

    public String getId() { return id; }
    public String getUserId() { return userId; }
    public String getSymbol() { return symbol; }
    public OrderType getType() { return type; }
    public OrderSide getSide() { return side; }
    public int getQuantity() { return quantity; }
    public double getLimitPrice() { return limitPrice; }
    public long getTimestamp() { return timestamp; }

    public void deductQuantity(int qty) { this.quantity -= qty; }
    public boolean isFilled() { return quantity == 0; }
}

interface TradeObserver {
    void onTradeExecuted(Order buyOrder, Order sellOrder, int quantity, double price);
}

class TransactionLogger implements TradeObserver {
    @Override
    public void onTradeExecuted(Order buyOrder, Order sellOrder, int quantity, double price) {
        System.out.println(String.format("[Trade Executed] %d shares of %s matched between User %s and User %s at $%.2f", 
                quantity, buyOrder.getSymbol(), buyOrder.getUserId(), sellOrder.getUserId(), price));
    }
}

// ─── COMMAND PATTERN (TRADING TRANSACTION) ──────────────────────────────────
interface OrderCommand {
    boolean execute();
    void undo();
}

class PlaceOrderCommand implements OrderCommand {
    private final BrokerageSystem system;
    private final Order order;

    public PlaceOrderCommand(BrokerageSystem system, Order order) {
        this.system = system;
        this.order = order;
    }

    @Override
    public boolean execute() {
        system.placeOrder(order);
        return true;
    }

    @Override
    public void undo() {
        system.cancelOrder(order.getSymbol(), order.getId());
    }
}

class OrderBook {
    private final String symbol;
    // Bids sorted descending by limit price. For matching timestamps, earlier order has priority.
    private final PriorityQueue<Order> bids = new PriorityQueue<>((o1, o2) -> {
        int priceCompare = Double.compare(o2.getLimitPrice(), o1.getLimitPrice());
        if (priceCompare != 0) return priceCompare;
        return Long.compare(o1.getTimestamp(), o2.getTimestamp());
    });
    // Asks sorted ascending by limit price. For matching timestamps, earlier order has priority.
    private final PriorityQueue<Order> asks = new PriorityQueue<>((o1, o2) -> {
        int priceCompare = Double.compare(o1.getLimitPrice(), o2.getLimitPrice());
        if (priceCompare != 0) return priceCompare;
        return Long.compare(o1.getTimestamp(), o2.getTimestamp());
    });

    private final ReentrantLock bookLock = new ReentrantLock();
    private final List<TradeObserver> observers = new CopyOnWriteArrayList<>();

    public OrderBook(String symbol) {
        this.symbol = symbol;
    }

    public void addObserver(TradeObserver obs) { observers.add(obs); }

    public void addOrder(Order order) {
        bookLock.lock();
        try {
            if (order.getSide() == OrderSide.BUY) {
                bids.add(order);
            } else {
                asks.add(order);
            }
            matchOrders();
        } finally {
            bookLock.unlock();
        }
    }

    public void cancelOrder(String orderId) {
        bookLock.lock();
        try {
            bids.removeIf(o -> o.getId().equals(orderId));
            asks.removeIf(o -> o.getId().equals(orderId));
        } finally {
            bookLock.unlock();
        }
    }

    private void matchOrders() {
        while (!bids.isEmpty() && !asks.isEmpty()) {
            Order bid = bids.peek();
            Order ask = asks.peek();

            // Match limit overlapping pricing
            if (bid.getLimitPrice() >= ask.getLimitPrice()) {
                int matchQty = Math.min(bid.getQuantity(), ask.getQuantity());
                double executionPrice = ask.getLimitPrice(); // Price of standing order

                bid.deductQuantity(matchQty);
                ask.deductQuantity(matchQty);

                for (TradeObserver obs : observers) {
                    obs.onTradeExecuted(bid, ask, matchQty, executionPrice);
                }

                if (bid.isFilled()) bids.poll();
                if (ask.isFilled()) asks.poll();
            } else {
                break;
            }
        }
    }
}

class BrokerageSystem {
    private final Map<String, OrderBook> orderBooks = new ConcurrentHashMap<>();
    private final TransactionLogger logger = new TransactionLogger();

    public void placeOrder(Order order) {
        OrderBook book = orderBooks.computeIfAbsent(order.getSymbol(), k -> {
            OrderBook newBook = new OrderBook(k);
            newBook.addObserver(logger);
            return newBook;
        });
        book.addOrder(order);
    }

    public void cancelOrder(String symbol, String orderId) {
        OrderBook book = orderBooks.get(symbol);
        if (book != null) {
            book.cancelOrder(orderId);
            System.out.println("[Service] Order Canceled: " + orderId);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        System.out.println("=== Stock Brokerage Matching Engine ===");
        BrokerageSystem system = new BrokerageSystem();

        // Placing matching limit orders
        System.out.println("\n--- Client places Limit Bid ---");
        Order buyOrder = new Order("ORD-1", "User-A", "AAPL", OrderType.LIMIT, OrderSide.BUY, 50, 150.00);
        OrderCommand cmd1 = new PlaceOrderCommand(system, buyOrder);
        cmd1.execute();

        System.out.println("\n--- Client places overlapping Limit Ask (Causes Match) ---");
        Order sellOrder = new Order("ORD-2", "User-B", "AAPL", OrderType.LIMIT, OrderSide.SELL, 30, 149.50);
        OrderCommand cmd2 = new PlaceOrderCommand(system, sellOrder);
        cmd2.execute();
    }
}