kc.
Back to projects
systemssystemsjavaoopconcurrency

Stock Market Simulation

Multi-threaded stock-market simulator with 25 autonomous trading bots and a React dashboard, built around classic OOP design patterns.

University of Groningen, Advanced OOP · 2024

Stock Market Simulation banner

Stack

  • Java
  • Maven
  • Docker
  • React

Role

Course project

Team

2 people

Repository

Overview

Two-person course project for Advanced OOP. The brief asked for a non-trivial concurrent system exercising several design patterns, with the trader and the exchange evolving independently and talking only through a shared message bus. We modelled a stock market: 25 trader bots live in one application, the exchange lives in another, and everything they say to each other goes through a thread-safe queue.

Both sides depend on a small shared layer of interfaces and on the queue, so either can change without the other noticing. The whole thing runs as a set of containers behind a React dashboard.

What was built

1. A modular split with a thread-safe bus

The trader application and the exchange never import each other. They both depend on a small core of shared interfaces and on the queue that carries orders between them.

  • An early version polled the queue on a tick. Replacing the loop with an observer subscription let the exchange react the moment an order arrived and removed the polling cost entirely.
  • Splitting the system into independent applications meant either side could be replaced without touching the other.

2. Trader bots and pluggable strategies

25 autonomous bots run in the trader application, each driven by a pluggable strategy. Adding a new trading style is one new class.

  • Bots see the market through a stable interface, so the same bot code works against any exchange implementation.
  • Today only a random-choice strategy ships (random buy/sell, price, amount). New strategies plug in without touching the bot or the networking layer.
  • 11 simulated stocks with price, symbol, name, market cap, and shares outstanding.

3. Dispatch and the React dashboard

Each order type is its own class implementing a shared Command interface, with a Factory keeping the dispatch sites clean. The dashboard reads everything through stable model interfaces.

  • A new order type is a new class instead of a new branch in a switch statement, and the dispatch code never has to change.
  • Three views (overview, trader detail, stock detail) all read through model interfaces, so swapping in a different visualisation later means no changes to the core.

Technical details

A multi-module Java project deployed as containers, with the React frontend reading state through stable model interfaces.

  • Patterns: Observer, Command, Strategy, Factory
  • Concurrency: a thread-safe queue between the two applications
  • Networking: client/server with per-connection handlers
  • Default load: 25 trader bots against 11 stocks running continuously

Key technical decisions

  • Module independence over inheritance: Trader and exchange never import each other. They both depend on a small shared interface layer and on the queue, so changing one side doesn't risk breaking the other.
  • Observer over busy-poll: The first version polled the queue on a tick and spent CPU on nothing while introducing latency. Subscribing to the queue's change events removed the loop and processes orders the instant they land.
  • Command + Factory over switch-case: Each order type is its own class. New order types are new classes, not new branches, and the factory keeps the dispatch sites clean.

Results

25

autonomous trader bots

11

simulated stocks

4

design patterns exercised

Concurrent dispatch

The queue plus an observer subscription handles concurrent arrivals without a polling loop.

Challenges & tradeoffs

  • Sequential order processing: The exchange handles one order at a time, which won't scale to a million orders per second. The write-up sketches the fix: parallel extraction plus per-stock locking to coordinate writes safely.
  • Polling versus push: The initial polling-loop design burned CPU and added latency. Switching to an observer subscription eliminated both. The cheapest performance fix was removing a loop, not optimising one.
  • Module independence vs shared state: The exchange and traders need to talk about the same things without depending on each other. Putting the shared definitions in a third common module lets both sides depend on an abstraction instead of one another.

What I learned

  • Strategy and Observer earn their keep when the system runs continuously. New trading styles or new order-arrival behaviours both become one-class additions.
  • Sequential dispatch is the ceiling on order throughput. Scaling beyond it requires parallel extraction and per-stock locking, not faster logic.
  • Modules that share only interfaces and a queue can change independently. The trader and exchange never importing each other made everything else easier.
  • Switching from polling to an observer subscription removed CPU spin and dispatch latency in one swap, with no tuning involved.

Gallery

  • 01 / 04Overview dashboard. All 25 trader bots and 11 stocks rendered live with current price, balance, and performance.
  • 02 / 04Trader detail view. Portfolio distribution across the 11 stocks, balance over time, and performance history for an individual bot.
  • 03 / 04Stock detail view. Price chart, yearly performance, and key statistics for a single ticker, updated live as bots execute Buy and Sell commands against the exchange.
  • 04 / 04System framework. The trader application and the exchange are connected only through a message queue; both share a small core of definitions, and state updates fan back out to connected UI clients.

Next project

CityScale: Real-Height DSM from a Single Aerial RGB