Skip to content

Commit b0a7194

Browse files
committed
Added Order and OrderList ADT with corresponding tests
1 parent 9709585 commit b0a7194

11 files changed

+426
-0
lines changed

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Ignore CMake Debug and Release builds
2+
cmake-build-release
3+
cmake-build-debug
4+
# Ignore .idea folder generated by CLion
5+
.idea

CMakeLists.txt

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
cmake_minimum_required(VERSION 3.5)
2+
project(fast_exchange)
3+
set(CMAKE_CXX_STANDARD 17)
4+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -O3")
5+
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/bin")
6+
set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR})
7+
set(EXCHANGE_INSTALL_LIB_DIR ${PROJECT_SOURCE_DIR}/lib)
8+
set(EXCHANGE_INSTALL_BIN_DIR ${CMAKE_INSTALL_BINDIR})
9+
add_subdirectory(src)
10+
enable_testing()
11+
add_subdirectory(test)

bin/.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
*
2+
*/
3+
!.gitignore

src/CMakeLists.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
cmake_minimum_required(VERSION 3.5)
2+
project(fast_exchange_source)
3+
add_subdirectory(order_book)

src/order_book/CMakeLists.txt

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
cmake_minimum_required(VERSION 3.5)
2+
project(order_book)
3+
find_package(Boost)
4+
if(Boost_FOUND)
5+
add_library(${PROJECT_NAME} src/order_list.cpp)
6+
target_include_directories(${PROJECT_NAME} PUBLIC include ${Boost_INCLUDE_DIRS})
7+
endif()

src/order_book/include/order.h

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#ifndef FAST_EXCHANGE_ORDER_H
2+
#define FAST_EXCHANGE_ORDER_H
3+
#include <chrono>
4+
#include <string>
5+
#include <utility>
6+
#include <array>
7+
#include <stdexcept>
8+
#include <numeric>
9+
#include <cmath>
10+
#include <iomanip>
11+
#include <boost/intrusive/list.hpp>
12+
13+
// Represents the different actions of orders.
14+
enum class OrderAction {Limit = 0, Market = 1};
15+
constexpr std::array order_action_to_string {"LIMIT", "MARKET"};
16+
17+
//Represents the different types of orders.
18+
// Good 'Til Cancelled: A good-til-canceled order will remain active until
19+
// you decide to cancel it.
20+
// Fill Or Kill: A fill-or-kill order will be executed immediately in its entirety;
21+
// otherwise, it will be cancelled.
22+
// Immediate or Cancel: A immediate-or-cancel order will be executed immediately
23+
// as fully as possible. Non-executed parts of the order are deleted
24+
// without entry in the order book.
25+
enum class OrderType {GoodTillCancel = 0, FillOrKill = 1, ImmediateOrCancel = 2};
26+
constexpr std::array order_type_to_string {"GTC", "FOK", "IOC"};
27+
28+
// Represents the side of the order.
29+
enum class OrderSide {Bid = 0, Ask = 1};
30+
constexpr std::array order_side_to_string {"BID", "ASK"};
31+
32+
// Represents the status of an order.
33+
enum class OrderStatus {Accepted = 0, Rejected = 1, PartiallyFilled = 1, Filled = 2, Cancelled = 3};
34+
constexpr std::array order_status_to_string {"ACCEPTED", "REJECTED", "PARTIALLY FILLED", "FILLED", "CANCELLED"};
35+
36+
using namespace boost::intrusive;
37+
38+
/**
39+
* An mutable order ADT.
40+
*/
41+
struct Order: public list_base_hook<> {
42+
const OrderAction action;
43+
const OrderSide side;
44+
const OrderType type;
45+
const uint64_t user_id;
46+
const uint64_t id;
47+
const std::string symbol;
48+
const std::chrono::time_point<std::chrono::system_clock> time;
49+
const float price;
50+
OrderStatus status;
51+
uint64_t quantity;
52+
53+
/**
54+
* A constructor for the order ADT.
55+
* TODO: When is an order rejected?
56+
*
57+
* @param order_action the action of the order - limit or market.
58+
* @param order_side the side of the order - ask or bid.
59+
* @param order_type the type of the order - GTC, IOC, FOK.
60+
* @param order_quantity the quantity of the order, require that quantity is positive.
61+
* @param order_price the price of the order, require that price is non-negative.
62+
* @param order_id a unique ID associated with the order.
63+
* @param order_user_id a unique ID associated with the user placing the order.
64+
* @throws Error if price is negative or if quantity is not positive.
65+
*/
66+
Order(OrderAction order_action, OrderSide order_side, OrderType order_type, uint64_t order_quantity, float order_price,
67+
uint64_t order_id, uint64_t order_user_id)
68+
: action(order_action), side(order_side), type(order_type), status(OrderStatus::Accepted), quantity(order_quantity),
69+
price(order_price), id(order_id), user_id(order_user_id), time(std::chrono::system_clock::now())
70+
{
71+
if (quantity == 0)
72+
throw std::invalid_argument("quantity must be positive!");
73+
if (price < 0)
74+
throw std::invalid_argument("price must be non-negative!");
75+
}
76+
77+
/**
78+
* Indicates whether two orders are equal. Two orders are equal if and only if they have
79+
* the same status, the same side, the same ID, and the same price (within epsilon).
80+
*
81+
* @param other another order.
82+
* @return true if the orders are equal and false otherwise.
83+
*/
84+
bool operator==(const Order& other) const {
85+
return status == other.status &&
86+
action == other.action &&
87+
type == other.type &&
88+
side == other.side &&
89+
id == other.id &&
90+
user_id == other.user_id &&
91+
quantity == other.quantity &&
92+
(std::fabs(price - other.price) <=
93+
std::numeric_limits<float>::epsilon() * std::fmax(std::fabs(price), std::fabs(other.price)));
94+
}
95+
96+
bool operator!=(const Order& other) const {
97+
return !(*this == other);
98+
}
99+
/**
100+
* @return the string representation of an order.
101+
*/
102+
std::string toString() {
103+
auto order_time = std::chrono::system_clock::to_time_t(time);
104+
std::string time_string = std::ctime(&order_time);
105+
std::string order_action = order_action_to_string[static_cast<std::underlying_type<OrderStatus>::type>(action)];
106+
std::string order_side = order_side_to_string[static_cast<std::underlying_type<OrderStatus>::type>(side)];
107+
std::string order_status = order_status_to_string[static_cast<std::underlying_type<OrderStatus>::type>(status)];
108+
std::string order_type = order_type_to_string[static_cast<std::underlying_type<OrderStatus>::type>(type)];
109+
std::string order_price = std::to_string(price);
110+
std::string order_quantity = std::to_string(quantity);
111+
std::string order_id = std::to_string(id);
112+
std::string order_user_id = std::to_string(user_id);
113+
114+
return "Order ID: " + order_id +
115+
"\nUser ID: " + order_user_id +
116+
"\nOrder Time: " + time_string +
117+
"\nOrder Side: " + order_side +
118+
"\nOrder Action: " + order_action +
119+
"\nOrder Status: " + order_status +
120+
"\nOrder Type: " + order_type +
121+
"\nOrder Price: " + order_price +
122+
"\nOrder Quantity: " + order_quantity + "\n";
123+
}
124+
};
125+
#endif //FAST_EXCHANGE_ORDER_H

src/order_book/include/order_list.h

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#ifndef FAST_EXCHANGE_ORDER_LIST_H
2+
#define FAST_EXCHANGE_ORDER_LIST_H
3+
#include <list>
4+
#include <unordered_map>
5+
#include "order.h"
6+
7+
using namespace boost::intrusive;
8+
9+
/**
10+
* A doubly linked list of orders that are associated with a single price.
11+
*/
12+
class OrderList {
13+
public:
14+
/**
15+
* A constructor for the OrderList ADT, initializes
16+
* an empty OrderList.
17+
*/
18+
OrderList() = default;
19+
20+
/**
21+
* A constructor for the OrderList ADT, initializes
22+
* an order list with a single order.
23+
*
24+
* @param order an order.
25+
*/
26+
explicit OrderList(Order& order);
27+
28+
/**
29+
* Adds an order to the front of the order list if it is not
30+
* already in the list and it has the same price level.
31+
*
32+
* @param order an order, require that price associated with
33+
* the order is equal to the price of the order
34+
* list.
35+
* @throws Error if the order does not have the same price as
36+
* the order list.
37+
*/
38+
void addOrder(Order& order);
39+
40+
/**
41+
* Removes the order associated with order_id
42+
* from the list - does not cancel the order.
43+
*
44+
* @param order_id the ID of the order to remove, require
45+
* that the order associated with ID
46+
* is in the order list.
47+
* @return the order that was removed from the list.
48+
* @throws Error if the order associated with order_id
49+
* is not in the order list.
50+
*/
51+
void removeOrder(const Order& order);
52+
53+
/**
54+
* Given an order ID, determines whether the order associated with that ID
55+
* is in the order list.
56+
*
57+
* @param order_id an ID associated with an order.
58+
* @return true if the order associated with order_id is in the order list
59+
* and false otherwise.
60+
*/
61+
bool hasOrder(const Order& order);
62+
63+
/**
64+
* @returns true if the order list is empty and false otherwise.
65+
*/
66+
bool isEmpty();
67+
68+
/**
69+
* @returns the number of orders that are in the order list.
70+
*/
71+
size_t size();
72+
73+
private:
74+
// A doubly-linked list of orders.
75+
list<Order> order_list;
76+
};
77+
#endif //FAST_EXCHANGE_ORDER_LIST_H

src/order_book/src/order_list.cpp

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#include <algorithm>
2+
#include "order_list.h"
3+
4+
OrderList::OrderList(Order& order) {
5+
order_list.push_back(order);
6+
}
7+
8+
void OrderList::addOrder(Order& order) {
9+
order_list.push_back(order);
10+
}
11+
12+
void OrderList::removeOrder(const Order &order) {
13+
auto order_it = order_list.iterator_to(order);
14+
// Require that order is in list.
15+
assert(order_it != order_list.end());
16+
order_list.erase(order_it);
17+
}
18+
bool OrderList::isEmpty() {
19+
return order_list.empty();
20+
}
21+
22+
size_t OrderList::size() {
23+
return order_list.size();
24+
}
25+
26+
bool OrderList::hasOrder(const Order& order) {
27+
return std::find(order_list.begin(), order_list.end(), order) != order_list.end();
28+
}

test/CMakeLists.txt

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
cmake_minimum_required(VERSION 3.8)
2+
project(fast_exchange_tests)
3+
find_package(GTest REQUIRED)
4+
add_executable(fast_exchange_tests order_test.cpp order_list_test.cpp)
5+
target_link_libraries(fast_exchange_tests GTest::GTest GTest::Main order_book)
6+
install(TARGETS fast_exchange_tests DESTINATION ${EXCHANGE_INSTALL_BIN_DIR})
7+
include(GoogleTest)
8+
gtest_discover_tests(fast_exchange_tests)

test/order_list_test.cpp

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#include <gtest/gtest.h>
2+
#include "order_list.h"
3+
4+
TEST(OrderListTest, HandlesAddAndRemoveOrder) {
5+
const auto order1_action = OrderAction::Limit;
6+
const auto order1_side = OrderSide::Bid;
7+
const auto order1_type = OrderType::GoodTillCancel;
8+
const auto order1_quantity = 301;
9+
const auto order1_price = 69.54;
10+
const auto order1_id = 1;
11+
const auto order1_user_id = 1;
12+
const auto order2_action = OrderAction::Limit;
13+
const auto order2_side = OrderSide::Bid;
14+
const auto order2_type = OrderType::GoodTillCancel;
15+
const auto order2_quantity = 245;
16+
const auto order2_price = 69.54;
17+
const auto order2_id = 2;
18+
const auto order2_user_id = 2;
19+
const auto order3_action = OrderAction::Limit;
20+
const auto order3_side = OrderSide::Bid;
21+
const auto order3_type = OrderType::GoodTillCancel;
22+
const auto order3_quantity = 657;
23+
const auto order3_price = 220.32;
24+
const auto order3_id = 3;
25+
const auto order3_user_id = 3;
26+
const auto expected_size1 = 1;
27+
const auto expected_size2 = 2;
28+
const auto expected_size3 = 3;
29+
const auto expected_size4 = 0;
30+
auto order1 = Order(order1_action, order1_side, order1_type, order1_quantity,
31+
order1_price, order1_id, order1_user_id);
32+
auto order2 = Order(order2_action, order2_side, order2_type, order2_quantity,
33+
order2_price, order2_id, order2_user_id);
34+
auto order3 = Order(order3_action, order3_side, order3_type, order3_quantity,
35+
order3_price, order3_id, order3_user_id);
36+
OrderList order_list;
37+
38+
// Add the orders to the list.
39+
order_list.addOrder(order1);
40+
ASSERT_TRUE(order_list.size() == expected_size1);
41+
ASSERT_TRUE(order_list.hasOrder(order1));
42+
order_list.addOrder(order2);
43+
ASSERT_TRUE(order_list.size() == expected_size2);
44+
ASSERT_TRUE(order_list.hasOrder(order2));
45+
order_list.addOrder(order3);
46+
ASSERT_TRUE(order_list.size() == expected_size3);
47+
ASSERT_TRUE(order_list.hasOrder(order3));
48+
// Remove orders from the list.
49+
order_list.removeOrder(order1);
50+
ASSERT_TRUE(order_list.size() == expected_size2);
51+
ASSERT_TRUE(!order_list.hasOrder(order1));
52+
order_list.removeOrder(order2);
53+
ASSERT_TRUE(order_list.size() == expected_size1);
54+
ASSERT_TRUE(!order_list.hasOrder(order2));
55+
order_list.removeOrder(order3);
56+
ASSERT_TRUE(order_list.size() == expected_size4);
57+
ASSERT_TRUE(!order_list.hasOrder(order3));
58+
}
59+
60+
TEST(OrderListTest, HandlesIsEmptyCorrectly) {
61+
const auto order_action = OrderAction::Limit;
62+
const auto order_side = OrderSide::Bid;
63+
const auto order_type = OrderType::GoodTillCancel;
64+
const auto order_quantity = 301;
65+
const auto order_price = 69.54;
66+
const auto order_id = 1;
67+
const auto order_user_id = 1;
68+
auto order = Order(order_action, order_side, order_type, order_quantity, order_price, order_id, order_user_id);
69+
auto order_list = OrderList(order);
70+
ASSERT_TRUE(!order_list.isEmpty());
71+
order_list.removeOrder(order);
72+
ASSERT_TRUE(order_list.isEmpty());
73+
}

0 commit comments

Comments
 (0)