A production-grade stock market simulator written in C++17 featuring realistic price simulation, a price-time-priority order matching engine, pluggable trading strategies, portfolio management, backtesting, and multi-threaded simulation across multiple stocks.
stock_simulator/
βββ main.cpp β CLI entry point, wires all components
βββ Makefile
βββ config.ini β All tuneable parameters
β
βββ engine/
β βββ MarketEngine.{h,cpp} β GBM price simulation, multi-stock, threading
β βββ MatchingEngine.{h,cpp} β Price-time priority order book & matching
β βββ StrategyEngine.{h,cpp} β Pipes ticks β strategies β orders β portfolio
β βββ BacktestEngine.{h,cpp} β CSV replay, metrics: PnL, Sharpe, drawdown
β
βββ models/
β βββ Stock.h β Price history, SMA helper, volatility
β βββ Order.h β Order types (MARKET/LIMIT), BUY/SELL, status
β βββ Trade.h β Execution record (price, qty, buyer/seller IDs)
β βββ Portfolio.h β Cash, positions, PnL, Sharpe ratio, drawdown
β
βββ strategies/
β βββ Strategy.h β Abstract base class (pure virtual onPrice/reset)
β βββ MovingAverageStrategy.{h,cpp} β Dual MA crossover (golden/death cross)
β βββ MomentumStrategy.{h,cpp} β Rate-of-change threshold with cooldown
β
βββ utils/
β βββ Logger.{h,cpp} β Thread-safe singleton logger (console + file)
β βββ CSVReader.{h,cpp} β CSV loader for historical price data
β βββ Config.{h,cpp} β INI-style config file parser
β
βββ data/
βββ sample_stock_data.csv β 200-step GBM price paths for 4 stocks
- GCC 8+ or Clang 7+ with C++17 support
- POSIX threading (
-pthread) - No third-party libraries required
# Clone / unzip the project, then:
cd stock_simulator
# Build (release)
make
# Run interactive CLI
./stock_simulator
# Or use make shortcut
make rung++ -std=c++17 -pthread -Wall -O2 \
main.cpp \
engine/MarketEngine.cpp \
engine/MatchingEngine.cpp \
engine/StrategyEngine.cpp \
engine/BacktestEngine.cpp \
strategies/MovingAverageStrategy.cpp \
strategies/MomentumStrategy.cpp \
utils/Logger.cpp \
utils/CSVReader.cpp \
-o stock_simulatormake debug
./stock_simulator_debugββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β STOCK MARKET SIMULATOR v1.0 β
β C++17 | Random Walk | Order Matching | Backtest β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββ
β MAIN MENU β
βββββββββββββββββββββββββββββββββββββββββββ€
β 1. Live Simulation (Moving Average) β
β 2. Live Simulation (Momentum) β
β 3. Backtest: Moving Average Strategy β
β 4. Backtest: Momentum Strategy β
β 5. Compare Both Backtest Strategies β
β 6. Exit β
βββββββββββββββββββββββββββββββββββββββββββ
| Key | Default | Description |
|---|---|---|
SIMULATION_STEPS |
100 | Number of time steps in live sim |
PARALLEL_SIMULATION |
true | Use std::thread per stock |
INITIAL_CASH |
100000.0 | Starting portfolio cash |
RANDOM_SEED |
42 | RNG seed for reproducibility |
LOG_FILE |
simulator.log | Output log path |
LOG_LEVEL |
INFO | DEBUG / INFO / WARN / ERROR |
MA_SHORT_PERIOD |
5 | Moving average fast window |
MA_LONG_PERIOD |
20 | Moving average slow window |
MA_TRADE_QUANTITY |
10 | Shares per MA signal |
MOMENTUM_LOOKBACK |
10 | Momentum look-back window |
MOMENTUM_THRESHOLD |
0.02 | Β±2% rate-of-change trigger |
MOMENTUM_COOLDOWN |
5 | Steps to wait after a trade |
MOMENTUM_TRADE_QUANTITY |
10 | Shares per momentum signal |
BACKTEST_CSV |
data/sample_stock_data.csv | Historical data path |
BACKTEST_STARTING_CASH |
100000.0 | Backtest portfolio cash |
STOCK_N |
AAPL,150.0,0.015 | Symbol, start price, volatility |
Each stock price evolves each step via:
S(t+1) = S(t) Γ exp(Ο Γ Ξ΅) where Ξ΅ ~ N(0,1)
Ο(volatility) is per-stock and configurable- Hard floor at $0.01 prevents negative prices
- Multi-stock parallel mode: one
std::threadper stock,std::mutexprotects shared RNG
- Bid book:
std::map<double, std::deque<Order>, std::greater<>>β highest bid first - Ask book:
std::map<double, std::deque<Order>>β lowest ask first - Incoming orders sweep the contra-side book at best available price
- Partial fills supported: orders split across multiple resting levels
- Market maker synthetic orders seed initial liquidity at Β±0.1% spread
Abstract Strategy interface with two methods:
virtual std::optional<Signal> onPrice(const std::string& symbol,
double price, int64_t timestamp) = 0;
virtual void reset() = 0;- Computes SMA(5) and SMA(20) from rolling price window
- BUY signal: SMA(5) crosses above SMA(20) β golden cross
- SELL signal: SMA(5) crosses below SMA(20) β death cross
- No double-signals: tracks previous crossover state per symbol
- Computes rate-of-change over
lookbackwindow:(P_now - P_old) / P_old - BUY when RoC > +
threshold; SELL when RoC < βthreshold - Cooldown timer prevents over-trading after each signal
- Tracks open/closed position state per symbol
- Tracks cash and per-symbol
Position { quantity, avgCost, realizedPnL } - Average cost uses volume-weighted cost basis for accurate PnL
- Metrics computed on-the-fly:
- Realized PnL: locked in from closed positions
- Unrealized PnL: mark-to-market against current prices
- Max Drawdown: rolling peak-to-trough from value history
- Sharpe Ratio: annualised
mean(returns) / std(returns) Γ β252 - Win Rate: winning sell trades / total sell trades
- Loads CSV via
CSVReader::load()β sortedvector<PriceRecord> - Replays ticks chronologically, feeding each price to the strategy
- Executes signals as immediate fills at the tick price (no slippage model)
- Liquidates remaining open positions at last available price for fair PnL
- Reports: Total PnL, Return %, Win Rate, Max Drawdown, Sharpe Ratio
Thread-safe singleton logger (utils/Logger.h):
- Writes to both
stdoutandsimulator.logsimultaneously - Mutex-protected for multi-threaded correctness
- Log levels: DEBUG / INFO / WARN / ERROR (filter via
config.ini) - Flushes after every write to prevent data loss on crash
ββββββββββββββββββββββββββββββββββββββββββββββββ
β BACKTEST RESULTS SUMMARY β
β βββββββββββββββββββββββββββββββββββββββββββββββ£
β Strategy MovingAverage β
β Starting Cash $100000 β
β Final Value $105471 β
β Total P&L $+5471.80 β
β Return +5.47% β
β Total Trades 20 β
β Winning Trades 9 β
β Losing Trades 11 β
β Win Rate 45.0% β
β Max Drawdown -84.19% β
β Sharpe Ratio 3.444 β
ββββββββββββββββββββββββββββββββββββββββββββββββ
[INFO ] [STRATEGY:MovingAverage] BUY GOOGL x10 @ 3078.77 | Golden cross SMA5(3003.69) > SMA20(2984.59)
[INFO ] [TRADE] BUY GOOGL x10 @ $2993.93 | Cash: $70060.70
[INFO ] [STRATEGY:MovingAverage] SELL GOOGL x10 @ 2862.61 | Death cross SMA5(2958.88) < SMA20(2988.20)
[INFO ] [TRADE] SELL GOOGL x10 @ $2863.72 | Cash: $98697.87
| Principle | Implementation |
|---|---|
| SRP | Each class has one responsibility (matching β pricing β portfolio) |
| OCP | Add new strategies by extending Strategy base class only |
| LSP | MovingAverageStrategy and MomentumStrategy are interchangeable |
| DIP | StrategyEngine depends on Strategy interface, not concrete classes |
| Encapsulation | Order books, position data hidden behind public APIs |
| Move semantics | Order objects moved into books; Trade returned by value |
| Thread safety | All shared state protected by std::mutex |
| RAII | ofstream in Logger closed in destructor automatically |
- Create
strategies/MyStrategy.hextendingStrategy:
class MyStrategy : public Strategy {
public:
MyStrategy() : Strategy("MyStrategy") {}
std::optional<Signal> onPrice(const std::string& sym,
double price, int64_t ts) override;
void reset() override;
};- Implement
onPrice()β returnstd::nulloptto pass, or aSignalstruct - Add to
main.cppmenu and wire viastrategyEngine.addStrategy(...)
The CSV loader expects timestamp,symbol,price but can be extended.
Add columns to PriceRecord in utils/CSVReader.h and parse them
in CSVReader::load(). The backtest engine receives the full record.
- No slippage model: backtest fills at exact tick price
- No transaction costs: commissions/spreads not deducted
- Single-strategy at a time: live sim runs one strategy (easily extended)
- No persistence: portfolio state lost between runs (add SQLite or JSON export)
- Fixed lot sizes: strategies trade fixed quantity; add position sizing