Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2")

enable_testing()

add_subdirectory(googletest)

set(Headers
./Limit_Order_Book/Book.hpp
./Limit_Order_Book/Limit.hpp
Expand All @@ -27,11 +25,19 @@ set(Sources

# Define the library target
add_library(${PROJECT_NAME}_lib STATIC ${Sources} ${Headers})
add_library(${PROJECT_NAME}_gen_lib STATIC ${Sources} ${Headers})

# Define the executable target
add_executable(${PROJECT_NAME} main.cpp)
add_executable(GenerateOrders generate.cpp)

# Add the definition to the executable's own compilation of the library sources
# Use a separate library target if the code logic actually changes
target_compile_definitions(${PROJECT_NAME}_gen_lib PRIVATE GENERATE)

# Link the generator to this specific "gen" version
# Link the executable with the library
target_link_libraries(GenerateOrders PRIVATE ${PROJECT_NAME}_gen_lib)
target_link_libraries(${PROJECT_NAME} PRIVATE ${PROJECT_NAME}_lib)

add_subdirectory(test)
Expand Down
24 changes: 10 additions & 14 deletions Generate_Orders/GenerateOrders.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ void GenerateOrders::modifyStopLimit()
void GenerateOrders::createOrders(int numberOfOrders)
{
// Open a file named "orders.txt" for writing
file.open("C:/Users/benja/Documents/Limit_order_book/orders.txt");
file.open("./Generate_Orders/Orders.txt");

if (!file.is_open()) {
std::cerr << "Error opening file for writing!" << std::endl;
Expand All @@ -285,8 +285,7 @@ void GenerateOrders::createOrders(int numberOfOrders)
std::uniform_real_distribution<> dis(0.0, 1.0);

// Define the probabilities and actions
std::vector<double> probabilities = {0.025, 0, 0.195, 0.295, 0.025, 0, 0.12, 0.12, 0, 0.12, 0.12};
// std::vector<double> probabilities = {0.05, 0, 0.2, 0.3, 0, 0, 0.15, 0.15, 0, 0, 0.15};
std::vector<double> probabilities = {0.025, 0.192, 0.119, 0.179, 0.025, 0.093, 0.073, 0.073, 0.09, 0.071, 0.06};
std::vector<std::function<void()>> actions = {
std::bind(&GenerateOrders::market, this),
std::bind(&GenerateOrders::addLimit, this),
Expand Down Expand Up @@ -318,16 +317,13 @@ void GenerateOrders::createOrders(int numberOfOrders)
// Perform the selected action
if (selectedAction < probabilities.size()) {
actions[selectedAction]();
//std::cout << "Selected Action: " << selectedAction << std::endl;

// if (i%100000 == 0)
// {
// std::cout << "-------------------------------------" << std::endl;
// std::cout << "Number of orders done: " << i << std::endl;

// std::cout << "Highest Stop Sell: " << book->getHighestStopSell()->getLimitPrice() << ", Lowest Stop Buy: " << book->getLowestStopBuy()->getLimitPrice() << std::endl;
// std::cout << "Lowest Sell: " << book->getLowestSell()->getLimitPrice() << ", Highest Buy: " << book->getHighestBuy()->getLimitPrice() << std::endl;
// book->printOrderBook();
// }
if (i%100000 == 0)
{
std::cout << "-------------------------------------" << std::endl;
std::cout << "Number of orders done: " << i << std::endl;
}

} else {
std::cerr << "Error: No action selected!" << std::endl;
Expand All @@ -340,7 +336,7 @@ void GenerateOrders::createOrders(int numberOfOrders)
void GenerateOrders::createInitialOrders(int numberOfOrders, int centreOfBook)
{
// Open a file named "initialOrders.txt" for writing
std::ofstream file("C:/Users/benja/Documents/Limit_order_book/initialOrders.txt");
std::ofstream file("./Generate_Orders/initialOrders.txt");

if (!file.is_open()) {
std::cerr << "Error opening file for writing!" << std::endl;
Expand Down Expand Up @@ -394,4 +390,4 @@ void GenerateOrders::createInitialOrders(int numberOfOrders, int centreOfBook)
file.close();

std::cout << "Orders written to initialOrders.txt successfully!" << std::endl;
}
}
48 changes: 36 additions & 12 deletions Limit_Order_Book/Book.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ void Book::addLimitOrder(int orderId, bool buyOrSell, int shares, int limitPrice
addLimit(limitPrice, newOrder->getBuyOrSell());
}
limitMap.at(limitPrice)->append(newOrder);
// limitOrders.insert(newOrder);
#ifdef GENERATE
limitOrders.insert(newOrder);
#endif
} else {
executeStopOrders(buyOrSell);
}
Expand All @@ -123,7 +125,9 @@ void Book::cancelLimitOrder(int orderId)
deleteLimit(order->getParentLimit());
}
deleteFromOrderMap(orderId);
// limitOrders.erase(order);
#ifdef GENERATE
limitOrders.erase(order);
#endif
delete order;
}
}
Expand Down Expand Up @@ -171,7 +175,9 @@ void Book::addStopOrder(int orderId, bool buyOrSell, int shares, int stopPrice)
addStop(stopPrice, newOrder->getBuyOrSell());
}
stopMap.at(stopPrice)->append(newOrder);
// stopOrders.insert(newOrder);
#ifdef GENERATE
stopOrders.insert(newOrder);
#endif
}
}

Expand All @@ -190,7 +196,9 @@ void Book::cancelStopOrder(int orderId)
deleteStopLevel(order->getParentLimit());
}
deleteFromOrderMap(orderId);
// stopOrders.erase(order);
#ifdef GENERATE
stopOrders.erase(order);
#endif
delete order;
}
}
Expand Down Expand Up @@ -237,7 +245,9 @@ void Book::addStopLimitOrder(int orderId, bool buyOrSell, int shares, int limitP
addStop(stopPrice, newOrder->getBuyOrSell());
}
stopMap.at(stopPrice)->append(newOrder);
// stopLimitOrders.insert(newOrder);
#ifdef GENERATE
stopLimitOrders.insert(newOrder);
#endif
}
}

Expand All @@ -255,7 +265,9 @@ void Book::cancelStopLimitOrder(int orderId)
deleteStopLevel(order->getParentLimit());
}
deleteFromOrderMap(orderId);
// stopLimitOrders.erase(order);
#ifdef GENERATE
stopLimitOrders.erase(order);
#endif
delete order;
}
}
Expand Down Expand Up @@ -936,11 +948,15 @@ void Book::executeStopOrders(bool buyOrSell)
deleteStopLevel(lowestStopBuy);
}
deleteFromOrderMap(headOrder->getOrderId());
// stopOrders.erase(headOrder);
#ifdef GENERATE
stopOrders.erase(headOrder);
#endif
delete headOrder;
marketOrderHelper(0, true, shares);
} else {
// stopLimitOrders.erase(headOrder);
#ifdef GENERATE
stopLimitOrders.erase(headOrder);
#endif
stopLimitOrderToLimitOrder(headOrder, buyOrSell);
}
}
Expand All @@ -959,11 +975,15 @@ void Book::executeStopOrders(bool buyOrSell)
deleteStopLevel(highestStopSell);
}
deleteFromOrderMap(headOrder->getOrderId());
// stopOrders.erase(headOrder);
#ifdef GENERATE
stopOrders.erase(headOrder);
#endif
delete headOrder;
marketOrderHelper(0, false, shares);
} else {
// stopLimitOrders.erase(headOrder);
#ifdef GENERATE
stopLimitOrders.erase(headOrder);
#endif
stopLimitOrderToLimitOrder(headOrder, buyOrSell);
}
}
Expand Down Expand Up @@ -993,7 +1013,9 @@ void Book::stopLimitOrderToLimitOrder(Order* headOrder, bool buyOrSell)
addLimit(headOrder->getLimit(), buyOrSell);
}
limitMap.at(headOrder->getLimit())->append(headOrder);
// limitOrders.insert(headOrder);
#ifdef GENERATE
limitOrders.insert(headOrder);
#endif
}
}

Expand All @@ -1013,7 +1035,9 @@ void Book::marketOrderHelper(int orderId, bool buyOrSell, int shares)
deleteLimit(bookEdge);
}
deleteFromOrderMap(headOrder->getOrderId());
// limitOrders.erase(headOrder);
#ifdef GENERATE
limitOrders.erase(headOrder);
#endif
delete headOrder;
executedOrdersCount += 1;
}
Expand Down
51 changes: 27 additions & 24 deletions Process_Orders/data_visualisation.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import pandas as pd
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import sys

def create_bar_chart_from_csv(csv_filename):
def create_bar_chart_from_csv(csv_filename, save_plots):
# Read the CSV file into a DataFrame
df = pd.read_csv(csv_filename, header=None, names=['Order Type', 'Times', 'Executed Orders', 'AVL Tree Balances'])

Expand All @@ -16,22 +17,16 @@ def create_bar_chart_from_csv(csv_filename):
average_time = times.mean()
trades_count = df['Executed Orders'].sum()
balances_count = df['AVL Tree Balances'].sum()
# print(f'{trades_count} {balances_count}')
# print(f"Average time: {average_time}")

# Print the 20 rows with the highest times
# top_20_times = df.nlargest(20, 'Times')
# print("\nTop 20 rows with the highest times:")
# print(top_20_times)

# Create a pie chart of the different order types
order_type_counts = orderTypes.value_counts()
order_type_counts = order_type_counts[::-1]
plt.figure(figsize=(8, 8))
plt.pie(order_type_counts, labels=order_type_counts.index, autopct='%1.1f%%', startangle=0, colors=plt.cm.tab20.colors)
plt.title('Distribution of Order Types')
# plt.show()
# plt.savefig('../figures/OrderTypes.png')
if save_plots:
plt.savefig('./figures/OrderTypes.png')
plt.show()

# Create a histogram of the order latencies
filtered_times = times[times <= 4000]
Expand All @@ -40,29 +35,32 @@ def create_bar_chart_from_csv(csv_filename):
plt.title(f'Orders by Latency Histogram - mean={average_time:.1f}ns ({1000000000/average_time:,.0f} orders/s)')
plt.xlabel('Latency (ns)')
plt.ylabel('Number of Orders')
# plt.show()
# plt.savefig('../figures/LatencyHistogram.png')
if save_plots:
plt.savefig('./figures/LatencyHistogram.png')
plt.show()

# Exclude 'Market' and 'AddMarketLimit' order types
excluded_order_types = ['Market', 'AddMarketLimit']
filtered_df = df[~df['Order Type'].isin(excluded_order_types)]

# Calculate mean, 15th, and 85th percentiles for each order type
stats = filtered_df.groupby('Order Type')['Times'].agg(['mean', lambda x: x.quantile(0.15), lambda x: x.quantile(0.85)]).reset_index()
stats.columns = ['Order Type', 'mean', '25th', '75th']
stats['error_lower'] = stats['mean'] - stats['25th']
stats['error_upper'] = stats['75th'] - stats['mean']
stats.columns = ['Order Type', 'mean', '15th', '85th']
stats['error_lower'] = stats['15th']# - stats['25th']
stats['error_upper'] = stats['85th']# - stats['mean']
stats = stats.sort_values(by='mean')


# Create a bar chart with error bars for latency for each order type
plt.figure(figsize=(12, 6))
plt.bar(stats['Order Type'], stats['mean'], yerr=[stats['error_lower'], stats['error_upper']], capsize=5, color='skyblue', edgecolor='black')
plt.title('Latency by Order Type')
plt.xlabel('Order Type')
plt.ylabel('Latency (ns)')
plt.xticks(rotation=45)
# plt.show()
# plt.savefig('../figures/OrderTypeLatencies.png', bbox_inches='tight')
if save_plots:
plt.savefig('./figures/OrderTypeLatencies.png', bbox_inches='tight')
plt.show()

# Filter for Market and AddMarketLimit order types
market_df = df[df['Order Type'].isin(['Market', 'AddMarketLimit'])]
Expand All @@ -78,8 +76,9 @@ def create_bar_chart_from_csv(csv_filename):
plt.title('Latency by Number of Trades')
plt.xlabel('Number of Trades per Order')
plt.ylabel('Latency (ns)')
# plt.show()
# plt.savefig('../figures/ExecutedOrders.png')
if save_plots:
plt.savefig('./figures/ExecutedOrders.png')
plt.show()


balance_df = df[df['AVL Tree Balances'] != 0]
Expand All @@ -95,8 +94,9 @@ def create_bar_chart_from_csv(csv_filename):
plt.title('Latency by Number of AVL Tree Balances')
plt.xlabel('Number of AVL Tree Balances')
plt.ylabel('Latency (ns)')
# plt.show()
# plt.savefig('../figures/AVLTreeBalances.png')
if save_plots:
plt.savefig('./figures/AVLTreeBalances.png')
plt.show()

# Create figure and 3D axis
fig = plt.figure(figsize=(10, 6))
Expand All @@ -120,9 +120,12 @@ def create_bar_chart_from_csv(csv_filename):
ax.set_xticks([3, 13, 23, 33, 43, 53, 63, 73])
ax.set_xticklabels([70, 60, 50, 40, 30, 20, 10, 0])
plt.title('Latency by Number of Trades and AVL Tree Balances')
# plt.show()
# plt.savefig('../figures/3D.png')
if save_plots:
plt.savefig('./figures/3D.png')
plt.show()


csv_filename = './order_processing_times.csv'
create_bar_chart_from_csv(csv_filename)
# Check if '--save' was passed in the terminal
save_plots = '--save' in sys.argv
create_bar_chart_from_csv(csv_filename, save_plots)
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,40 @@ This Limit Order Book is developed in `C++` from scratch and able to handle over

Performance testing of the order book was also quite a challenging task as it required getting order data for testing, performing the testing to collect latency statistics, and finally analysing and visualising the collected data. All functionality testing was completed thorough a set of unit tests and integration tests using `GoogleTest`.

## Build

To build the targets `LimitOrderBook`, `LimitOrderBookTests`, and `GenerateOrders`, simply run

```bash
./build.sh
```

## Generate Orders

To generate `Orders.txt` and `initialOrders.txt`, just run

```bash
./GenerateOrders
```

## Process Orders

To process `Orders.txt`, run

```bash
./LimitOrderBook
```

## Visualize Results

To produce the plots in this readme, run

```bash
python3 Process_Orders/data_visualization.py --save
```

The `--save` option is optional to save the figures in `./figures`.

## Background

### Matching Engine
Expand Down
9 changes: 9 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/bash

mkdir build
cd build
cmake ..
make
cp LimitOrderBook ../
cp GenerateOrders ../
cp ./test/LimitOrderBookTests ../
Loading