From 41bf0d9f8bfa70c52c37e90b36f622527fb02100 Mon Sep 17 00:00:00 2001 From: kwmcclintick Date: Sat, 2 May 2026 12:00:24 -0400 Subject: [PATCH 1/3] build --- CMakeLists.txt | 2 -- Generate_Orders/GenerateOrders.cpp | 9 +++++++-- build.sh | 7 +++++++ main.cpp | 19 ++++--------------- test/CMakeLists.txt | 24 +++++++++++++++++------- 5 files changed, 35 insertions(+), 26 deletions(-) create mode 100755 build.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 07a9f51..dfa11af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/Generate_Orders/GenerateOrders.cpp b/Generate_Orders/GenerateOrders.cpp index e3f3567..8030ddd 100644 --- a/Generate_Orders/GenerateOrders.cpp +++ b/Generate_Orders/GenerateOrders.cpp @@ -275,7 +275,10 @@ 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"); + std::cout << "Creating Orders..." << std::endl; + file.open("./Orders.txt"); + + std::cout << "File opened." << std::endl; if (!file.is_open()) { std::cerr << "Error opening file for writing!" << std::endl; @@ -304,6 +307,7 @@ void GenerateOrders::createOrders(int numberOfOrders) // Calculate the cumulative probabilities std::partial_sum(probabilities.begin(), probabilities.end(), probabilities.begin()); + std::cout << "Looping." << std::endl; for (size_t i = 1; i < numberOfOrders + 1; i++) { // Generate a random number between 0 and 1 @@ -317,6 +321,7 @@ void GenerateOrders::createOrders(int numberOfOrders) // Perform the selected action if (selectedAction < probabilities.size()) { + std::cout << "selected action=" << selectedAction << std::endl; actions[selectedAction](); // if (i%100000 == 0) @@ -394,4 +399,4 @@ void GenerateOrders::createInitialOrders(int numberOfOrders, int centreOfBook) file.close(); std::cout << "Orders written to initialOrders.txt successfully!" << std::endl; -} \ No newline at end of file +} diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..74aaccf --- /dev/null +++ b/build.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +mkdir build +cd build +cmake .. +make +mv LimitOrderBook ../ diff --git a/main.cpp b/main.cpp index a71f0cf..09563f0 100644 --- a/main.cpp +++ b/main.cpp @@ -11,29 +11,18 @@ int main() { Book* book = new Book(); OrderPipeline orderPipeline(book); - - // GenerateOrders generateOrders(book); - - // generateOrders.createInitialOrders(10000, 300); - - orderPipeline.processOrdersFromFile("./initialOrders.txt"); - - // generateOrders.createOrders(5000000); - + GenerateOrders generateOrders(book); + //generateOrders.createOrders(5000000); // Start measuring time auto start = std::chrono::high_resolution_clock::now(); - - orderPipeline.processOrdersFromFile("./Orders.txt"); - + orderPipeline.processOrdersFromFile("./initialOrders.txt"); // Stop measuring time auto stop = std::chrono::high_resolution_clock::now(); - // Calculate the duration auto duration = std::chrono::duration_cast(stop - start); - std::cout << "Time taken to process orders: " << duration.count() << " milliseconds" << std::endl; delete book; return 0; -} \ No newline at end of file +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 95bb4cc..db18f89 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,19 +1,29 @@ cmake_minimum_required(VERSION 3.29.0) -set(This LimitOrderBookTests) +# 1. Find GTest +find_package(GTest REQUIRED) -set(Sources - LimitOrderBookTests.cpp +# 2. Define the executable and its source files +set(This LimitOrderBookTests) +set(Sources + LimitOrderBookTests.cpp ExampleOrdersTests.cpp ) add_executable(${This} ${Sources}) -target_link_libraries(${This} PUBLIC - gtest_main + +# 3. Link libraries (Must come AFTER add_executable) +# Use GTest::gtest_main (the namespaced version) for better compatibility +target_link_libraries(${This} + PRIVATE + GTest::gtest_main LimitOrderBook_lib ) +# 4. Enable testing and add the test +enable_testing() add_test( - NAME ${This} + NAME ${This} COMMAND ${This} -) \ No newline at end of file +) + From 7492d61429d87c23ac016710e0bd11c2c0067e2b Mon Sep 17 00:00:00 2001 From: kwmcclintick Date: Thu, 7 May 2026 09:52:26 -0400 Subject: [PATCH 2/3] fixed build, generation of orders.txt, and processing script --- CMakeLists.txt | 10 +++++- Generate_Orders/GenerateOrders.cpp | 27 +++++---------- Limit_Order_Book/Book.cpp | 48 +++++++++++++++++++------- Process_Orders/data_visualisation.py | 51 +++++++++++++++------------- README.md | 34 +++++++++++++++++++ build.sh | 4 ++- generate.cpp | 21 ++++++++++++ main.cpp | 7 ++-- test/ExampleOrdersTests.cpp | 6 ++-- 9 files changed, 145 insertions(+), 63 deletions(-) create mode 100644 generate.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index dfa11af..bfdc78c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.29.0) project(LimitOrderBook VERSION 0.1.0) set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -g") enable_testing() @@ -25,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) diff --git a/Generate_Orders/GenerateOrders.cpp b/Generate_Orders/GenerateOrders.cpp index 8030ddd..e260cb4 100644 --- a/Generate_Orders/GenerateOrders.cpp +++ b/Generate_Orders/GenerateOrders.cpp @@ -275,10 +275,7 @@ void GenerateOrders::modifyStopLimit() void GenerateOrders::createOrders(int numberOfOrders) { // Open a file named "orders.txt" for writing - std::cout << "Creating Orders..." << std::endl; - file.open("./Orders.txt"); - - std::cout << "File opened." << std::endl; + file.open("./Generate_Orders/Orders.txt"); if (!file.is_open()) { std::cerr << "Error opening file for writing!" << std::endl; @@ -288,8 +285,7 @@ void GenerateOrders::createOrders(int numberOfOrders) std::uniform_real_distribution<> dis(0.0, 1.0); // Define the probabilities and actions - std::vector probabilities = {0.025, 0, 0.195, 0.295, 0.025, 0, 0.12, 0.12, 0, 0.12, 0.12}; - // std::vector probabilities = {0.05, 0, 0.2, 0.3, 0, 0, 0.15, 0.15, 0, 0, 0.15}; + std::vector 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> actions = { std::bind(&GenerateOrders::market, this), std::bind(&GenerateOrders::addLimit, this), @@ -307,7 +303,6 @@ void GenerateOrders::createOrders(int numberOfOrders) // Calculate the cumulative probabilities std::partial_sum(probabilities.begin(), probabilities.end(), probabilities.begin()); - std::cout << "Looping." << std::endl; for (size_t i = 1; i < numberOfOrders + 1; i++) { // Generate a random number between 0 and 1 @@ -321,18 +316,14 @@ void GenerateOrders::createOrders(int numberOfOrders) // Perform the selected action if (selectedAction < probabilities.size()) { - std::cout << "selected action=" << selectedAction << std::endl; 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; @@ -345,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; diff --git a/Limit_Order_Book/Book.cpp b/Limit_Order_Book/Book.cpp index 0e1e3c1..c66ce02 100644 --- a/Limit_Order_Book/Book.cpp +++ b/Limit_Order_Book/Book.cpp @@ -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); } @@ -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; } } @@ -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 } } @@ -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; } } @@ -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 } } @@ -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; } } @@ -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); } } @@ -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); } } @@ -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 } } @@ -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; } diff --git a/Process_Orders/data_visualisation.py b/Process_Orders/data_visualisation.py index 8d606e3..32cb8e9 100644 --- a/Process_Orders/data_visualisation.py +++ b/Process_Orders/data_visualisation.py @@ -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']) @@ -16,13 +17,6 @@ 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() @@ -30,8 +24,9 @@ def create_bar_chart_from_csv(csv_filename): 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] @@ -40,8 +35,9 @@ 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'] @@ -49,11 +45,12 @@ def create_bar_chart_from_csv(csv_filename): # 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') @@ -61,8 +58,9 @@ def create_bar_chart_from_csv(csv_filename): 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'])] @@ -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] @@ -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)) @@ -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) diff --git a/README.md b/README.md index 6d58e7d..10068fd 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/build.sh b/build.sh index 74aaccf..3649439 100755 --- a/build.sh +++ b/build.sh @@ -4,4 +4,6 @@ mkdir build cd build cmake .. make -mv LimitOrderBook ../ +cp LimitOrderBook ../ +cp GenerateOrders ../ +cp ./test/LimitOrderBookTests ../ diff --git a/generate.cpp b/generate.cpp new file mode 100644 index 0000000..1c3234d --- /dev/null +++ b/generate.cpp @@ -0,0 +1,21 @@ +#include "./Generate_Orders/GenerateOrders.hpp" +#include "./Process_Orders/OrderPipeline.hpp" +#include "./Limit_Order_Book/Book.hpp" +#include "./Limit_Order_Book/Limit.hpp" +#include "./Limit_Order_Book/Order.hpp" +#include +#include +#include + +int main() { + Book* book = new Book(); + OrderPipeline orderPipeline(book); + + GenerateOrders generateOrders(book); + generateOrders.createInitialOrders(10'000, 300); + orderPipeline.processOrdersFromFile("./Generate_Orders/initialOrders.txt"); + generateOrders.createOrders(5'000'000); + + delete book; + return 0; +} diff --git a/main.cpp b/main.cpp index 09563f0..20cd559 100644 --- a/main.cpp +++ b/main.cpp @@ -9,14 +9,13 @@ int main() { Book* book = new Book(); - OrderPipeline orderPipeline(book); - GenerateOrders generateOrders(book); - //generateOrders.createOrders(5000000); + + orderPipeline.processOrdersFromFile("./Generate_Orders/initialOrders.txt"); // Start measuring time auto start = std::chrono::high_resolution_clock::now(); - orderPipeline.processOrdersFromFile("./initialOrders.txt"); + orderPipeline.processOrdersFromFile("./Generate_Orders/Orders.txt"); // Stop measuring time auto stop = std::chrono::high_resolution_clock::now(); // Calculate the duration diff --git a/test/ExampleOrdersTests.cpp b/test/ExampleOrdersTests.cpp index 4942beb..f24fc2b 100644 --- a/test/ExampleOrdersTests.cpp +++ b/test/ExampleOrdersTests.cpp @@ -30,10 +30,10 @@ TEST_F(ExampleOrdersTests, CreateInitialOrdersTest) { } TEST_F(ExampleOrdersTests, ProcessInitialOrdersTest) { - orderPipeline->processOrdersFromFile("C:/Users/benja/Documents/Limit_order_book/initialOrders.txt"); + orderPipeline->processOrdersFromFile("./Generate_Orders/initialOrders.txt"); } TEST_F(ExampleOrdersTests, CreateOrdersTest) { - orderPipeline->processOrdersFromFile("C:/Users/benja/Documents/Limit_order_book/initialOrders.txt"); + orderPipeline->processOrdersFromFile("./Generate_Orders/initialOrders.txt"); generateOrders->createOrders(100000); -} \ No newline at end of file +} From e6d43e5148b8a14a13c321cdd2711835f68f7b99 Mon Sep 17 00:00:00 2001 From: kwmcclintick Date: Thu, 7 May 2026 09:53:40 -0400 Subject: [PATCH 3/3] removed -g --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bfdc78c..e35fc41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.29.0) project(LimitOrderBook VERSION 0.1.0) set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -g") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2") enable_testing()