Cryptocurrency Exchange Platform

Comprehensive C++ Code Analysis & Documentation

GitHub GitHub Repository

📊 Project Overview

System Architecture

This is a cryptocurrency exchange trading simulator built in C++ by Muhammad Saad Amin. It simulates a real-time order matching system where users can place bids and asks for various cryptocurrency pairs.

📈 Order Matching

Implements a sophisticated bid-ask matching algorithm that pairs buyers with sellers based on price priority

💰 Wallet Management

Tracks user cryptocurrency balances and validates transactions before execution

📁 CSV Processing

Reads historical market data from CSV files to populate the order book

⏰ Time Simulation

Advances through timestamps to simulate real market progression

Application Flow

User Input
MerkelMain (UI Controller)
OrderBook (Data Management)
Wallet (Balance Verification)
Order Matching Engine

Core Components

  • OrderBookEntry: Represents a single order (bid/ask) with price, amount, timestamp, and product
  • OrderBook: Manages all orders, handles matching logic, and tracks market state
  • Wallet: Manages user's cryptocurrency balances and validates orders
  • CSVReader: Parses CSV files to load historical market data
  • MerkelMain: User interface and main application controller

📚 OrderBook System

OrderBookEntry.h - Data Structure

enum class OrderBookType { bid, // Buy order ask, // Sell order unknown, sale // Completed transaction };

📝 Explanation

The OrderBookType enum defines four states:

  • bid: A buy order (user wants to purchase cryptocurrency)
  • ask: A sell order (user wants to sell cryptocurrency)
  • sale: A completed transaction between a bid and ask
  • unknown: Error state for invalid order types

OrderBookEntry Class

class OrderBookEntry { public: OrderBookEntry(double _price, double _amount, string _timestamp, string _product, OrderBookType _orderType); // Comparison functions for sorting static bool compareByTimestamp(OrderBookEntry &e1, OrderBookEntry &e2); static bool compareByPriceAsc(OrderBookEntry &e1, OrderBookEntry &e2); static bool compareByPriceDesc(OrderBookEntry &e1, OrderBookEntry &e2); double price; // Price per unit double amount; // Quantity string timestamp; // When order was placed string product; // e.g., "BTC/USDT" OrderBookType orderType; };

🔍 Key Features

  • Static Comparators: Used with std::sort to organize orders by timestamp or price
  • compareByPriceAsc: Sorts asks from lowest to highest (sellers willing to accept less go first)
  • compareByPriceDesc: Sorts bids from highest to lowest (buyers willing to pay more go first)
  • Product Format: Uses "BASE/QUOTE" notation (e.g., BTC/USDT means buying BTC with USDT)

OrderBook Class - Core Matching Engine

std::vector<OrderBookEntry> matchAsksToBids( string product, string timestamp) { // Get all sell orders for this product at this time vector<OrderBookEntry> asks = getOrders( OrderBookType::ask, product, timestamp); // Get all buy orders vector<OrderBookEntry> bids = getOrders( OrderBookType::bid, product, timestamp); // Sort asks lowest first (best sellers) std::sort(asks.begin(), asks.end(), OrderBookEntry::compareByPriceAsc); // Sort bids highest first (best buyers) std::sort(bids.begin(), bids.end(), OrderBookEntry::compareByPriceDesc);

⚙️ Matching Algorithm Logic

The algorithm implements price-time priority:

  1. Sort asks (sells) from lowest price to highest
  2. Sort bids (buys) from highest price to lowest
  3. Try to match the best buyer with the best seller
  4. If bid price ≥ ask price, a trade can occur!
for (OrderBookEntry &ask : asks) { for (OrderBookEntry &bid : bids) { if (bid.price >= ask.price) { // Create sale at the ask price (seller's price) OrderBookEntry sale{ask.price, 0, timestamp, product, OrderBookType::sale}; // Case 1: Exact match if (bid.amount == ask.amount) { sale.amount = ask.amount; sales.push_back(sale); bid.amount = 0; break; // Move to next ask } // Case 2: Bid is larger, ask completely filled if (bid.amount > ask.amount) { sale.amount = ask.amount; sales.push_back(sale); bid.amount -= ask.amount; // Partial fill of bid break; // Move to next ask } // Case 3: Ask is larger, bid completely filled if (bid.amount < ask.amount) { sale.amount=bid.amount; sales.push_back(sale); ask.amount -= bid.amount; // Partial fill of ask bid.amount = 0; continue; // Try next bid for remaining ask } } } } return sales; }

💡 Trading Logic Examples

Example 1 - Perfect Match:

  • Ask: Sell 5 BTC at $40,000
  • Bid: Buy 5 BTC at $41,000
  • Result: Sale of 5 BTC at $40,000 (seller's price)

Example 2 - Partial Fill (Bid Larger):

  • Ask: Sell 3 BTC at $40,000
  • Bid: Buy 10 BTC at $41,000
  • Result: Sale of 3 BTC at $40,000, bid remains for 7 BTC

Example 3 - Partial Fill (Ask Larger):

  • Ask: Sell 10 BTC at $40,000
  • Bid: Buy 3 BTC at $41,000
  • Result: Sale of 3 BTC at $40,000, ask remains for 7 BTC

📁 CSV Reader - Data Import System

Main Reading Function

std::vector<OrderBookEntry> readCSV(std::string csvFilename) { std::vector<OrderBookEntry> entries; std::ifstream csvFile{csvFilename}; std::string line; if (csvFile.is_open()) { while (std::getline(csvFile, line)) { try { OrderBookEntry obe = stringsToOBE(tokenise(line, ',')); entries.push_back(obe); } catch (const std::exception &e) { std::cout << "CSVReader::readCSV bad data" << std::endl; } } } std::cout << "CSVReader::readCSV read " << entries.size() << " entries" << std::endl; return entries; }

📖 How It Works

  1. Opens the CSV file using ifstream
  2. Reads each line of the file
  3. Tokenizes (splits) the line into fields using commas
  4. Converts the tokens into an OrderBookEntry object
  5. Error handling: If any line is malformed, it's skipped with an error message
  6. Returns all successfully parsed entries

Tokenization Function

std::vector<std::string> tokenise(std::string csvLine, char separator) { std::vector<std::string> tokens; signed int start, end; std::string token; start = csvLine.find_first_not_of(separator, 0); do { end = csvLine.find_first_of(separator, start); if (start == csvLine.length() || start == end) break; if (end >= 0) token = csvLine.substr(start, end - start); else token = csvLine.substr(start, csvLine.length() - start); tokens.push_back(token); start = end + 1; } while (end > 0); return tokens; }

✂️ Tokenization Process

This function splits a CSV line like:

2020/03/17 17:01:24,BTC/USDT,ask,9500.50,0.5

Into separate tokens:

  • tokens[0] = "2020/03/17 17:01:24" (timestamp)
  • tokens[1] = "BTC/USDT" (product)
  • tokens[2] = "ask" (order type)
  • tokens[3] = "9500.50" (price)
  • tokens[4] = "0.5" (amount)

String to OrderBookEntry Conversion

OrderBookEntry stringsToOBE(std::vector<std::string> tokens) { double price, amount; if (tokens.size() != 5) { std::cout << "Bad line" << std::endl; throw std::exception{}; } try { price = std::stod(tokens[3]); // Convert string to double amount = std::stod(tokens[4]); // Convert string to double } catch (const std::exception &e) { std::cout << "Bad float!" << std::endl; throw; } OrderBookEntry obe{price, amount, tokens[0], tokens[1], OrderBookEntry::stringToOrderBookType(tokens[2])}; return obe; }

🔄 Conversion Process

  • Validation: Ensures exactly 5 tokens exist
  • Type Conversion: Uses std::stod to convert price/amount strings to doubles
  • Error Handling: Catches conversion failures (e.g., "abc" → double)
  • Object Creation: Constructs OrderBookEntry with parsed data

💼 Wallet - Balance Management System

Currency Storage

class Wallet { private: std::map<std::string, double> currencies; // Key: Currency name (e.g., "BTC", "USDT") // Value: Balance amount };

🗂️ Data Structure

The wallet uses a std::map to store balances:

  • Key-value pairs allow quick lookup: O(log n)
  • Example: {"BTC": 1.5, "USDT": 50000.0, "ETH": 10.0}
  • Automatically handles new currencies when inserted

Insert Currency Function

void insertCurrency(std::string type, double amount) { double balance; if (amount < 0) { throw std::exception{}; // Can't add negative amounts } if (currencies.count(type) == 0) { // Currency not in wallet yet balance = 0; } else { // Currency exists, get current balance balance = currencies[type]; } balance += amount; // Add the new amount currencies[type] = balance; // Update the wallet }

➕ Adding Funds

This function safely adds cryptocurrency to the wallet:

  • Validation: Rejects negative amounts
  • New Currency: If currency doesn't exist, starts at 0
  • Existing Currency: Adds to current balance
  • Example: insertCurrency("BTC", 0.5) adds 0.5 BTC

Remove Currency Function

bool removeCurrency(std::string type, double amount) { if (amount < 0) { return false; // Can't remove negative amounts } if (currencies.count(type) == 0) { // Currency not in wallet return false; } else { if (containsCurrency(type, amount)) { // Enough balance? currencies[type] -= amount; // Deduct amount return true; } else { return false; // Insufficient funds } } }

➖ Removing Funds

This function safely removes cryptocurrency with multiple checks:

  • Negative Check: Prevents removing negative amounts
  • Existence Check: Verifies currency exists in wallet
  • Balance Check: Ensures sufficient funds before deduction
  • Returns: true if successful, false if failed

Order Fulfillment Validation

bool canFulfillOrder(OrderBookEntry order) { std::vector<std::string> currs = CSVReader::tokenise(order.product, '/'); if (order.orderType == OrderBookType::ask) { // Selling: Need the base currency (e.g., BTC in BTC/USDT) double amount = order.amount; std::string currency = currs[0]; return containsCurrency(currency, amount); } if (order.orderType == OrderBookType::bid) { // Buying: Need the quote currency (e.g., USDT in BTC/USDT) double amount = order.amount * order.price; // Total cost std::string currency = currs[1]; return containsCurrency(currency, amount); } return false; }

✅ Order Validation Logic

For ASK (Sell) Orders:

  • Product: "BTC/USDT", Amount: 0.5
  • Need: 0.5 BTC (base currency)
  • Check: Do we have 0.5 BTC in wallet?

For BID (Buy) Orders:

  • Product: "BTC/USDT", Price: $40,000, Amount: 0.5
  • Need: 0.5 × $40,000 = $20,000 USDT (quote currency)
  • Check: Do we have $20,000 USDT in wallet?

Wallet Display Function

std::string toString() { std::string s; for (std::pair<std::string, double> pair : currencies) { std::string currency = pair.first; double amount = pair.second; s += currency + " : " + std::to_string(amount) + "\n"; } return s; }

📊 Wallet Display

Creates a formatted string showing all balances:

BTC : 1.500000
ETH : 10.000000
USDT : 50000.000000

🎮 Main & MerkelMain - User Interface

Application Entry Point

int main() { MerkelMain app{}; app.init(); }

🚀 Program Start

Simple and clean:

  • Creates a MerkelMain instance
  • Calls init() to start the interactive simulation
  • Everything else is handled by MerkelMain

Main Loop - init() Function

void init() { int input; currentTime = orderBook.getEarliestTime(); while (true) { printMenu(); input = getUserOption(); processUserOption(input); } }

🔄 Infinite Loop

The application runs continuously:

  1. Initialize time to earliest timestamp in data
  2. Display menu options to user
  3. Get user's choice (1-6)
  4. Process the choice
  5. Repeat forever (until program closed)

Menu Display

void printMenu() { cout << "1: Print help" << endl; cout << "2: Print exchange stats" << endl; cout << "3: Make an ask" << endl; cout << "4: Make a bid" << endl; cout << "5: Print wallet" << endl; cout << "6: Continue" << endl; cout << "==============" << endl; cout << "Current time is: " << currentTime << endl; }

📋 Menu Options

  • Option 1: Help - Explains the game objective
  • Option 2: Market Stats - Shows current prices and order counts
  • Option 3: Place Ask - Sell cryptocurrency
  • Option 4: Place Bid - Buy cryptocurrency
  • Option 5: View Wallet - Check balances
  • Option 6: Advance Time - Move to next timestamp

Market Statistics Display

void printMarketStats() { for (string const &p : orderBook.getKnownProducts()) { cout << "Product: " << p << endl; vector<OrderBookEntry> entries = orderBook.getOrders( OrderBookType::ask, p, currentTime); cout << "Asks seen: " << entries.size() << endl; cout << "Max ask: " << OrderBook::getHighPrice(entries) << endl; cout << "Min ask: " << OrderBook::getLowPrice(entries) << endl; } }

📈 Market Analysis

For each trading pair (BTC/USDT, ETH/USDT, etc.):

  • Shows how many sell orders exist
  • Displays highest and lowest asking prices
  • Helps user make informed trading decisions
  • Example output:
    Product: BTC/USDT
    Asks seen: 25
    Max ask: 41500.00
    Min ask: 39800.00

Placing an Ask (Sell Order)

void enterAsk() { cout << "Make an ask - enter: product,price,amount" << endl; string input; getline(cin, input); vector<string> tokens = CSVReader::tokenise(input, ','); if (tokens.size() != 3) { cout << "Bad input!" << endl; } else { try { OrderBookEntry obe = CSVReader::stringsToOBE( tokens[1], // price tokens[2], // amount currentTime, tokens[0], // product OrderBookType::ask); if (wallet.canFulfillOrder(obe)) { cout << "Wallet looks good." << endl; orderBook.insertOrder(obe); } else { cout << "Insufficient funds." << endl; } } catch (const exception &e) { cout << "Bad input" << endl; } } }

💰 Selling Process

User Input Format: ETH/BTC,200,0.5

  • Product: ETH/BTC
  • Price: 200 (BTC per ETH)
  • Amount: 0.5 ETH

Validation Steps:

  1. Parse input into 3 tokens
  2. Create OrderBookEntry object
  3. Check wallet has 0.5 ETH
  4. If yes, add order to book
  5. If no, reject with error message

Time Advancement & Order Matching

void gotoNextTimeframe() { cout << "Going to next time frame." << endl; for (string &p : orderBook.getKnownProducts()) { cout << "matching " << p << endl; vector<OrderBookEntry> sales = orderBook.matchAsksToBids(p, currentTime); cout << "Sales: " << sales.size() << endl; for (OrderBookEntry &sale : sales) { cout << "Sale price: " << sale.price << " amount " << sale.amount << endl; } } currentTime = orderBook.getNextTime(currentTime); }

⏰ Time Progression

When user chooses option 6:

  1. For each trading pair (BTC/USDT, ETH/USDT, etc.)
  2. Run the matching algorithm
  3. Execute all possible trades
  4. Display completed sales
  5. Move to next timestamp
  6. If at end of data, wrap back to beginning

This simulates the passage of time in the market!

Member Variables

class MerkelMain { private: Wallet wallet{}; // User's cryptocurrency wallet string currentTime; // Current simulation timestamp OrderBook orderBook{"20200317.csv"}; // Market data from CSV };

🏗️ Application State

  • wallet: Tracks user's balances (initially empty)
  • currentTime: Current position in market timeline
  • orderBook: All market orders loaded from CSV file

These three components work together to create the complete trading simulation!

🎯 Complete System Flow

  1. Initialization: Load CSV data → Populate OrderBook → Set starting time
  2. User Loop: Display menu → Get input → Process action
  3. Trading: Validate wallet → Place order → Wait for matching
  4. Matching: Advance time → Match bids/asks → Execute trades → Update wallets
  5. Repeat: Continue simulation indefinitely