├── .gitignore ├── Chapter10 ├── CMakeLists.txt ├── common │ ├── CMakeLists.txt │ ├── lf_queue.h │ ├── lf_queue_example.cpp │ ├── logging.h │ ├── logging_example.cpp │ ├── macros.h │ ├── mcast_socket.cpp │ ├── mcast_socket.h │ ├── mem_pool.h │ ├── mem_pool_example.cpp │ ├── socket_example.cpp │ ├── socket_utils.h │ ├── tcp_server.cpp │ ├── tcp_server.h │ ├── tcp_socket.cpp │ ├── tcp_socket.h │ ├── thread_example.cpp │ ├── thread_utils.h │ ├── time_utils.h │ └── types.h ├── exchange │ ├── CMakeLists.txt │ ├── exchange_main.cpp │ ├── market_data │ │ ├── market_data_publisher.cpp │ │ ├── market_data_publisher.h │ │ ├── market_update.h │ │ ├── snapshot_synthesizer.cpp │ │ └── snapshot_synthesizer.h │ ├── matcher │ │ ├── matching_engine.cpp │ │ ├── matching_engine.h │ │ ├── me_order.cpp │ │ ├── me_order.h │ │ ├── me_order_book.cpp │ │ └── me_order_book.h │ └── order_server │ │ ├── client_request.h │ │ ├── client_response.h │ │ ├── fifo_sequencer.h │ │ ├── order_server.cpp │ │ └── order_server.h ├── scripts │ ├── build.sh │ ├── no_clean_build.sh │ ├── run_clients.sh │ └── run_exchange_and_clients.sh └── trading │ ├── CMakeLists.txt │ ├── market_data │ ├── market_data_consumer.cpp │ └── market_data_consumer.h │ ├── order_gw │ ├── order_gateway.cpp │ └── order_gateway.h │ ├── strategy │ ├── feature_engine.h │ ├── liquidity_taker.cpp │ ├── liquidity_taker.h │ ├── market_maker.cpp │ ├── market_maker.h │ ├── market_order.cpp │ ├── market_order.h │ ├── market_order_book.cpp │ ├── market_order_book.h │ ├── om_order.h │ ├── order_manager.cpp │ ├── order_manager.h │ ├── position_keeper.h │ ├── risk_manager.cpp │ ├── risk_manager.h │ ├── trade_engine.cpp │ └── trade_engine.h │ └── trading_main.cpp ├── Chapter11 ├── CMakeLists.txt ├── common │ ├── CMakeLists.txt │ ├── lf_queue.h │ ├── lf_queue_example.cpp │ ├── logging.h │ ├── logging_example.cpp │ ├── macros.h │ ├── mcast_socket.cpp │ ├── mcast_socket.h │ ├── mem_pool.h │ ├── mem_pool_example.cpp │ ├── perf_utils.h │ ├── socket_example.cpp │ ├── socket_utils.h │ ├── tcp_server.cpp │ ├── tcp_server.h │ ├── tcp_socket.cpp │ ├── tcp_socket.h │ ├── thread_example.cpp │ ├── thread_utils.h │ ├── time_utils.h │ └── types.h ├── exchange │ ├── CMakeLists.txt │ ├── exchange_main.cpp │ ├── market_data │ │ ├── market_data_publisher.cpp │ │ ├── market_data_publisher.h │ │ ├── market_update.h │ │ ├── snapshot_synthesizer.cpp │ │ └── snapshot_synthesizer.h │ ├── matcher │ │ ├── matching_engine.cpp │ │ ├── matching_engine.h │ │ ├── me_order.cpp │ │ ├── me_order.h │ │ ├── me_order_book.cpp │ │ └── me_order_book.h │ └── order_server │ │ ├── client_request.h │ │ ├── client_response.h │ │ ├── fifo_sequencer.h │ │ ├── order_server.cpp │ │ └── order_server.h ├── scripts │ ├── build.sh │ ├── no_clean_build.sh │ ├── run_clients.sh │ └── run_exchange_and_clients.sh └── trading │ ├── CMakeLists.txt │ ├── market_data │ ├── market_data_consumer.cpp │ └── market_data_consumer.h │ ├── order_gw │ ├── order_gateway.cpp │ └── order_gateway.h │ ├── strategy │ ├── feature_engine.h │ ├── liquidity_taker.cpp │ ├── liquidity_taker.h │ ├── market_maker.cpp │ ├── market_maker.h │ ├── market_order.cpp │ ├── market_order.h │ ├── market_order_book.cpp │ ├── market_order_book.h │ ├── om_order.h │ ├── order_manager.cpp │ ├── order_manager.h │ ├── position_keeper.h │ ├── risk_manager.cpp │ ├── risk_manager.h │ ├── trade_engine.cpp │ └── trade_engine.h │ └── trading_main.cpp ├── Chapter12 ├── CMakeLists.txt ├── benchmarks │ ├── CMakeLists.txt │ ├── hash_benchmark.cpp │ ├── logger_benchmark.cpp │ └── release_benchmark.cpp ├── common │ ├── CMakeLists.txt │ ├── lf_queue.h │ ├── lf_queue_example.cpp │ ├── logging.h │ ├── logging_example.cpp │ ├── macros.h │ ├── mcast_socket.cpp │ ├── mcast_socket.h │ ├── mem_pool.h │ ├── mem_pool_example.cpp │ ├── opt_logging.h │ ├── opt_mem_pool.h │ ├── perf_utils.h │ ├── socket_example.cpp │ ├── socket_utils.h │ ├── tcp_server.cpp │ ├── tcp_server.h │ ├── tcp_socket.cpp │ ├── tcp_socket.h │ ├── thread_example.cpp │ ├── thread_utils.h │ ├── time_utils.h │ └── types.h ├── exchange │ ├── CMakeLists.txt │ ├── exchange_main.cpp │ ├── market_data │ │ ├── market_data_publisher.cpp │ │ ├── market_data_publisher.h │ │ ├── market_update.h │ │ ├── snapshot_synthesizer.cpp │ │ └── snapshot_synthesizer.h │ ├── matcher │ │ ├── matching_engine.cpp │ │ ├── matching_engine.h │ │ ├── me_order.cpp │ │ ├── me_order.h │ │ ├── me_order_book.cpp │ │ ├── me_order_book.h │ │ ├── unordered_map_me_order_book.cpp │ │ └── unordered_map_me_order_book.h │ └── order_server │ │ ├── client_request.h │ │ ├── client_response.h │ │ ├── fifo_sequencer.h │ │ ├── order_server.cpp │ │ └── order_server.h ├── notebooks │ └── perf_analysis.ipynb ├── perf_analysis.html ├── scripts │ ├── build.sh │ ├── no_clean_build.sh │ ├── run_benchmarks.sh │ ├── run_clients.sh │ └── run_exchange_and_clients.sh └── trading │ ├── CMakeLists.txt │ ├── market_data │ ├── market_data_consumer.cpp │ └── market_data_consumer.h │ ├── order_gw │ ├── order_gateway.cpp │ └── order_gateway.h │ ├── strategy │ ├── feature_engine.h │ ├── liquidity_taker.cpp │ ├── liquidity_taker.h │ ├── market_maker.cpp │ ├── market_maker.h │ ├── market_order.cpp │ ├── market_order.h │ ├── market_order_book.cpp │ ├── market_order_book.h │ ├── om_order.h │ ├── order_manager.cpp │ ├── order_manager.h │ ├── position_keeper.h │ ├── risk_manager.cpp │ ├── risk_manager.h │ ├── trade_engine.cpp │ └── trade_engine.h │ └── trading_main.cpp ├── Chapter3 ├── alignment.cpp ├── branch.cpp ├── composition.cpp ├── crtp.cpp ├── induction.cpp ├── loop_invariant.cpp ├── loop_unroll.cpp ├── pointer_alias.cpp ├── rvo.cpp ├── strength.cpp ├── strict_alias.cpp ├── tail_call.cpp └── vector.cpp ├── Chapter4 ├── CMakeLists.txt ├── build.sh ├── lf_queue.h ├── lf_queue_example.cpp ├── logging.h ├── logging_example.cpp ├── macros.h ├── mcast_socket.cpp ├── mcast_socket.h ├── mem_pool.h ├── mem_pool_example.cpp ├── run_examples.sh ├── socket_example.cpp ├── socket_utils.h ├── tcp_server.cpp ├── tcp_server.h ├── tcp_socket.cpp ├── tcp_socket.h ├── thread_example.cpp ├── thread_utils.h └── time_utils.h ├── Chapter6 ├── CMakeLists.txt ├── build.sh ├── common │ ├── CMakeLists.txt │ ├── lf_queue.h │ ├── logging.cpp │ ├── logging.h │ ├── macros.h │ ├── mem_pool.h │ ├── thread_utils.h │ ├── time_utils.h │ └── types.h └── exchange │ ├── CMakeLists.txt │ ├── exchange_main.cpp │ ├── market_data │ └── market_update.h │ ├── matcher │ ├── matching_engine.cpp │ ├── matching_engine.h │ ├── me_order.cpp │ ├── me_order.h │ ├── me_order_book.cpp │ └── me_order_book.h │ └── order_server │ ├── client_request.h │ └── client_response.h ├── Chapter7 ├── CMakeLists.txt ├── build.sh ├── common │ ├── CMakeLists.txt │ ├── lf_queue.h │ ├── lf_queue_example.cpp │ ├── logging.h │ ├── logging_example.cpp │ ├── macros.h │ ├── mcast_socket.cpp │ ├── mcast_socket.h │ ├── mem_pool.h │ ├── mem_pool_example.cpp │ ├── socket_example.cpp │ ├── socket_utils.h │ ├── tcp_server.cpp │ ├── tcp_server.h │ ├── tcp_socket.cpp │ ├── tcp_socket.h │ ├── thread_example.cpp │ ├── thread_utils.h │ ├── time_utils.h │ └── types.h └── exchange │ ├── CMakeLists.txt │ ├── exchange_main.cpp │ ├── market_data │ ├── market_data_publisher.cpp │ ├── market_data_publisher.h │ ├── market_update.h │ ├── snapshot_synthesizer.cpp │ └── snapshot_synthesizer.h │ ├── matcher │ ├── matching_engine.cpp │ ├── matching_engine.h │ ├── me_order.cpp │ ├── me_order.h │ ├── me_order_book.cpp │ └── me_order_book.h │ └── order_server │ ├── client_request.h │ ├── client_response.h │ ├── fifo_sequencer.h │ ├── order_server.cpp │ └── order_server.h ├── Chapter8 ├── CMakeLists.txt ├── common │ ├── CMakeLists.txt │ ├── lf_queue.h │ ├── lf_queue_example.cpp │ ├── logging.h │ ├── logging_example.cpp │ ├── macros.h │ ├── mcast_socket.cpp │ ├── mcast_socket.h │ ├── mem_pool.h │ ├── mem_pool_example.cpp │ ├── socket_example.cpp │ ├── socket_utils.h │ ├── tcp_server.cpp │ ├── tcp_server.h │ ├── tcp_socket.cpp │ ├── tcp_socket.h │ ├── thread_example.cpp │ ├── thread_utils.h │ ├── time_utils.h │ └── types.h ├── exchange │ ├── CMakeLists.txt │ ├── exchange_main.cpp │ ├── market_data │ │ ├── market_data_publisher.cpp │ │ ├── market_data_publisher.h │ │ ├── market_update.h │ │ ├── snapshot_synthesizer.cpp │ │ └── snapshot_synthesizer.h │ ├── matcher │ │ ├── matching_engine.cpp │ │ ├── matching_engine.h │ │ ├── me_order.cpp │ │ ├── me_order.h │ │ ├── me_order_book.cpp │ │ └── me_order_book.h │ └── order_server │ │ ├── client_request.h │ │ ├── client_response.h │ │ ├── fifo_sequencer.h │ │ ├── order_server.cpp │ │ └── order_server.h ├── scripts │ ├── build.sh │ └── no_clean_build.sh └── trading │ ├── CMakeLists.txt │ ├── market_data │ ├── market_data_consumer.cpp │ └── market_data_consumer.h │ ├── order_gw │ ├── order_gateway.cpp │ └── order_gateway.h │ └── strategy │ ├── feature_engine.h │ ├── liquidity_taker.cpp │ ├── liquidity_taker.h │ ├── market_maker.cpp │ ├── market_maker.h │ ├── market_order.cpp │ ├── market_order.h │ ├── market_order_book.cpp │ ├── market_order_book.h │ ├── om_order.h │ ├── order_manager.cpp │ ├── order_manager.h │ ├── position_keeper.h │ ├── risk_manager.cpp │ ├── risk_manager.h │ ├── trade_engine.cpp │ └── trade_engine.h ├── Chapter9 ├── CMakeLists.txt ├── common │ ├── CMakeLists.txt │ ├── lf_queue.h │ ├── lf_queue_example.cpp │ ├── logging.h │ ├── logging_example.cpp │ ├── macros.h │ ├── mcast_socket.cpp │ ├── mcast_socket.h │ ├── mem_pool.h │ ├── mem_pool_example.cpp │ ├── socket_example.cpp │ ├── socket_utils.h │ ├── tcp_server.cpp │ ├── tcp_server.h │ ├── tcp_socket.cpp │ ├── tcp_socket.h │ ├── thread_example.cpp │ ├── thread_utils.h │ ├── time_utils.h │ └── types.h ├── exchange │ ├── CMakeLists.txt │ ├── exchange_main.cpp │ ├── market_data │ │ ├── market_data_publisher.cpp │ │ ├── market_data_publisher.h │ │ ├── market_update.h │ │ ├── snapshot_synthesizer.cpp │ │ └── snapshot_synthesizer.h │ ├── matcher │ │ ├── matching_engine.cpp │ │ ├── matching_engine.h │ │ ├── me_order.cpp │ │ ├── me_order.h │ │ ├── me_order_book.cpp │ │ └── me_order_book.h │ └── order_server │ │ ├── client_request.h │ │ ├── client_response.h │ │ ├── fifo_sequencer.h │ │ ├── order_server.cpp │ │ └── order_server.h ├── scripts │ ├── build.sh │ └── no_clean_build.sh └── trading │ ├── CMakeLists.txt │ ├── market_data │ ├── market_data_consumer.cpp │ └── market_data_consumer.h │ ├── order_gw │ ├── order_gateway.cpp │ └── order_gateway.h │ └── strategy │ ├── feature_engine.h │ ├── liquidity_taker.cpp │ ├── liquidity_taker.h │ ├── market_maker.cpp │ ├── market_maker.h │ ├── market_order.cpp │ ├── market_order.h │ ├── market_order_book.cpp │ ├── market_order_book.h │ ├── om_order.h │ ├── order_manager.cpp │ ├── order_manager.h │ ├── position_keeper.h │ ├── risk_manager.cpp │ ├── risk_manager.h │ ├── trade_engine.cpp │ └── trade_engine.h ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | *~ 35 | .idea 36 | cmake-build* 37 | *.log 38 | -------------------------------------------------------------------------------- /Chapter10/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(LowLatencyApp) 4 | 5 | set(CMAKE_CXX_STANDARD 20) 6 | set(CMAKE_CXX_COMPILER g++) 7 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 8 | set(CMAKE_VERBOSE_MAKEFILE on) 9 | 10 | add_subdirectory(common) 11 | add_subdirectory(exchange) 12 | add_subdirectory(trading) 13 | 14 | list(APPEND LIBS libexchange) 15 | list(APPEND LIBS libtrading) 16 | list(APPEND LIBS libcommon) 17 | list(APPEND LIBS pthread) 18 | 19 | include_directories(${PROJECT_SOURCE_DIR}) 20 | include_directories(${PROJECT_SOURCE_DIR}/exchange) 21 | include_directories(${PROJECT_SOURCE_DIR}/trading) 22 | 23 | add_executable(exchange_main exchange/exchange_main.cpp) 24 | target_link_libraries(exchange_main PUBLIC ${LIBS}) 25 | 26 | add_executable(trading_main trading/trading_main.cpp) 27 | target_link_libraries(trading_main PUBLIC ${LIBS}) 28 | -------------------------------------------------------------------------------- /Chapter10/common/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | set(CMAKE_CXX_COMPILER g++) 3 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 4 | set(CMAKE_VERBOSE_MAKEFILE on) 5 | 6 | file(GLOB SOURCES "*.cpp") 7 | 8 | include_directories(${PROJECT_SOURCE_DIR}) 9 | 10 | add_library(libcommon STATIC ${SOURCES}) 11 | 12 | list(APPEND LIBS libcommon) 13 | list(APPEND LIBS pthread) 14 | 15 | add_executable(thread_example thread_example.cpp) 16 | target_link_libraries(thread_example PUBLIC ${LIBS}) 17 | 18 | add_executable(mem_pool_example mem_pool_example.cpp) 19 | target_link_libraries(mem_pool_example PUBLIC ${LIBS}) 20 | 21 | add_executable(lf_queue_example lf_queue_example.cpp) 22 | target_link_libraries(lf_queue_example PUBLIC ${LIBS}) 23 | 24 | add_executable(logging_example logging_example.cpp) 25 | target_link_libraries(logging_example PUBLIC ${LIBS}) 26 | 27 | add_executable(socket_example socket_example.cpp) 28 | target_link_libraries(socket_example PUBLIC ${LIBS}) 29 | -------------------------------------------------------------------------------- /Chapter10/common/lf_queue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "macros.h" 8 | 9 | namespace Common { 10 | template 11 | class LFQueue final { 12 | public: 13 | explicit LFQueue(std::size_t num_elems) : 14 | store_(num_elems, T()) /* pre-allocation of vector storage. */ { 15 | } 16 | 17 | auto getNextToWriteTo() noexcept { 18 | return &store_[next_write_index_]; 19 | } 20 | 21 | auto updateWriteIndex() noexcept { 22 | next_write_index_ = (next_write_index_ + 1) % store_.size(); 23 | num_elements_++; 24 | } 25 | 26 | auto getNextToRead() const noexcept -> const T * { 27 | return (size() ? &store_[next_read_index_] : nullptr); 28 | } 29 | 30 | auto updateReadIndex() noexcept { 31 | next_read_index_ = (next_read_index_ + 1) % store_.size(); // wrap around at the end of container size. 32 | ASSERT(num_elements_ != 0, "Read an invalid element in:" + std::to_string(pthread_self())); 33 | num_elements_--; 34 | } 35 | 36 | auto size() const noexcept { 37 | return num_elements_.load(); 38 | } 39 | 40 | /// Deleted default, copy & move constructors and assignment-operators. 41 | LFQueue() = delete; 42 | 43 | LFQueue(const LFQueue &) = delete; 44 | 45 | LFQueue(const LFQueue &&) = delete; 46 | 47 | LFQueue &operator=(const LFQueue &) = delete; 48 | 49 | LFQueue &operator=(const LFQueue &&) = delete; 50 | 51 | private: 52 | /// Underlying container of data accessed in FIFO order. 53 | std::vector store_; 54 | 55 | /// Atomic trackers for next index to write new data to and read new data from. 56 | std::atomic next_write_index_ = {0}; 57 | std::atomic next_read_index_ = {0}; 58 | 59 | std::atomic num_elements_ = {0}; 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /Chapter10/common/lf_queue_example.cpp: -------------------------------------------------------------------------------- 1 | #include "thread_utils.h" 2 | #include "lf_queue.h" 3 | 4 | struct MyStruct { 5 | int d_[3]; 6 | }; 7 | 8 | using namespace Common; 9 | 10 | auto consumeFunction(LFQueue* lfq) { 11 | using namespace std::literals::chrono_literals; 12 | std::this_thread::sleep_for(5s); 13 | 14 | while(lfq->size()) { 15 | const auto d = lfq->getNextToRead(); 16 | lfq->updateReadIndex(); 17 | 18 | std::cout << "consumeFunction read elem:" << d->d_[0] << "," << d->d_[1] << "," << d->d_[2] << " lfq-size:" << lfq->size() << std::endl; 19 | 20 | std::this_thread::sleep_for(1s); 21 | } 22 | 23 | std::cout << "consumeFunction exiting." << std::endl; 24 | } 25 | 26 | int main(int, char **) { 27 | LFQueue lfq(20); 28 | 29 | auto ct = createAndStartThread(-1, "", consumeFunction, &lfq); 30 | 31 | for(auto i = 0; i < 50; ++i) { 32 | const MyStruct d{i, i * 10, i * 100}; 33 | *(lfq.getNextToWriteTo()) = d; 34 | lfq.updateWriteIndex(); 35 | 36 | std::cout << "main constructed elem:" << d.d_[0] << "," << d.d_[1] << "," << d.d_[2] << " lfq-size:" << lfq.size() << std::endl; 37 | 38 | using namespace std::literals::chrono_literals; 39 | std::this_thread::sleep_for(1s); 40 | } 41 | 42 | ct->join(); 43 | 44 | std::cout << "main exiting." << std::endl; 45 | 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /Chapter10/common/logging_example.cpp: -------------------------------------------------------------------------------- 1 | #include "logging.h" 2 | 3 | int main(int, char **) { 4 | using namespace Common; 5 | 6 | char c = 'd'; 7 | int i = 3; 8 | unsigned long ul = 65; 9 | float f = 3.4; 10 | double d = 34.56; 11 | const char* s = "test C-string"; 12 | std::string ss = "test string"; 13 | 14 | Logger logger("logging_example.log"); 15 | 16 | logger.log("Logging a char:% an int:% and an unsigned:%\n", c, i, ul); 17 | logger.log("Logging a float:% and a double:%\n", f, d); 18 | logger.log("Logging a C-string:'%'\n", s); 19 | logger.log("Logging a string:'%'\n", ss); 20 | 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /Chapter10/common/macros.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | /// Branch prediction hints. 7 | #define LIKELY(x) __builtin_expect(!!(x), 1) 8 | #define UNLIKELY(x) __builtin_expect(!!(x), 0) 9 | 10 | /// Check condition and exit if not true. 11 | inline auto ASSERT(bool cond, const std::string &msg) noexcept { 12 | if (UNLIKELY(!cond)) { 13 | std::cerr << "ASSERT : " << msg << std::endl; 14 | 15 | exit(EXIT_FAILURE); 16 | } 17 | } 18 | 19 | inline auto FATAL(const std::string &msg) noexcept { 20 | std::cerr << "FATAL : " << msg << std::endl; 21 | 22 | exit(EXIT_FAILURE); 23 | } 24 | -------------------------------------------------------------------------------- /Chapter10/common/mcast_socket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "socket_utils.h" 6 | 7 | #include "logging.h" 8 | 9 | namespace Common { 10 | /// Size of send and receive buffers in bytes. 11 | constexpr size_t McastBufferSize = 64 * 1024 * 1024; 12 | 13 | struct McastSocket { 14 | McastSocket(Logger &logger) 15 | : logger_(logger) { 16 | outbound_data_.resize(McastBufferSize); 17 | inbound_data_.resize(McastBufferSize); 18 | } 19 | 20 | /// Initialize multicast socket to read from or publish to a stream. 21 | /// Does not join the multicast stream yet. 22 | auto init(const std::string &ip, const std::string &iface, int port, bool is_listening) -> int; 23 | 24 | /// Add / Join membership / subscription to a multicast stream. 25 | auto join(const std::string &ip) -> bool; 26 | 27 | /// Remove / Leave membership / subscription to a multicast stream. 28 | auto leave(const std::string &ip, int port) -> void; 29 | 30 | /// Publish outgoing data and read incoming data. 31 | auto sendAndRecv() noexcept -> bool; 32 | 33 | /// Copy data to send buffers - does not send them out yet. 34 | auto send(const void *data, size_t len) noexcept -> void; 35 | 36 | int socket_fd_ = -1; 37 | 38 | /// Send and receive buffers, typically only one or the other is needed, not both. 39 | std::vector outbound_data_; 40 | size_t next_send_valid_index_ = 0; 41 | std::vector inbound_data_; 42 | size_t next_rcv_valid_index_ = 0; 43 | 44 | /// Function wrapper for the method to call when data is read. 45 | std::function recv_callback_ = nullptr; 46 | 47 | std::string time_str_; 48 | Logger &logger_; 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /Chapter10/common/mem_pool_example.cpp: -------------------------------------------------------------------------------- 1 | #include "mem_pool.h" 2 | 3 | struct MyStruct { 4 | int d_[3]; 5 | }; 6 | 7 | int main(int, char **) { 8 | using namespace Common; 9 | 10 | MemPool prim_pool(50); 11 | MemPool struct_pool(50); 12 | 13 | for(auto i = 0; i < 50; ++i) { 14 | auto p_ret = prim_pool.allocate(i); 15 | auto s_ret = struct_pool.allocate(MyStruct{i, i+1, i+2}); 16 | 17 | std::cout << "prim elem:" << *p_ret << " allocated at:" << p_ret << std::endl; 18 | std::cout << "struct elem:" << s_ret->d_[0] << "," << s_ret->d_[1] << "," << s_ret->d_[2] << " allocated at:" << s_ret << std::endl; 19 | 20 | if(i % 5 == 0) { 21 | std::cout << "deallocating prim elem:" << *p_ret << " from:" << p_ret << std::endl; 22 | std::cout << "deallocating struct elem:" << s_ret->d_[0] << "," << s_ret->d_[1] << "," << s_ret->d_[2] << " from:" << s_ret << std::endl; 23 | 24 | prim_pool.deallocate(p_ret); 25 | struct_pool.deallocate(s_ret); 26 | } 27 | } 28 | 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /Chapter10/common/tcp_server.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "tcp_socket.h" 4 | 5 | namespace Common { 6 | struct TCPServer { 7 | explicit TCPServer(Logger &logger) 8 | : listener_socket_(logger), logger_(logger) { 9 | } 10 | 11 | /// Start listening for connections on the provided interface and port. 12 | auto listen(const std::string &iface, int port) -> void; 13 | 14 | /// Check for new connections or dead connections and update containers that track the sockets. 15 | auto poll() noexcept -> void; 16 | 17 | /// Publish outgoing data from the send buffer and read incoming data from the receive buffer. 18 | auto sendAndRecv() noexcept -> void; 19 | 20 | private: 21 | /// Add and remove socket file descriptors to and from the EPOLL list. 22 | auto addToEpollList(TCPSocket *socket); 23 | 24 | public: 25 | /// Socket on which this server is listening for new connections on. 26 | int epoll_fd_ = -1; 27 | TCPSocket listener_socket_; 28 | 29 | epoll_event events_[1024]; 30 | 31 | /// Collection of all sockets, sockets for incoming data, sockets for outgoing data and dead connections. 32 | std::vector receive_sockets_, send_sockets_; 33 | 34 | /// Function wrapper to call back when data is available. 35 | std::function recv_callback_ = nullptr; 36 | /// Function wrapper to call back when all data across all TCPSockets has been read and dispatched this round. 37 | std::function recv_finished_callback_ = nullptr; 38 | 39 | std::string time_str_; 40 | Logger &logger_; 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /Chapter10/common/tcp_socket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "socket_utils.h" 6 | #include "logging.h" 7 | 8 | namespace Common { 9 | /// Size of our send and receive buffers in bytes. 10 | constexpr size_t TCPBufferSize = 64 * 1024 * 1024; 11 | 12 | struct TCPSocket { 13 | explicit TCPSocket(Logger &logger) 14 | : logger_(logger) { 15 | outbound_data_.resize(TCPBufferSize); 16 | inbound_data_.resize(TCPBufferSize); 17 | } 18 | 19 | /// Create TCPSocket with provided attributes to either listen-on / connect-to. 20 | auto connect(const std::string &ip, const std::string &iface, int port, bool is_listening) -> int; 21 | 22 | /// Called to publish outgoing data from the buffers as well as check for and callback if data is available in the read buffers. 23 | auto sendAndRecv() noexcept -> bool; 24 | 25 | /// Write outgoing data to the send buffers. 26 | auto send(const void *data, size_t len) noexcept -> void; 27 | 28 | /// Deleted default, copy & move constructors and assignment-operators. 29 | TCPSocket() = delete; 30 | 31 | TCPSocket(const TCPSocket &) = delete; 32 | 33 | TCPSocket(const TCPSocket &&) = delete; 34 | 35 | TCPSocket &operator=(const TCPSocket &) = delete; 36 | 37 | TCPSocket &operator=(const TCPSocket &&) = delete; 38 | 39 | /// File descriptor for the socket. 40 | int socket_fd_ = -1; 41 | 42 | /// Send and receive buffers and trackers for read/write indices. 43 | std::vector outbound_data_; 44 | size_t next_send_valid_index_ = 0; 45 | std::vector inbound_data_; 46 | size_t next_rcv_valid_index_ = 0; 47 | 48 | /// Socket attributes. 49 | struct sockaddr_in socket_attrib_{}; 50 | 51 | /// Function wrapper to callback when there is data to be processed. 52 | std::function recv_callback_ = nullptr; 53 | 54 | std::string time_str_; 55 | Logger &logger_; 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /Chapter10/common/thread_example.cpp: -------------------------------------------------------------------------------- 1 | #include "thread_utils.h" 2 | 3 | auto dummyFunction(int a, int b, bool sleep) { 4 | std::cout << "dummyFunction(" << a << "," << b << ")" << std::endl; 5 | std::cout << "dummyFunction output=" << a + b << std::endl; 6 | 7 | if(sleep) { 8 | std::cout << "dummyFunction sleeping..." << std::endl; 9 | 10 | using namespace std::literals::chrono_literals; 11 | std::this_thread::sleep_for(5s); 12 | } 13 | 14 | std::cout << "dummyFunction done." << std::endl; 15 | } 16 | 17 | int main(int, char **) { 18 | using namespace Common; 19 | 20 | auto t1 = createAndStartThread(-1, "dummyFunction1", dummyFunction, 12, 21, false); 21 | auto t2 = createAndStartThread(1, "dummyFunction2", dummyFunction, 15, 51, true); 22 | 23 | std::cout << "main waiting for threads to be done." << std::endl; 24 | t1->join(); 25 | t2->join(); 26 | std::cout << "main exiting." << std::endl; 27 | 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /Chapter10/common/thread_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace Common { 11 | /// Set affinity for current thread to be pinned to the provided core_id. 12 | inline auto setThreadCore(int core_id) noexcept { 13 | cpu_set_t cpuset; 14 | 15 | CPU_ZERO(&cpuset); 16 | CPU_SET(core_id, &cpuset); 17 | 18 | return (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) == 0); 19 | } 20 | 21 | /// Creates a thread instance, sets affinity on it, assigns it a name and 22 | /// passes the function to be run on that thread as well as the arguments to the function. 23 | template 24 | inline auto createAndStartThread(int core_id, const std::string &name, T &&func, A &&... args) noexcept { 25 | auto t = new std::thread([&]() { 26 | if (core_id >= 0 && !setThreadCore(core_id)) { 27 | std::cerr << "Failed to set core affinity for " << name << " " << pthread_self() << " to " << core_id << std::endl; 28 | exit(EXIT_FAILURE); 29 | } 30 | std::cerr << "Set core affinity for " << name << " " << pthread_self() << " to " << core_id << std::endl; 31 | 32 | std::forward(func)((std::forward(args))...); 33 | }); 34 | 35 | using namespace std::literals::chrono_literals; 36 | std::this_thread::sleep_for(1s); 37 | 38 | return t; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Chapter10/common/time_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Common { 8 | typedef int64_t Nanos; 9 | 10 | constexpr Nanos NANOS_TO_MICROS = 1000; 11 | constexpr Nanos MICROS_TO_MILLIS = 1000; 12 | constexpr Nanos MILLIS_TO_SECS = 1000; 13 | constexpr Nanos NANOS_TO_MILLIS = NANOS_TO_MICROS * MICROS_TO_MILLIS; 14 | constexpr Nanos NANOS_TO_SECS = NANOS_TO_MILLIS * MILLIS_TO_SECS; 15 | 16 | inline auto getCurrentNanos() noexcept { 17 | return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); 18 | } 19 | 20 | inline auto& getCurrentTimeStr(std::string* time_str) { 21 | const auto time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); 22 | time_str->assign(ctime(&time)); 23 | if(!time_str->empty()) 24 | time_str->at(time_str->length()-1) = '\0'; 25 | return *time_str; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Chapter10/exchange/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | set(CMAKE_CXX_COMPILER g++) 3 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 4 | set(CMAKE_VERBOSE_MAKEFILE on) 5 | 6 | file(GLOB SOURCES "*/*.cpp") 7 | 8 | include_directories(${PROJECT_SOURCE_DIR}) 9 | include_directories(${PROJECT_SOURCE_DIR}/exchange) 10 | 11 | add_library(libexchange STATIC ${SOURCES}) 12 | -------------------------------------------------------------------------------- /Chapter10/exchange/matcher/matching_engine.cpp: -------------------------------------------------------------------------------- 1 | #include "matching_engine.h" 2 | 3 | namespace Exchange { 4 | MatchingEngine::MatchingEngine(ClientRequestLFQueue *client_requests, ClientResponseLFQueue *client_responses, 5 | MEMarketUpdateLFQueue *market_updates) 6 | : incoming_requests_(client_requests), outgoing_ogw_responses_(client_responses), outgoing_md_updates_(market_updates), 7 | logger_("exchange_matching_engine.log") { 8 | for(size_t i = 0; i < ticker_order_book_.size(); ++i) { 9 | ticker_order_book_[i] = new MEOrderBook(i, &logger_, this); 10 | } 11 | } 12 | 13 | MatchingEngine::~MatchingEngine() { 14 | stop(); 15 | 16 | using namespace std::literals::chrono_literals; 17 | std::this_thread::sleep_for(1s); 18 | 19 | incoming_requests_ = nullptr; 20 | outgoing_ogw_responses_ = nullptr; 21 | outgoing_md_updates_ = nullptr; 22 | 23 | for(auto& order_book : ticker_order_book_) { 24 | delete order_book; 25 | order_book = nullptr; 26 | } 27 | } 28 | 29 | /// Start and stop the matching engine main thread. 30 | auto MatchingEngine::start() -> void { 31 | run_ = true; 32 | ASSERT(Common::createAndStartThread(-1, "Exchange/MatchingEngine", [this]() { run(); }) != nullptr, "Failed to start MatchingEngine thread."); 33 | } 34 | 35 | auto MatchingEngine::stop() -> void { 36 | run_ = false; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Chapter10/exchange/matcher/me_order.cpp: -------------------------------------------------------------------------------- 1 | #include "me_order.h" 2 | 3 | namespace Exchange { 4 | auto MEOrder::toString() const -> std::string { 5 | std::stringstream ss; 6 | ss << "MEOrder" << "[" 7 | << "ticker:" << tickerIdToString(ticker_id_) << " " 8 | << "cid:" << clientIdToString(client_id_) << " " 9 | << "oid:" << orderIdToString(client_order_id_) << " " 10 | << "moid:" << orderIdToString(market_order_id_) << " " 11 | << "side:" << sideToString(side_) << " " 12 | << "price:" << priceToString(price_) << " " 13 | << "qty:" << qtyToString(qty_) << " " 14 | << "prio:" << priorityToString(priority_) << " " 15 | << "prev:" << orderIdToString(prev_order_ ? prev_order_->market_order_id_ : OrderId_INVALID) << " " 16 | << "next:" << orderIdToString(next_order_ ? next_order_->market_order_id_ : OrderId_INVALID) << "]"; 17 | 18 | return ss.str(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter10/exchange/order_server/order_server.cpp: -------------------------------------------------------------------------------- 1 | #include "order_server.h" 2 | 3 | namespace Exchange { 4 | OrderServer::OrderServer(ClientRequestLFQueue *client_requests, ClientResponseLFQueue *client_responses, const std::string &iface, int port) 5 | : iface_(iface), port_(port), outgoing_responses_(client_responses), logger_("exchange_order_server.log"), 6 | tcp_server_(logger_), fifo_sequencer_(client_requests, &logger_) { 7 | cid_next_outgoing_seq_num_.fill(1); 8 | cid_next_exp_seq_num_.fill(1); 9 | cid_tcp_socket_.fill(nullptr); 10 | 11 | tcp_server_.recv_callback_ = [this](auto socket, auto rx_time) { recvCallback(socket, rx_time); }; 12 | tcp_server_.recv_finished_callback_ = [this]() { recvFinishedCallback(); }; 13 | } 14 | 15 | OrderServer::~OrderServer() { 16 | stop(); 17 | 18 | using namespace std::literals::chrono_literals; 19 | std::this_thread::sleep_for(1s); 20 | } 21 | 22 | /// Start and stop the order server main thread. 23 | auto OrderServer::start() -> void { 24 | run_ = true; 25 | tcp_server_.listen(iface_, port_); 26 | 27 | ASSERT(Common::createAndStartThread(-1, "Exchange/OrderServer", [this]() { run(); }) != nullptr, "Failed to start OrderServer thread."); 28 | } 29 | 30 | auto OrderServer::stop() -> void { 31 | run_ = false; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Chapter10/scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # TODO - point these to the correct binary locations on your system. 4 | CMAKE=$(which cmake) 5 | NINJA=$(which ninja) 6 | 7 | mkdir -p ./cmake-build-release 8 | $CMAKE -DCMAKE_BUILD_TYPE=Release -DCMAKE_MAKE_PROGRAM=$NINJA -G Ninja -S . -B ./cmake-build-release 9 | 10 | $CMAKE --build ./cmake-build-release --target clean -j 4 11 | $CMAKE --build ./cmake-build-release --target all -j 4 12 | 13 | mkdir -p ./cmake-build-debug 14 | $CMAKE -DCMAKE_BUILD_TYPE=Debug -DCMAKE_MAKE_PROGRAM=$NINJA -G Ninja -S . -B ./cmake-build-debug 15 | 16 | $CMAKE --build ./cmake-build-debug --target clean -j 4 17 | $CMAKE --build ./cmake-build-debug --target all -j 4 18 | -------------------------------------------------------------------------------- /Chapter10/scripts/no_clean_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # TODO - point these to the correct binary locations on your system. 4 | CMAKE=$(which cmake) 5 | NINJA=$(which ninja) 6 | 7 | mkdir -p ./cmake-build-release 8 | $CMAKE -DCMAKE_BUILD_TYPE=Release -DCMAKE_MAKE_PROGRAM=$NINJA -G Ninja -S . -B ./cmake-build-release 9 | 10 | $CMAKE --build ./cmake-build-release --target all -j 4 11 | 12 | mkdir -p ./cmake-build-debug 13 | $CMAKE -DCMAKE_BUILD_TYPE=Debug -DCMAKE_MAKE_PROGRAM=$NINJA -G Ninja -S . -B ./cmake-build-debug 14 | 15 | $CMAKE --build ./cmake-build-debug --target all -j 4 16 | -------------------------------------------------------------------------------- /Chapter10/scripts/run_exchange_and_clients.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | bash scripts/build.sh 4 | 5 | date 6 | 7 | echo "---------------------------------------------------------------------------------------------------------------------------------------------------------" 8 | echo "Starting Exchange..." 9 | echo "---------------------------------------------------------------------------------------------------------------------------------------------------------" 10 | ./cmake-build-release/exchange_main 2>&1 & 11 | sleep 10 12 | 13 | bash ./scripts/run_clients.sh 14 | 15 | sleep 5 16 | 17 | echo "---------------------------------------------------------------------------------------------------------------------------------------------------------" 18 | echo "Stopping Exchange..." 19 | echo "---------------------------------------------------------------------------------------------------------------------------------------------------------" 20 | pkill -2 exchange 21 | 22 | sleep 10 23 | 24 | wait 25 | date 26 | -------------------------------------------------------------------------------- /Chapter10/trading/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | set(CMAKE_CXX_COMPILER g++) 3 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 4 | set(CMAKE_VERBOSE_MAKEFILE on) 5 | 6 | file(GLOB SOURCES "*/*.cpp") 7 | 8 | include_directories(${PROJECT_SOURCE_DIR}) 9 | include_directories(${PROJECT_SOURCE_DIR}/trading) 10 | 11 | add_library(libtrading STATIC ${SOURCES} strategy/risk_manager.cpp) 12 | -------------------------------------------------------------------------------- /Chapter10/trading/strategy/liquidity_taker.cpp: -------------------------------------------------------------------------------- 1 | #include "liquidity_taker.h" 2 | 3 | #include "trade_engine.h" 4 | 5 | namespace Trading { 6 | LiquidityTaker::LiquidityTaker(Common::Logger *logger, TradeEngine *trade_engine, const FeatureEngine *feature_engine, 7 | OrderManager *order_manager, 8 | const TradeEngineCfgHashMap &ticker_cfg) 9 | : feature_engine_(feature_engine), order_manager_(order_manager), logger_(logger), 10 | ticker_cfg_(ticker_cfg) { 11 | trade_engine->algoOnOrderBookUpdate_ = [this](auto ticker_id, auto price, auto side, auto book) { 12 | onOrderBookUpdate(ticker_id, price, side, book); 13 | }; 14 | trade_engine->algoOnTradeUpdate_ = [this](auto market_update, auto book) { onTradeUpdate(market_update, book); }; 15 | trade_engine->algoOnOrderUpdate_ = [this](auto client_response) { onOrderUpdate(client_response); }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter10/trading/strategy/market_maker.cpp: -------------------------------------------------------------------------------- 1 | #include "market_maker.h" 2 | 3 | #include "trade_engine.h" 4 | 5 | namespace Trading { 6 | MarketMaker::MarketMaker(Common::Logger *logger, TradeEngine *trade_engine, const FeatureEngine *feature_engine, 7 | OrderManager *order_manager, const TradeEngineCfgHashMap &ticker_cfg) 8 | : feature_engine_(feature_engine), order_manager_(order_manager), logger_(logger), 9 | ticker_cfg_(ticker_cfg) { 10 | trade_engine->algoOnOrderBookUpdate_ = [this](auto ticker_id, auto price, auto side, auto book) { 11 | onOrderBookUpdate(ticker_id, price, side, book); 12 | }; 13 | trade_engine->algoOnTradeUpdate_ = [this](auto market_update, auto book) { onTradeUpdate(market_update, book); }; 14 | trade_engine->algoOnOrderUpdate_ = [this](auto client_response) { onOrderUpdate(client_response); }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Chapter10/trading/strategy/market_order.cpp: -------------------------------------------------------------------------------- 1 | #include "market_order.h" 2 | 3 | namespace Trading { 4 | auto MarketOrder::toString() const -> std::string { 5 | std::stringstream ss; 6 | ss << "MarketOrder" << "[" 7 | << "oid:" << orderIdToString(order_id_) << " " 8 | << "side:" << sideToString(side_) << " " 9 | << "price:" << priceToString(price_) << " " 10 | << "qty:" << qtyToString(qty_) << " " 11 | << "prio:" << priorityToString(priority_) << " " 12 | << "prev:" << orderIdToString(prev_order_ ? prev_order_->order_id_ : OrderId_INVALID) << " " 13 | << "next:" << orderIdToString(next_order_ ? next_order_->order_id_ : OrderId_INVALID) << "]"; 14 | 15 | return ss.str(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter10/trading/strategy/order_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "order_manager.h" 2 | #include "trade_engine.h" 3 | 4 | namespace Trading { 5 | /// Send a new order with specified attribute, and update the OMOrder object passed here. 6 | auto OrderManager::newOrder(OMOrder *order, TickerId ticker_id, Price price, Side side, Qty qty) noexcept -> void { 7 | const Exchange::MEClientRequest new_request{Exchange::ClientRequestType::NEW, trade_engine_->clientId(), ticker_id, 8 | next_order_id_, side, price, qty}; 9 | trade_engine_->sendClientRequest(&new_request); 10 | 11 | *order = {ticker_id, next_order_id_, side, price, qty, OMOrderState::PENDING_NEW}; 12 | ++next_order_id_; 13 | 14 | logger_->log("%:% %() % Sent new order % for %\n", __FILE__, __LINE__, __FUNCTION__, 15 | Common::getCurrentTimeStr(&time_str_), 16 | new_request.toString().c_str(), order->toString().c_str()); 17 | } 18 | 19 | /// Send a cancel for the specified order, and update the OMOrder object passed here. 20 | auto OrderManager::cancelOrder(OMOrder *order) noexcept -> void { 21 | const Exchange::MEClientRequest cancel_request{Exchange::ClientRequestType::CANCEL, trade_engine_->clientId(), 22 | order->ticker_id_, order->order_id_, order->side_, order->price_, 23 | order->qty_}; 24 | trade_engine_->sendClientRequest(&cancel_request); 25 | 26 | order->order_state_ = OMOrderState::PENDING_CANCEL; 27 | 28 | logger_->log("%:% %() % Sent cancel % for %\n", __FILE__, __LINE__, __FUNCTION__, 29 | Common::getCurrentTimeStr(&time_str_), 30 | cancel_request.toString().c_str(), order->toString().c_str()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Chapter10/trading/strategy/risk_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "risk_manager.h" 2 | 3 | #include "order_manager.h" 4 | 5 | namespace Trading { 6 | RiskManager::RiskManager(Common::Logger *logger, const PositionKeeper *position_keeper, const TradeEngineCfgHashMap &ticker_cfg) 7 | : logger_(logger) { 8 | for (TickerId i = 0; i < ME_MAX_TICKERS; ++i) { 9 | ticker_risk_.at(i).position_info_ = position_keeper->getPositionInfo(i); 10 | ticker_risk_.at(i).risk_cfg_ = ticker_cfg[i].risk_cfg_; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Chapter11/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(LowLatencyApp) 4 | 5 | set(CMAKE_CXX_STANDARD 20) 6 | set(CMAKE_CXX_COMPILER g++) 7 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 8 | set(CMAKE_VERBOSE_MAKEFILE on) 9 | 10 | add_subdirectory(common) 11 | add_subdirectory(exchange) 12 | add_subdirectory(trading) 13 | 14 | list(APPEND LIBS libexchange) 15 | list(APPEND LIBS libtrading) 16 | list(APPEND LIBS libcommon) 17 | list(APPEND LIBS pthread) 18 | 19 | include_directories(${PROJECT_SOURCE_DIR}) 20 | include_directories(${PROJECT_SOURCE_DIR}/exchange) 21 | include_directories(${PROJECT_SOURCE_DIR}/trading) 22 | 23 | add_executable(exchange_main exchange/exchange_main.cpp) 24 | target_link_libraries(exchange_main PUBLIC ${LIBS}) 25 | 26 | add_executable(trading_main trading/trading_main.cpp) 27 | target_link_libraries(trading_main PUBLIC ${LIBS}) 28 | -------------------------------------------------------------------------------- /Chapter11/common/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | set(CMAKE_CXX_COMPILER g++) 3 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 4 | set(CMAKE_VERBOSE_MAKEFILE on) 5 | 6 | file(GLOB SOURCES "*.cpp") 7 | 8 | include_directories(${PROJECT_SOURCE_DIR}) 9 | 10 | add_library(libcommon STATIC ${SOURCES}) 11 | 12 | list(APPEND LIBS libcommon) 13 | list(APPEND LIBS pthread) 14 | 15 | add_executable(thread_example thread_example.cpp) 16 | target_link_libraries(thread_example PUBLIC ${LIBS}) 17 | 18 | add_executable(mem_pool_example mem_pool_example.cpp) 19 | target_link_libraries(mem_pool_example PUBLIC ${LIBS}) 20 | 21 | add_executable(lf_queue_example lf_queue_example.cpp) 22 | target_link_libraries(lf_queue_example PUBLIC ${LIBS}) 23 | 24 | add_executable(logging_example logging_example.cpp) 25 | target_link_libraries(logging_example PUBLIC ${LIBS}) 26 | 27 | add_executable(socket_example socket_example.cpp) 28 | target_link_libraries(socket_example PUBLIC ${LIBS}) 29 | -------------------------------------------------------------------------------- /Chapter11/common/lf_queue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "macros.h" 8 | 9 | namespace Common { 10 | template 11 | class LFQueue final { 12 | public: 13 | explicit LFQueue(std::size_t num_elems) : 14 | store_(num_elems, T()) /* pre-allocation of vector storage. */ { 15 | } 16 | 17 | auto getNextToWriteTo() noexcept { 18 | return &store_[next_write_index_]; 19 | } 20 | 21 | auto updateWriteIndex() noexcept { 22 | next_write_index_ = (next_write_index_ + 1) % store_.size(); 23 | num_elements_++; 24 | } 25 | 26 | auto getNextToRead() const noexcept -> const T * { 27 | return (size() ? &store_[next_read_index_] : nullptr); 28 | } 29 | 30 | auto updateReadIndex() noexcept { 31 | next_read_index_ = (next_read_index_ + 1) % store_.size(); // wrap around at the end of container size. 32 | ASSERT(num_elements_ != 0, "Read an invalid element in:" + std::to_string(pthread_self())); 33 | num_elements_--; 34 | } 35 | 36 | auto size() const noexcept { 37 | return num_elements_.load(); 38 | } 39 | 40 | /// Deleted default, copy & move constructors and assignment-operators. 41 | LFQueue() = delete; 42 | 43 | LFQueue(const LFQueue &) = delete; 44 | 45 | LFQueue(const LFQueue &&) = delete; 46 | 47 | LFQueue &operator=(const LFQueue &) = delete; 48 | 49 | LFQueue &operator=(const LFQueue &&) = delete; 50 | 51 | private: 52 | /// Underlying container of data accessed in FIFO order. 53 | std::vector store_; 54 | 55 | /// Atomic trackers for next index to write new data to and read new data from. 56 | std::atomic next_write_index_ = {0}; 57 | std::atomic next_read_index_ = {0}; 58 | 59 | std::atomic num_elements_ = {0}; 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /Chapter11/common/lf_queue_example.cpp: -------------------------------------------------------------------------------- 1 | #include "thread_utils.h" 2 | #include "lf_queue.h" 3 | 4 | struct MyStruct { 5 | int d_[3]; 6 | }; 7 | 8 | using namespace Common; 9 | 10 | auto consumeFunction(LFQueue* lfq) { 11 | using namespace std::literals::chrono_literals; 12 | std::this_thread::sleep_for(5s); 13 | 14 | while(lfq->size()) { 15 | const auto d = lfq->getNextToRead(); 16 | lfq->updateReadIndex(); 17 | 18 | std::cout << "consumeFunction read elem:" << d->d_[0] << "," << d->d_[1] << "," << d->d_[2] << " lfq-size:" << lfq->size() << std::endl; 19 | 20 | std::this_thread::sleep_for(1s); 21 | } 22 | 23 | std::cout << "consumeFunction exiting." << std::endl; 24 | } 25 | 26 | int main(int, char **) { 27 | LFQueue lfq(20); 28 | 29 | auto ct = createAndStartThread(-1, "", consumeFunction, &lfq); 30 | 31 | for(auto i = 0; i < 50; ++i) { 32 | const MyStruct d{i, i * 10, i * 100}; 33 | *(lfq.getNextToWriteTo()) = d; 34 | lfq.updateWriteIndex(); 35 | 36 | std::cout << "main constructed elem:" << d.d_[0] << "," << d.d_[1] << "," << d.d_[2] << " lfq-size:" << lfq.size() << std::endl; 37 | 38 | using namespace std::literals::chrono_literals; 39 | std::this_thread::sleep_for(1s); 40 | } 41 | 42 | ct->join(); 43 | 44 | std::cout << "main exiting." << std::endl; 45 | 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /Chapter11/common/logging_example.cpp: -------------------------------------------------------------------------------- 1 | #include "logging.h" 2 | 3 | int main(int, char **) { 4 | using namespace Common; 5 | 6 | char c = 'd'; 7 | int i = 3; 8 | unsigned long ul = 65; 9 | float f = 3.4; 10 | double d = 34.56; 11 | const char* s = "test C-string"; 12 | std::string ss = "test string"; 13 | 14 | Logger logger("logging_example.log"); 15 | 16 | logger.log("Logging a char:% an int:% and an unsigned:%\n", c, i, ul); 17 | logger.log("Logging a float:% and a double:%\n", f, d); 18 | logger.log("Logging a C-string:'%'\n", s); 19 | logger.log("Logging a string:'%'\n", ss); 20 | 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /Chapter11/common/macros.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | /// Branch prediction hints. 7 | #define LIKELY(x) __builtin_expect(!!(x), 1) 8 | #define UNLIKELY(x) __builtin_expect(!!(x), 0) 9 | 10 | /// Check condition and exit if not true. 11 | inline auto ASSERT(bool cond, const std::string &msg) noexcept { 12 | if (UNLIKELY(!cond)) { 13 | std::cerr << "ASSERT : " << msg << std::endl; 14 | 15 | exit(EXIT_FAILURE); 16 | } 17 | } 18 | 19 | inline auto FATAL(const std::string &msg) noexcept { 20 | std::cerr << "FATAL : " << msg << std::endl; 21 | 22 | exit(EXIT_FAILURE); 23 | } 24 | -------------------------------------------------------------------------------- /Chapter11/common/mcast_socket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "socket_utils.h" 6 | 7 | #include "logging.h" 8 | 9 | namespace Common { 10 | /// Size of send and receive buffers in bytes. 11 | constexpr size_t McastBufferSize = 64 * 1024 * 1024; 12 | 13 | struct McastSocket { 14 | McastSocket(Logger &logger) 15 | : logger_(logger) { 16 | outbound_data_.resize(McastBufferSize); 17 | inbound_data_.resize(McastBufferSize); 18 | } 19 | 20 | /// Initialize multicast socket to read from or publish to a stream. 21 | /// Does not join the multicast stream yet. 22 | auto init(const std::string &ip, const std::string &iface, int port, bool is_listening) -> int; 23 | 24 | /// Add / Join membership / subscription to a multicast stream. 25 | auto join(const std::string &ip) -> bool; 26 | 27 | /// Remove / Leave membership / subscription to a multicast stream. 28 | auto leave(const std::string &ip, int port) -> void; 29 | 30 | /// Publish outgoing data and read incoming data. 31 | auto sendAndRecv() noexcept -> bool; 32 | 33 | /// Copy data to send buffers - does not send them out yet. 34 | auto send(const void *data, size_t len) noexcept -> void; 35 | 36 | int socket_fd_ = -1; 37 | 38 | /// Send and receive buffers, typically only one or the other is needed, not both. 39 | std::vector outbound_data_; 40 | size_t next_send_valid_index_ = 0; 41 | std::vector inbound_data_; 42 | size_t next_rcv_valid_index_ = 0; 43 | 44 | /// Function wrapper for the method to call when data is read. 45 | std::function recv_callback_ = nullptr; 46 | 47 | std::string time_str_; 48 | Logger &logger_; 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /Chapter11/common/mem_pool_example.cpp: -------------------------------------------------------------------------------- 1 | #include "mem_pool.h" 2 | 3 | struct MyStruct { 4 | int d_[3]; 5 | }; 6 | 7 | int main(int, char **) { 8 | using namespace Common; 9 | 10 | MemPool prim_pool(50); 11 | MemPool struct_pool(50); 12 | 13 | for(auto i = 0; i < 50; ++i) { 14 | auto p_ret = prim_pool.allocate(i); 15 | auto s_ret = struct_pool.allocate(MyStruct{i, i+1, i+2}); 16 | 17 | std::cout << "prim elem:" << *p_ret << " allocated at:" << p_ret << std::endl; 18 | std::cout << "struct elem:" << s_ret->d_[0] << "," << s_ret->d_[1] << "," << s_ret->d_[2] << " allocated at:" << s_ret << std::endl; 19 | 20 | if(i % 5 == 0) { 21 | std::cout << "deallocating prim elem:" << *p_ret << " from:" << p_ret << std::endl; 22 | std::cout << "deallocating struct elem:" << s_ret->d_[0] << "," << s_ret->d_[1] << "," << s_ret->d_[2] << " from:" << s_ret << std::endl; 23 | 24 | prim_pool.deallocate(p_ret); 25 | struct_pool.deallocate(s_ret); 26 | } 27 | } 28 | 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /Chapter11/common/perf_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Common { 4 | /// Read from the TSC register and return a uint64_t value to represent elapsed CPU clock cycles. 5 | inline auto rdtsc() noexcept { 6 | unsigned int lo, hi; 7 | __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); 8 | return ((uint64_t) hi << 32) | lo; 9 | } 10 | } 11 | 12 | /// Start latency measurement using rdtsc(). Creates a variable called TAG in the local scope. 13 | #define START_MEASURE(TAG) const auto TAG = Common::rdtsc() 14 | 15 | /// End latency measurement using rdtsc(). Expects a variable called TAG to already exist in the local scope. 16 | #define END_MEASURE(TAG, LOGGER) \ 17 | do { \ 18 | const auto end = Common::rdtsc(); \ 19 | LOGGER.log("% RDTSC "#TAG" %\n", Common::getCurrentTimeStr(&time_str_), (end - TAG)); \ 20 | } while(false) 21 | 22 | /// Log a current timestamp at the time this macro is invoked. 23 | #define TTT_MEASURE(TAG, LOGGER) \ 24 | do { \ 25 | const auto TAG = Common::getCurrentNanos(); \ 26 | LOGGER.log("% TTT "#TAG" %\n", Common::getCurrentTimeStr(&time_str_), TAG); \ 27 | } while(false) 28 | -------------------------------------------------------------------------------- /Chapter11/common/tcp_server.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "tcp_socket.h" 4 | 5 | namespace Common { 6 | struct TCPServer { 7 | explicit TCPServer(Logger &logger) 8 | : listener_socket_(logger), logger_(logger) { 9 | } 10 | 11 | /// Start listening for connections on the provided interface and port. 12 | auto listen(const std::string &iface, int port) -> void; 13 | 14 | /// Check for new connections or dead connections and update containers that track the sockets. 15 | auto poll() noexcept -> void; 16 | 17 | /// Publish outgoing data from the send buffer and read incoming data from the receive buffer. 18 | auto sendAndRecv() noexcept -> void; 19 | 20 | private: 21 | /// Add and remove socket file descriptors to and from the EPOLL list. 22 | auto addToEpollList(TCPSocket *socket); 23 | 24 | public: 25 | /// Socket on which this server is listening for new connections on. 26 | int epoll_fd_ = -1; 27 | TCPSocket listener_socket_; 28 | 29 | epoll_event events_[1024]; 30 | 31 | /// Collection of all sockets, sockets for incoming data, sockets for outgoing data and dead connections. 32 | std::vector receive_sockets_, send_sockets_; 33 | 34 | /// Function wrapper to call back when data is available. 35 | std::function recv_callback_ = nullptr; 36 | /// Function wrapper to call back when all data across all TCPSockets has been read and dispatched this round. 37 | std::function recv_finished_callback_ = nullptr; 38 | 39 | std::string time_str_; 40 | Logger &logger_; 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /Chapter11/common/tcp_socket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "socket_utils.h" 6 | #include "logging.h" 7 | 8 | namespace Common { 9 | /// Size of our send and receive buffers in bytes. 10 | constexpr size_t TCPBufferSize = 64 * 1024 * 1024; 11 | 12 | struct TCPSocket { 13 | explicit TCPSocket(Logger &logger) 14 | : logger_(logger) { 15 | outbound_data_.resize(TCPBufferSize); 16 | inbound_data_.resize(TCPBufferSize); 17 | } 18 | 19 | /// Create TCPSocket with provided attributes to either listen-on / connect-to. 20 | auto connect(const std::string &ip, const std::string &iface, int port, bool is_listening) -> int; 21 | 22 | /// Called to publish outgoing data from the buffers as well as check for and callback if data is available in the read buffers. 23 | auto sendAndRecv() noexcept -> bool; 24 | 25 | /// Write outgoing data to the send buffers. 26 | auto send(const void *data, size_t len) noexcept -> void; 27 | 28 | /// Deleted default, copy & move constructors and assignment-operators. 29 | TCPSocket() = delete; 30 | 31 | TCPSocket(const TCPSocket &) = delete; 32 | 33 | TCPSocket(const TCPSocket &&) = delete; 34 | 35 | TCPSocket &operator=(const TCPSocket &) = delete; 36 | 37 | TCPSocket &operator=(const TCPSocket &&) = delete; 38 | 39 | /// File descriptor for the socket. 40 | int socket_fd_ = -1; 41 | 42 | /// Send and receive buffers and trackers for read/write indices. 43 | std::vector outbound_data_; 44 | size_t next_send_valid_index_ = 0; 45 | std::vector inbound_data_; 46 | size_t next_rcv_valid_index_ = 0; 47 | 48 | /// Socket attributes. 49 | struct sockaddr_in socket_attrib_{}; 50 | 51 | /// Function wrapper to callback when there is data to be processed. 52 | std::function recv_callback_ = nullptr; 53 | 54 | std::string time_str_; 55 | Logger &logger_; 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /Chapter11/common/thread_example.cpp: -------------------------------------------------------------------------------- 1 | #include "thread_utils.h" 2 | 3 | auto dummyFunction(int a, int b, bool sleep) { 4 | std::cout << "dummyFunction(" << a << "," << b << ")" << std::endl; 5 | std::cout << "dummyFunction output=" << a + b << std::endl; 6 | 7 | if(sleep) { 8 | std::cout << "dummyFunction sleeping..." << std::endl; 9 | 10 | using namespace std::literals::chrono_literals; 11 | std::this_thread::sleep_for(5s); 12 | } 13 | 14 | std::cout << "dummyFunction done." << std::endl; 15 | } 16 | 17 | int main(int, char **) { 18 | using namespace Common; 19 | 20 | auto t1 = createAndStartThread(-1, "dummyFunction1", dummyFunction, 12, 21, false); 21 | auto t2 = createAndStartThread(1, "dummyFunction2", dummyFunction, 15, 51, true); 22 | 23 | std::cout << "main waiting for threads to be done." << std::endl; 24 | t1->join(); 25 | t2->join(); 26 | std::cout << "main exiting." << std::endl; 27 | 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /Chapter11/common/thread_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace Common { 11 | /// Set affinity for current thread to be pinned to the provided core_id. 12 | inline auto setThreadCore(int core_id) noexcept { 13 | cpu_set_t cpuset; 14 | 15 | CPU_ZERO(&cpuset); 16 | CPU_SET(core_id, &cpuset); 17 | 18 | return (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) == 0); 19 | } 20 | 21 | /// Creates a thread instance, sets affinity on it, assigns it a name and 22 | /// passes the function to be run on that thread as well as the arguments to the function. 23 | template 24 | inline auto createAndStartThread(int core_id, const std::string &name, T &&func, A &&... args) noexcept { 25 | auto t = new std::thread([&]() { 26 | if (core_id >= 0 && !setThreadCore(core_id)) { 27 | std::cerr << "Failed to set core affinity for " << name << " " << pthread_self() << " to " << core_id << std::endl; 28 | exit(EXIT_FAILURE); 29 | } 30 | std::cerr << "Set core affinity for " << name << " " << pthread_self() << " to " << core_id << std::endl; 31 | 32 | std::forward(func)((std::forward(args))...); 33 | }); 34 | 35 | using namespace std::literals::chrono_literals; 36 | std::this_thread::sleep_for(1s); 37 | 38 | return t; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Chapter11/common/time_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "perf_utils.h" 8 | 9 | namespace Common { 10 | /// Represent a nanosecond timestamp. 11 | typedef int64_t Nanos; 12 | 13 | /// Convert between nanos, micros, millis and secs. 14 | constexpr Nanos NANOS_TO_MICROS = 1000; 15 | constexpr Nanos MICROS_TO_MILLIS = 1000; 16 | constexpr Nanos MILLIS_TO_SECS = 1000; 17 | constexpr Nanos NANOS_TO_MILLIS = NANOS_TO_MICROS * MICROS_TO_MILLIS; 18 | constexpr Nanos NANOS_TO_SECS = NANOS_TO_MILLIS * MILLIS_TO_SECS; 19 | 20 | /// Get current nanosecond timestamp. 21 | inline auto getCurrentNanos() noexcept { 22 | return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); 23 | } 24 | 25 | /// Format current timestamp to a human readable string. 26 | /// String formatting is inefficient. 27 | inline auto& getCurrentTimeStr(std::string* time_str) { 28 | const auto clock = std::chrono::system_clock::now(); 29 | const auto time = std::chrono::system_clock::to_time_t(clock); 30 | 31 | char nanos_str[24]; 32 | sprintf(nanos_str, "%.8s.%09ld", ctime(&time) + 11, std::chrono::duration_cast(clock.time_since_epoch()).count() % NANOS_TO_SECS); 33 | time_str->assign(nanos_str); 34 | 35 | return *time_str; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Chapter11/exchange/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | set(CMAKE_CXX_COMPILER g++) 3 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 4 | set(CMAKE_VERBOSE_MAKEFILE on) 5 | 6 | file(GLOB SOURCES "*/*.cpp") 7 | 8 | include_directories(${PROJECT_SOURCE_DIR}) 9 | include_directories(${PROJECT_SOURCE_DIR}/exchange) 10 | 11 | add_library(libexchange STATIC ${SOURCES}) 12 | -------------------------------------------------------------------------------- /Chapter11/exchange/matcher/matching_engine.cpp: -------------------------------------------------------------------------------- 1 | #include "matching_engine.h" 2 | 3 | namespace Exchange { 4 | MatchingEngine::MatchingEngine(ClientRequestLFQueue *client_requests, ClientResponseLFQueue *client_responses, 5 | MEMarketUpdateLFQueue *market_updates) 6 | : incoming_requests_(client_requests), outgoing_ogw_responses_(client_responses), outgoing_md_updates_(market_updates), 7 | logger_("exchange_matching_engine.log") { 8 | for(size_t i = 0; i < ticker_order_book_.size(); ++i) { 9 | ticker_order_book_[i] = new MEOrderBook(i, &logger_, this); 10 | } 11 | } 12 | 13 | MatchingEngine::~MatchingEngine() { 14 | stop(); 15 | 16 | using namespace std::literals::chrono_literals; 17 | std::this_thread::sleep_for(1s); 18 | 19 | incoming_requests_ = nullptr; 20 | outgoing_ogw_responses_ = nullptr; 21 | outgoing_md_updates_ = nullptr; 22 | 23 | for(auto& order_book : ticker_order_book_) { 24 | delete order_book; 25 | order_book = nullptr; 26 | } 27 | } 28 | 29 | /// Start and stop the matching engine main thread. 30 | auto MatchingEngine::start() -> void { 31 | run_ = true; 32 | ASSERT(Common::createAndStartThread(-1, "Exchange/MatchingEngine", [this]() { run(); }) != nullptr, "Failed to start MatchingEngine thread."); 33 | } 34 | 35 | auto MatchingEngine::stop() -> void { 36 | run_ = false; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Chapter11/exchange/matcher/me_order.cpp: -------------------------------------------------------------------------------- 1 | #include "me_order.h" 2 | 3 | namespace Exchange { 4 | auto MEOrder::toString() const -> std::string { 5 | std::stringstream ss; 6 | ss << "MEOrder" << "[" 7 | << "ticker:" << tickerIdToString(ticker_id_) << " " 8 | << "cid:" << clientIdToString(client_id_) << " " 9 | << "oid:" << orderIdToString(client_order_id_) << " " 10 | << "moid:" << orderIdToString(market_order_id_) << " " 11 | << "side:" << sideToString(side_) << " " 12 | << "price:" << priceToString(price_) << " " 13 | << "qty:" << qtyToString(qty_) << " " 14 | << "prio:" << priorityToString(priority_) << " " 15 | << "prev:" << orderIdToString(prev_order_ ? prev_order_->market_order_id_ : OrderId_INVALID) << " " 16 | << "next:" << orderIdToString(next_order_ ? next_order_->market_order_id_ : OrderId_INVALID) << "]"; 17 | 18 | return ss.str(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter11/exchange/order_server/order_server.cpp: -------------------------------------------------------------------------------- 1 | #include "order_server.h" 2 | 3 | namespace Exchange { 4 | OrderServer::OrderServer(ClientRequestLFQueue *client_requests, ClientResponseLFQueue *client_responses, const std::string &iface, int port) 5 | : iface_(iface), port_(port), outgoing_responses_(client_responses), logger_("exchange_order_server.log"), 6 | tcp_server_(logger_), fifo_sequencer_(client_requests, &logger_) { 7 | cid_next_outgoing_seq_num_.fill(1); 8 | cid_next_exp_seq_num_.fill(1); 9 | cid_tcp_socket_.fill(nullptr); 10 | 11 | tcp_server_.recv_callback_ = [this](auto socket, auto rx_time) { recvCallback(socket, rx_time); }; 12 | tcp_server_.recv_finished_callback_ = [this]() { recvFinishedCallback(); }; 13 | } 14 | 15 | OrderServer::~OrderServer() { 16 | stop(); 17 | 18 | using namespace std::literals::chrono_literals; 19 | std::this_thread::sleep_for(1s); 20 | } 21 | 22 | /// Start and stop the order server main thread. 23 | auto OrderServer::start() -> void { 24 | run_ = true; 25 | tcp_server_.listen(iface_, port_); 26 | 27 | ASSERT(Common::createAndStartThread(-1, "Exchange/OrderServer", [this]() { run(); }) != nullptr, "Failed to start OrderServer thread."); 28 | } 29 | 30 | auto OrderServer::stop() -> void { 31 | run_ = false; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Chapter11/scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # TODO - point these to the correct binary locations on your system. 4 | CMAKE=$(which cmake) 5 | NINJA=$(which ninja) 6 | 7 | mkdir -p ./cmake-build-release 8 | $CMAKE -DCMAKE_BUILD_TYPE=Release -DCMAKE_MAKE_PROGRAM=$NINJA -G Ninja -S . -B ./cmake-build-release 9 | 10 | $CMAKE --build ./cmake-build-release --target clean -j 4 11 | $CMAKE --build ./cmake-build-release --target all -j 4 12 | 13 | mkdir -p ./cmake-build-debug 14 | $CMAKE -DCMAKE_BUILD_TYPE=Debug -DCMAKE_MAKE_PROGRAM=$NINJA -G Ninja -S . -B ./cmake-build-debug 15 | 16 | $CMAKE --build ./cmake-build-debug --target clean -j 4 17 | $CMAKE --build ./cmake-build-debug --target all -j 4 18 | -------------------------------------------------------------------------------- /Chapter11/scripts/no_clean_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # TODO - point these to the correct binary locations on your system. 4 | CMAKE=$(which cmake) 5 | NINJA=$(which ninja) 6 | 7 | mkdir -p ./cmake-build-release 8 | $CMAKE -DCMAKE_BUILD_TYPE=Release -DCMAKE_MAKE_PROGRAM=$NINJA -G Ninja -S . -B ./cmake-build-release 9 | 10 | $CMAKE --build ./cmake-build-release --target all -j 4 11 | 12 | mkdir -p ./cmake-build-debug 13 | $CMAKE -DCMAKE_BUILD_TYPE=Debug -DCMAKE_MAKE_PROGRAM=$NINJA -G Ninja -S . -B ./cmake-build-debug 14 | 15 | $CMAKE --build ./cmake-build-debug --target all -j 4 16 | -------------------------------------------------------------------------------- /Chapter11/scripts/run_exchange_and_clients.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | bash scripts/build.sh 4 | 5 | date 6 | 7 | echo "---------------------------------------------------------------------------------------------------------------------------------------------------------" 8 | echo "Starting Exchange..." 9 | echo "---------------------------------------------------------------------------------------------------------------------------------------------------------" 10 | ./cmake-build-release/exchange_main 2>&1 & 11 | sleep 10 12 | 13 | bash ./scripts/run_clients.sh 14 | 15 | sleep 5 16 | 17 | echo "---------------------------------------------------------------------------------------------------------------------------------------------------------" 18 | echo "Stopping Exchange..." 19 | echo "---------------------------------------------------------------------------------------------------------------------------------------------------------" 20 | pkill -2 exchange 21 | 22 | sleep 10 23 | 24 | wait 25 | date 26 | -------------------------------------------------------------------------------- /Chapter11/trading/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | set(CMAKE_CXX_COMPILER g++) 3 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 4 | set(CMAKE_VERBOSE_MAKEFILE on) 5 | 6 | file(GLOB SOURCES "*/*.cpp") 7 | 8 | include_directories(${PROJECT_SOURCE_DIR}) 9 | include_directories(${PROJECT_SOURCE_DIR}/trading) 10 | 11 | add_library(libtrading STATIC ${SOURCES} strategy/risk_manager.cpp) 12 | -------------------------------------------------------------------------------- /Chapter11/trading/strategy/liquidity_taker.cpp: -------------------------------------------------------------------------------- 1 | #include "liquidity_taker.h" 2 | 3 | #include "trade_engine.h" 4 | 5 | namespace Trading { 6 | LiquidityTaker::LiquidityTaker(Common::Logger *logger, TradeEngine *trade_engine, const FeatureEngine *feature_engine, 7 | OrderManager *order_manager, 8 | const TradeEngineCfgHashMap &ticker_cfg) 9 | : feature_engine_(feature_engine), order_manager_(order_manager), logger_(logger), 10 | ticker_cfg_(ticker_cfg) { 11 | trade_engine->algoOnOrderBookUpdate_ = [this](auto ticker_id, auto price, auto side, auto book) { 12 | onOrderBookUpdate(ticker_id, price, side, book); 13 | }; 14 | trade_engine->algoOnTradeUpdate_ = [this](auto market_update, auto book) { onTradeUpdate(market_update, book); }; 15 | trade_engine->algoOnOrderUpdate_ = [this](auto client_response) { onOrderUpdate(client_response); }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter11/trading/strategy/market_maker.cpp: -------------------------------------------------------------------------------- 1 | #include "market_maker.h" 2 | 3 | #include "trade_engine.h" 4 | 5 | namespace Trading { 6 | MarketMaker::MarketMaker(Common::Logger *logger, TradeEngine *trade_engine, const FeatureEngine *feature_engine, 7 | OrderManager *order_manager, const TradeEngineCfgHashMap &ticker_cfg) 8 | : feature_engine_(feature_engine), order_manager_(order_manager), logger_(logger), 9 | ticker_cfg_(ticker_cfg) { 10 | trade_engine->algoOnOrderBookUpdate_ = [this](auto ticker_id, auto price, auto side, auto book) { 11 | onOrderBookUpdate(ticker_id, price, side, book); 12 | }; 13 | trade_engine->algoOnTradeUpdate_ = [this](auto market_update, auto book) { onTradeUpdate(market_update, book); }; 14 | trade_engine->algoOnOrderUpdate_ = [this](auto client_response) { onOrderUpdate(client_response); }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Chapter11/trading/strategy/market_order.cpp: -------------------------------------------------------------------------------- 1 | #include "market_order.h" 2 | 3 | namespace Trading { 4 | auto MarketOrder::toString() const -> std::string { 5 | std::stringstream ss; 6 | ss << "MarketOrder" << "[" 7 | << "oid:" << orderIdToString(order_id_) << " " 8 | << "side:" << sideToString(side_) << " " 9 | << "price:" << priceToString(price_) << " " 10 | << "qty:" << qtyToString(qty_) << " " 11 | << "prio:" << priorityToString(priority_) << " " 12 | << "prev:" << orderIdToString(prev_order_ ? prev_order_->order_id_ : OrderId_INVALID) << " " 13 | << "next:" << orderIdToString(next_order_ ? next_order_->order_id_ : OrderId_INVALID) << "]"; 14 | 15 | return ss.str(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter11/trading/strategy/order_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "order_manager.h" 2 | #include "trade_engine.h" 3 | 4 | namespace Trading { 5 | /// Send a new order with specified attribute, and update the OMOrder object passed here. 6 | auto OrderManager::newOrder(OMOrder *order, TickerId ticker_id, Price price, Side side, Qty qty) noexcept -> void { 7 | const Exchange::MEClientRequest new_request{Exchange::ClientRequestType::NEW, trade_engine_->clientId(), ticker_id, 8 | next_order_id_, side, price, qty}; 9 | trade_engine_->sendClientRequest(&new_request); 10 | 11 | *order = {ticker_id, next_order_id_, side, price, qty, OMOrderState::PENDING_NEW}; 12 | ++next_order_id_; 13 | 14 | logger_->log("%:% %() % Sent new order % for %\n", __FILE__, __LINE__, __FUNCTION__, 15 | Common::getCurrentTimeStr(&time_str_), 16 | new_request.toString().c_str(), order->toString().c_str()); 17 | } 18 | 19 | /// Send a cancel for the specified order, and update the OMOrder object passed here. 20 | auto OrderManager::cancelOrder(OMOrder *order) noexcept -> void { 21 | const Exchange::MEClientRequest cancel_request{Exchange::ClientRequestType::CANCEL, trade_engine_->clientId(), 22 | order->ticker_id_, order->order_id_, order->side_, order->price_, 23 | order->qty_}; 24 | trade_engine_->sendClientRequest(&cancel_request); 25 | 26 | order->order_state_ = OMOrderState::PENDING_CANCEL; 27 | 28 | logger_->log("%:% %() % Sent cancel % for %\n", __FILE__, __LINE__, __FUNCTION__, 29 | Common::getCurrentTimeStr(&time_str_), 30 | cancel_request.toString().c_str(), order->toString().c_str()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Chapter11/trading/strategy/risk_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "risk_manager.h" 2 | 3 | #include "order_manager.h" 4 | 5 | namespace Trading { 6 | RiskManager::RiskManager(Common::Logger *logger, const PositionKeeper *position_keeper, const TradeEngineCfgHashMap &ticker_cfg) 7 | : logger_(logger) { 8 | for (TickerId i = 0; i < ME_MAX_TICKERS; ++i) { 9 | ticker_risk_.at(i).position_info_ = position_keeper->getPositionInfo(i); 10 | ticker_risk_.at(i).risk_cfg_ = ticker_cfg[i].risk_cfg_; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Chapter12/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(LowLatencyApp) 4 | 5 | set(CMAKE_CXX_STANDARD 20) 6 | set(CMAKE_CXX_COMPILER g++) 7 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 8 | set(CMAKE_VERBOSE_MAKEFILE on) 9 | 10 | add_subdirectory(common) 11 | add_subdirectory(exchange) 12 | add_subdirectory(trading) 13 | add_subdirectory(benchmarks) 14 | 15 | list(APPEND LIBS libexchange) 16 | list(APPEND LIBS libtrading) 17 | list(APPEND LIBS libcommon) 18 | list(APPEND LIBS pthread) 19 | 20 | include_directories(${PROJECT_SOURCE_DIR}) 21 | include_directories(${PROJECT_SOURCE_DIR}/exchange) 22 | include_directories(${PROJECT_SOURCE_DIR}/trading) 23 | 24 | add_executable(exchange_main exchange/exchange_main.cpp) 25 | target_link_libraries(exchange_main PUBLIC ${LIBS}) 26 | 27 | add_executable(trading_main trading/trading_main.cpp) 28 | target_link_libraries(trading_main PUBLIC ${LIBS}) 29 | 30 | add_executable(logger_benchmark benchmarks/logger_benchmark.cpp) 31 | target_link_libraries(logger_benchmark PUBLIC ${LIBS}) 32 | 33 | add_executable(release_benchmark benchmarks/release_benchmark.cpp) 34 | target_link_libraries(release_benchmark PUBLIC ${LIBS}) 35 | 36 | add_executable(hash_benchmark benchmarks/hash_benchmark.cpp) 37 | target_link_libraries(hash_benchmark PUBLIC ${LIBS}) 38 | -------------------------------------------------------------------------------- /Chapter12/benchmarks/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | set(CMAKE_CXX_COMPILER g++) 3 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 4 | set(CMAKE_VERBOSE_MAKEFILE on) 5 | 6 | include_directories(${PROJECT_SOURCE_DIR}) 7 | -------------------------------------------------------------------------------- /Chapter12/benchmarks/logger_benchmark.cpp: -------------------------------------------------------------------------------- 1 | #include "common/logging.h" 2 | #include "common/opt_logging.h" 3 | 4 | std::string random_string(size_t length) { 5 | auto randchar = []() -> char { 6 | const char charset[] = 7 | "0123456789" 8 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 9 | "abcdefghijklmnopqrstuvwxyz"; 10 | const size_t max_index = (sizeof(charset) - 1); 11 | return charset[rand() % max_index]; 12 | }; 13 | std::string str(length, 0); 14 | std::generate_n(str.begin(), length, randchar); 15 | return str; 16 | } 17 | 18 | template 19 | size_t benchmarkLogging(T *logger) { 20 | constexpr size_t loop_count = 100000; 21 | size_t total_rdtsc = 0; 22 | for (size_t i = 0; i < loop_count; ++i) { 23 | const auto s = random_string(128); 24 | const auto start = Common::rdtsc(); 25 | logger->log("%\n", s); 26 | total_rdtsc += (Common::rdtsc() - start); 27 | } 28 | 29 | return (total_rdtsc / loop_count); 30 | } 31 | 32 | int main(int, char **) { 33 | using namespace std::literals::chrono_literals; 34 | 35 | { 36 | Common::Logger logger("logger_benchmark_original.log"); 37 | const auto cycles = benchmarkLogging(&logger); 38 | std::cout << "ORIGINAL LOGGER " << cycles << " CLOCK CYCLES PER OPERATION." << std::endl; 39 | std::this_thread::sleep_for(10s); 40 | } 41 | 42 | { 43 | OptCommon::OptLogger opt_logger("logger_benchmark_optimized.log"); 44 | const auto cycles = benchmarkLogging(&opt_logger); 45 | std::cout << "OPTIMIZED LOGGER " << cycles << " CLOCK CYCLES PER OPERATION." << std::endl; 46 | std::this_thread::sleep_for(10s); 47 | } 48 | 49 | exit(EXIT_SUCCESS); 50 | } 51 | -------------------------------------------------------------------------------- /Chapter12/benchmarks/release_benchmark.cpp: -------------------------------------------------------------------------------- 1 | #include "common/mem_pool.h" 2 | #include "common/opt_mem_pool.h" 3 | #include "common/perf_utils.h" 4 | 5 | #include "exchange/market_data/market_update.h" 6 | 7 | template 8 | size_t benchmarkMemPool(T *mem_pool) { 9 | constexpr size_t loop_count = 100000; 10 | size_t total_rdtsc = 0; 11 | std::array allocated_objs; 12 | 13 | for (size_t i = 0; i < loop_count; ++i) { 14 | for(size_t j = 0; j < allocated_objs.size(); ++j) { 15 | const auto start = Common::rdtsc(); 16 | allocated_objs[j] = mem_pool->allocate(); 17 | total_rdtsc += (Common::rdtsc() - start); 18 | } 19 | for(size_t j = 0; j < allocated_objs.size(); ++j) { 20 | const auto start = Common::rdtsc(); 21 | mem_pool->deallocate(allocated_objs[j]); 22 | total_rdtsc += (Common::rdtsc() - start); 23 | } 24 | } 25 | 26 | return (total_rdtsc / (loop_count * allocated_objs.size())); 27 | } 28 | 29 | int main(int, char **) { 30 | { 31 | Common::MemPool mem_pool(512); 32 | const auto cycles = benchmarkMemPool(&mem_pool); 33 | std::cout << "ORIGINAL MEMPOOL " << cycles << " CLOCK CYCLES PER OPERATION." << std::endl; 34 | } 35 | 36 | { 37 | OptCommon::OptMemPool opt_mem_pool(512); 38 | const auto cycles = benchmarkMemPool(&opt_mem_pool); 39 | std::cout << "OPTIMIZED MEMPOOL " << cycles << " CLOCK CYCLES PER OPERATION." << std::endl; 40 | } 41 | 42 | exit(EXIT_SUCCESS); 43 | } 44 | -------------------------------------------------------------------------------- /Chapter12/common/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | set(CMAKE_CXX_COMPILER g++) 3 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 4 | set(CMAKE_VERBOSE_MAKEFILE on) 5 | 6 | file(GLOB SOURCES "*.cpp") 7 | 8 | include_directories(${PROJECT_SOURCE_DIR}) 9 | 10 | add_library(libcommon STATIC ${SOURCES}) 11 | 12 | list(APPEND LIBS libcommon) 13 | list(APPEND LIBS pthread) 14 | 15 | add_executable(thread_example thread_example.cpp) 16 | target_link_libraries(thread_example PUBLIC ${LIBS}) 17 | 18 | add_executable(mem_pool_example mem_pool_example.cpp) 19 | target_link_libraries(mem_pool_example PUBLIC ${LIBS}) 20 | 21 | add_executable(lf_queue_example lf_queue_example.cpp) 22 | target_link_libraries(lf_queue_example PUBLIC ${LIBS}) 23 | 24 | add_executable(logging_example logging_example.cpp) 25 | target_link_libraries(logging_example PUBLIC ${LIBS}) 26 | 27 | add_executable(socket_example socket_example.cpp) 28 | target_link_libraries(socket_example PUBLIC ${LIBS}) 29 | -------------------------------------------------------------------------------- /Chapter12/common/lf_queue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "macros.h" 8 | 9 | namespace Common { 10 | template 11 | class LFQueue final { 12 | public: 13 | explicit LFQueue(std::size_t num_elems) : 14 | store_(num_elems, T()) /* pre-allocation of vector storage. */ { 15 | } 16 | 17 | auto getNextToWriteTo() noexcept { 18 | return &store_[next_write_index_]; 19 | } 20 | 21 | auto updateWriteIndex() noexcept { 22 | next_write_index_ = (next_write_index_ + 1) % store_.size(); 23 | num_elements_++; 24 | } 25 | 26 | auto getNextToRead() const noexcept -> const T * { 27 | return (size() ? &store_[next_read_index_] : nullptr); 28 | } 29 | 30 | auto updateReadIndex() noexcept { 31 | next_read_index_ = (next_read_index_ + 1) % store_.size(); // wrap around at the end of container size. 32 | ASSERT(num_elements_ != 0, "Read an invalid element in:" + std::to_string(pthread_self())); 33 | num_elements_--; 34 | } 35 | 36 | auto size() const noexcept { 37 | return num_elements_.load(); 38 | } 39 | 40 | /// Deleted default, copy & move constructors and assignment-operators. 41 | LFQueue() = delete; 42 | 43 | LFQueue(const LFQueue &) = delete; 44 | 45 | LFQueue(const LFQueue &&) = delete; 46 | 47 | LFQueue &operator=(const LFQueue &) = delete; 48 | 49 | LFQueue &operator=(const LFQueue &&) = delete; 50 | 51 | private: 52 | /// Underlying container of data accessed in FIFO order. 53 | std::vector store_; 54 | 55 | /// Atomic trackers for next index to write new data to and read new data from. 56 | std::atomic next_write_index_ = {0}; 57 | std::atomic next_read_index_ = {0}; 58 | 59 | std::atomic num_elements_ = {0}; 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /Chapter12/common/lf_queue_example.cpp: -------------------------------------------------------------------------------- 1 | #include "thread_utils.h" 2 | #include "lf_queue.h" 3 | 4 | struct MyStruct { 5 | int d_[3]; 6 | }; 7 | 8 | using namespace Common; 9 | 10 | auto consumeFunction(LFQueue* lfq) { 11 | using namespace std::literals::chrono_literals; 12 | std::this_thread::sleep_for(5s); 13 | 14 | while(lfq->size()) { 15 | const auto d = lfq->getNextToRead(); 16 | lfq->updateReadIndex(); 17 | 18 | std::cout << "consumeFunction read elem:" << d->d_[0] << "," << d->d_[1] << "," << d->d_[2] << " lfq-size:" << lfq->size() << std::endl; 19 | 20 | std::this_thread::sleep_for(1s); 21 | } 22 | 23 | std::cout << "consumeFunction exiting." << std::endl; 24 | } 25 | 26 | int main(int, char **) { 27 | LFQueue lfq(20); 28 | 29 | auto ct = createAndStartThread(-1, "", consumeFunction, &lfq); 30 | 31 | for(auto i = 0; i < 50; ++i) { 32 | const MyStruct d{i, i * 10, i * 100}; 33 | *(lfq.getNextToWriteTo()) = d; 34 | lfq.updateWriteIndex(); 35 | 36 | std::cout << "main constructed elem:" << d.d_[0] << "," << d.d_[1] << "," << d.d_[2] << " lfq-size:" << lfq.size() << std::endl; 37 | 38 | using namespace std::literals::chrono_literals; 39 | std::this_thread::sleep_for(1s); 40 | } 41 | 42 | ct->join(); 43 | 44 | std::cout << "main exiting." << std::endl; 45 | 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /Chapter12/common/logging_example.cpp: -------------------------------------------------------------------------------- 1 | #include "logging.h" 2 | 3 | int main(int, char **) { 4 | using namespace Common; 5 | 6 | char c = 'd'; 7 | int i = 3; 8 | unsigned long ul = 65; 9 | float f = 3.4; 10 | double d = 34.56; 11 | const char* s = "test C-string"; 12 | std::string ss = "test string"; 13 | 14 | Logger logger("logging_example.log"); 15 | 16 | logger.log("Logging a char:% an int:% and an unsigned:%\n", c, i, ul); 17 | logger.log("Logging a float:% and a double:%\n", f, d); 18 | logger.log("Logging a C-string:'%'\n", s); 19 | logger.log("Logging a string:'%'\n", ss); 20 | 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /Chapter12/common/macros.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | /// Branch prediction hints. 7 | #define LIKELY(x) __builtin_expect(!!(x), 1) 8 | #define UNLIKELY(x) __builtin_expect(!!(x), 0) 9 | 10 | /// Check condition and exit if not true. 11 | inline auto ASSERT(bool cond, const std::string &msg) noexcept { 12 | if (UNLIKELY(!cond)) { 13 | std::cerr << "ASSERT : " << msg << std::endl; 14 | 15 | exit(EXIT_FAILURE); 16 | } 17 | } 18 | 19 | inline auto FATAL(const std::string &msg) noexcept { 20 | std::cerr << "FATAL : " << msg << std::endl; 21 | 22 | exit(EXIT_FAILURE); 23 | } 24 | -------------------------------------------------------------------------------- /Chapter12/common/mcast_socket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "socket_utils.h" 6 | 7 | #include "logging.h" 8 | 9 | namespace Common { 10 | /// Size of send and receive buffers in bytes. 11 | constexpr size_t McastBufferSize = 64 * 1024 * 1024; 12 | 13 | struct McastSocket { 14 | McastSocket(Logger &logger) 15 | : logger_(logger) { 16 | outbound_data_.resize(McastBufferSize); 17 | inbound_data_.resize(McastBufferSize); 18 | } 19 | 20 | /// Initialize multicast socket to read from or publish to a stream. 21 | /// Does not join the multicast stream yet. 22 | auto init(const std::string &ip, const std::string &iface, int port, bool is_listening) -> int; 23 | 24 | /// Add / Join membership / subscription to a multicast stream. 25 | auto join(const std::string &ip) -> bool; 26 | 27 | /// Remove / Leave membership / subscription to a multicast stream. 28 | auto leave(const std::string &ip, int port) -> void; 29 | 30 | /// Publish outgoing data and read incoming data. 31 | auto sendAndRecv() noexcept -> bool; 32 | 33 | /// Copy data to send buffers - does not send them out yet. 34 | auto send(const void *data, size_t len) noexcept -> void; 35 | 36 | int socket_fd_ = -1; 37 | 38 | /// Send and receive buffers, typically only one or the other is needed, not both. 39 | std::vector outbound_data_; 40 | size_t next_send_valid_index_ = 0; 41 | std::vector inbound_data_; 42 | size_t next_rcv_valid_index_ = 0; 43 | 44 | /// Function wrapper for the method to call when data is read. 45 | std::function recv_callback_ = nullptr; 46 | 47 | std::string time_str_; 48 | Logger &logger_; 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /Chapter12/common/mem_pool_example.cpp: -------------------------------------------------------------------------------- 1 | #include "mem_pool.h" 2 | 3 | struct MyStruct { 4 | int d_[3]; 5 | }; 6 | 7 | int main(int, char **) { 8 | using namespace Common; 9 | 10 | MemPool prim_pool(50); 11 | MemPool struct_pool(50); 12 | 13 | for(auto i = 0; i < 50; ++i) { 14 | auto p_ret = prim_pool.allocate(i); 15 | auto s_ret = struct_pool.allocate(MyStruct{i, i+1, i+2}); 16 | 17 | std::cout << "prim elem:" << *p_ret << " allocated at:" << p_ret << std::endl; 18 | std::cout << "struct elem:" << s_ret->d_[0] << "," << s_ret->d_[1] << "," << s_ret->d_[2] << " allocated at:" << s_ret << std::endl; 19 | 20 | if(i % 5 == 0) { 21 | std::cout << "deallocating prim elem:" << *p_ret << " from:" << p_ret << std::endl; 22 | std::cout << "deallocating struct elem:" << s_ret->d_[0] << "," << s_ret->d_[1] << "," << s_ret->d_[2] << " from:" << s_ret << std::endl; 23 | 24 | prim_pool.deallocate(p_ret); 25 | struct_pool.deallocate(s_ret); 26 | } 27 | } 28 | 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /Chapter12/common/perf_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Common { 4 | /// Read from the TSC register and return a uint64_t value to represent elapsed CPU clock cycles. 5 | inline auto rdtsc() noexcept { 6 | unsigned int lo, hi; 7 | __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); 8 | return ((uint64_t) hi << 32) | lo; 9 | } 10 | } 11 | 12 | /// Start latency measurement using rdtsc(). Creates a variable called TAG in the local scope. 13 | #define START_MEASURE(TAG) const auto TAG = Common::rdtsc() 14 | 15 | /// End latency measurement using rdtsc(). Expects a variable called TAG to already exist in the local scope. 16 | #define END_MEASURE(TAG, LOGGER) \ 17 | do { \ 18 | const auto end = Common::rdtsc(); \ 19 | LOGGER.log("% RDTSC "#TAG" %\n", Common::getCurrentTimeStr(&time_str_), (end - TAG)); \ 20 | } while(false) 21 | 22 | /// Log a current timestamp at the time this macro is invoked. 23 | #define TTT_MEASURE(TAG, LOGGER) \ 24 | do { \ 25 | const auto TAG = Common::getCurrentNanos(); \ 26 | LOGGER.log("% TTT "#TAG" %\n", Common::getCurrentTimeStr(&time_str_), TAG); \ 27 | } while(false) 28 | -------------------------------------------------------------------------------- /Chapter12/common/tcp_server.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "tcp_socket.h" 4 | 5 | namespace Common { 6 | struct TCPServer { 7 | explicit TCPServer(Logger &logger) 8 | : listener_socket_(logger), logger_(logger) { 9 | } 10 | 11 | /// Start listening for connections on the provided interface and port. 12 | auto listen(const std::string &iface, int port) -> void; 13 | 14 | /// Check for new connections or dead connections and update containers that track the sockets. 15 | auto poll() noexcept -> void; 16 | 17 | /// Publish outgoing data from the send buffer and read incoming data from the receive buffer. 18 | auto sendAndRecv() noexcept -> void; 19 | 20 | private: 21 | /// Add and remove socket file descriptors to and from the EPOLL list. 22 | auto addToEpollList(TCPSocket *socket); 23 | 24 | public: 25 | /// Socket on which this server is listening for new connections on. 26 | int epoll_fd_ = -1; 27 | TCPSocket listener_socket_; 28 | 29 | epoll_event events_[1024]; 30 | 31 | /// Collection of all sockets, sockets for incoming data, sockets for outgoing data and dead connections. 32 | std::vector receive_sockets_, send_sockets_; 33 | 34 | /// Function wrapper to call back when data is available. 35 | std::function recv_callback_ = nullptr; 36 | /// Function wrapper to call back when all data across all TCPSockets has been read and dispatched this round. 37 | std::function recv_finished_callback_ = nullptr; 38 | 39 | std::string time_str_; 40 | Logger &logger_; 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /Chapter12/common/tcp_socket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "socket_utils.h" 6 | #include "logging.h" 7 | 8 | namespace Common { 9 | /// Size of our send and receive buffers in bytes. 10 | constexpr size_t TCPBufferSize = 64 * 1024 * 1024; 11 | 12 | struct TCPSocket { 13 | explicit TCPSocket(Logger &logger) 14 | : logger_(logger) { 15 | outbound_data_.resize(TCPBufferSize); 16 | inbound_data_.resize(TCPBufferSize); 17 | } 18 | 19 | /// Create TCPSocket with provided attributes to either listen-on / connect-to. 20 | auto connect(const std::string &ip, const std::string &iface, int port, bool is_listening) -> int; 21 | 22 | /// Called to publish outgoing data from the buffers as well as check for and callback if data is available in the read buffers. 23 | auto sendAndRecv() noexcept -> bool; 24 | 25 | /// Write outgoing data to the send buffers. 26 | auto send(const void *data, size_t len) noexcept -> void; 27 | 28 | /// Deleted default, copy & move constructors and assignment-operators. 29 | TCPSocket() = delete; 30 | 31 | TCPSocket(const TCPSocket &) = delete; 32 | 33 | TCPSocket(const TCPSocket &&) = delete; 34 | 35 | TCPSocket &operator=(const TCPSocket &) = delete; 36 | 37 | TCPSocket &operator=(const TCPSocket &&) = delete; 38 | 39 | /// File descriptor for the socket. 40 | int socket_fd_ = -1; 41 | 42 | /// Send and receive buffers and trackers for read/write indices. 43 | std::vector outbound_data_; 44 | size_t next_send_valid_index_ = 0; 45 | std::vector inbound_data_; 46 | size_t next_rcv_valid_index_ = 0; 47 | 48 | /// Socket attributes. 49 | struct sockaddr_in socket_attrib_{}; 50 | 51 | /// Function wrapper to callback when there is data to be processed. 52 | std::function recv_callback_ = nullptr; 53 | 54 | std::string time_str_; 55 | Logger &logger_; 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /Chapter12/common/thread_example.cpp: -------------------------------------------------------------------------------- 1 | #include "thread_utils.h" 2 | 3 | auto dummyFunction(int a, int b, bool sleep) { 4 | std::cout << "dummyFunction(" << a << "," << b << ")" << std::endl; 5 | std::cout << "dummyFunction output=" << a + b << std::endl; 6 | 7 | if(sleep) { 8 | std::cout << "dummyFunction sleeping..." << std::endl; 9 | 10 | using namespace std::literals::chrono_literals; 11 | std::this_thread::sleep_for(5s); 12 | } 13 | 14 | std::cout << "dummyFunction done." << std::endl; 15 | } 16 | 17 | int main(int, char **) { 18 | using namespace Common; 19 | 20 | auto t1 = createAndStartThread(-1, "dummyFunction1", dummyFunction, 12, 21, false); 21 | auto t2 = createAndStartThread(1, "dummyFunction2", dummyFunction, 15, 51, true); 22 | 23 | std::cout << "main waiting for threads to be done." << std::endl; 24 | t1->join(); 25 | t2->join(); 26 | std::cout << "main exiting." << std::endl; 27 | 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /Chapter12/common/thread_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace Common { 11 | /// Set affinity for current thread to be pinned to the provided core_id. 12 | inline auto setThreadCore(int core_id) noexcept { 13 | cpu_set_t cpuset; 14 | 15 | CPU_ZERO(&cpuset); 16 | CPU_SET(core_id, &cpuset); 17 | 18 | return (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) == 0); 19 | } 20 | 21 | /// Creates a thread instance, sets affinity on it, assigns it a name and 22 | /// passes the function to be run on that thread as well as the arguments to the function. 23 | template 24 | inline auto createAndStartThread(int core_id, const std::string &name, T &&func, A &&... args) noexcept { 25 | auto t = new std::thread([&]() { 26 | if (core_id >= 0 && !setThreadCore(core_id)) { 27 | std::cerr << "Failed to set core affinity for " << name << " " << pthread_self() << " to " << core_id << std::endl; 28 | exit(EXIT_FAILURE); 29 | } 30 | std::cerr << "Set core affinity for " << name << " " << pthread_self() << " to " << core_id << std::endl; 31 | 32 | std::forward(func)((std::forward(args))...); 33 | }); 34 | 35 | using namespace std::literals::chrono_literals; 36 | std::this_thread::sleep_for(1s); 37 | 38 | return t; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Chapter12/common/time_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "perf_utils.h" 8 | 9 | namespace Common { 10 | /// Represent a nanosecond timestamp. 11 | typedef int64_t Nanos; 12 | 13 | /// Convert between nanos, micros, millis and secs. 14 | constexpr Nanos NANOS_TO_MICROS = 1000; 15 | constexpr Nanos MICROS_TO_MILLIS = 1000; 16 | constexpr Nanos MILLIS_TO_SECS = 1000; 17 | constexpr Nanos NANOS_TO_MILLIS = NANOS_TO_MICROS * MICROS_TO_MILLIS; 18 | constexpr Nanos NANOS_TO_SECS = NANOS_TO_MILLIS * MILLIS_TO_SECS; 19 | 20 | /// Get current nanosecond timestamp. 21 | inline auto getCurrentNanos() noexcept { 22 | return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); 23 | } 24 | 25 | /// Format current timestamp to a human readable string. 26 | /// String formatting is inefficient. 27 | inline auto& getCurrentTimeStr(std::string* time_str) { 28 | const auto clock = std::chrono::system_clock::now(); 29 | const auto time = std::chrono::system_clock::to_time_t(clock); 30 | 31 | char nanos_str[24]; 32 | sprintf(nanos_str, "%.8s.%09ld", ctime(&time) + 11, std::chrono::duration_cast(clock.time_since_epoch()).count() % NANOS_TO_SECS); 33 | time_str->assign(nanos_str); 34 | 35 | return *time_str; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Chapter12/exchange/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | set(CMAKE_CXX_COMPILER g++) 3 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 4 | set(CMAKE_VERBOSE_MAKEFILE on) 5 | 6 | file(GLOB SOURCES "*/*.cpp") 7 | 8 | include_directories(${PROJECT_SOURCE_DIR}) 9 | include_directories(${PROJECT_SOURCE_DIR}/exchange) 10 | 11 | add_library(libexchange STATIC ${SOURCES}) 12 | -------------------------------------------------------------------------------- /Chapter12/exchange/matcher/matching_engine.cpp: -------------------------------------------------------------------------------- 1 | #include "matching_engine.h" 2 | 3 | namespace Exchange { 4 | MatchingEngine::MatchingEngine(ClientRequestLFQueue *client_requests, ClientResponseLFQueue *client_responses, 5 | MEMarketUpdateLFQueue *market_updates) 6 | : incoming_requests_(client_requests), outgoing_ogw_responses_(client_responses), outgoing_md_updates_(market_updates), 7 | logger_("exchange_matching_engine.log") { 8 | for(size_t i = 0; i < ticker_order_book_.size(); ++i) { 9 | ticker_order_book_[i] = new MEOrderBook(i, &logger_, this); 10 | } 11 | } 12 | 13 | MatchingEngine::~MatchingEngine() { 14 | stop(); 15 | 16 | using namespace std::literals::chrono_literals; 17 | std::this_thread::sleep_for(1s); 18 | 19 | incoming_requests_ = nullptr; 20 | outgoing_ogw_responses_ = nullptr; 21 | outgoing_md_updates_ = nullptr; 22 | 23 | for(auto& order_book : ticker_order_book_) { 24 | delete order_book; 25 | order_book = nullptr; 26 | } 27 | } 28 | 29 | /// Start and stop the matching engine main thread. 30 | auto MatchingEngine::start() -> void { 31 | run_ = true; 32 | ASSERT(Common::createAndStartThread(-1, "Exchange/MatchingEngine", [this]() { run(); }) != nullptr, "Failed to start MatchingEngine thread."); 33 | } 34 | 35 | auto MatchingEngine::stop() -> void { 36 | run_ = false; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Chapter12/exchange/matcher/me_order.cpp: -------------------------------------------------------------------------------- 1 | #include "me_order.h" 2 | 3 | namespace Exchange { 4 | auto MEOrder::toString() const -> std::string { 5 | std::stringstream ss; 6 | ss << "MEOrder" << "[" 7 | << "ticker:" << tickerIdToString(ticker_id_) << " " 8 | << "cid:" << clientIdToString(client_id_) << " " 9 | << "oid:" << orderIdToString(client_order_id_) << " " 10 | << "moid:" << orderIdToString(market_order_id_) << " " 11 | << "side:" << sideToString(side_) << " " 12 | << "price:" << priceToString(price_) << " " 13 | << "qty:" << qtyToString(qty_) << " " 14 | << "prio:" << priorityToString(priority_) << " " 15 | << "prev:" << orderIdToString(prev_order_ ? prev_order_->market_order_id_ : OrderId_INVALID) << " " 16 | << "next:" << orderIdToString(next_order_ ? next_order_->market_order_id_ : OrderId_INVALID) << "]"; 17 | 18 | return ss.str(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter12/exchange/order_server/order_server.cpp: -------------------------------------------------------------------------------- 1 | #include "order_server.h" 2 | 3 | namespace Exchange { 4 | OrderServer::OrderServer(ClientRequestLFQueue *client_requests, ClientResponseLFQueue *client_responses, const std::string &iface, int port) 5 | : iface_(iface), port_(port), outgoing_responses_(client_responses), logger_("exchange_order_server.log"), 6 | tcp_server_(logger_), fifo_sequencer_(client_requests, &logger_) { 7 | cid_next_outgoing_seq_num_.fill(1); 8 | cid_next_exp_seq_num_.fill(1); 9 | cid_tcp_socket_.fill(nullptr); 10 | 11 | tcp_server_.recv_callback_ = [this](auto socket, auto rx_time) { recvCallback(socket, rx_time); }; 12 | tcp_server_.recv_finished_callback_ = [this]() { recvFinishedCallback(); }; 13 | } 14 | 15 | OrderServer::~OrderServer() { 16 | stop(); 17 | 18 | using namespace std::literals::chrono_literals; 19 | std::this_thread::sleep_for(1s); 20 | } 21 | 22 | /// Start and stop the order server main thread. 23 | auto OrderServer::start() -> void { 24 | run_ = true; 25 | tcp_server_.listen(iface_, port_); 26 | 27 | ASSERT(Common::createAndStartThread(-1, "Exchange/OrderServer", [this]() { run(); }) != nullptr, "Failed to start OrderServer thread."); 28 | } 29 | 30 | auto OrderServer::stop() -> void { 31 | run_ = false; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Chapter12/scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # TODO - point these to the correct binary locations on your system. 4 | CMAKE=$(which cmake) 5 | NINJA=$(which ninja) 6 | 7 | mkdir -p ./cmake-build-release 8 | $CMAKE -DCMAKE_BUILD_TYPE=Release -DCMAKE_MAKE_PROGRAM=$NINJA -G Ninja -S . -B ./cmake-build-release 9 | 10 | $CMAKE --build ./cmake-build-release --target clean -j 4 11 | $CMAKE --build ./cmake-build-release --target all -j 4 12 | 13 | mkdir -p ./cmake-build-debug 14 | $CMAKE -DCMAKE_BUILD_TYPE=Debug -DCMAKE_MAKE_PROGRAM=$NINJA -G Ninja -S . -B ./cmake-build-debug 15 | 16 | $CMAKE --build ./cmake-build-debug --target clean -j 4 17 | $CMAKE --build ./cmake-build-debug --target all -j 4 18 | -------------------------------------------------------------------------------- /Chapter12/scripts/no_clean_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # TODO - point these to the correct binary locations on your system. 4 | CMAKE=$(which cmake) 5 | NINJA=$(which ninja) 6 | 7 | mkdir -p ./cmake-build-release 8 | $CMAKE -DCMAKE_BUILD_TYPE=Release -DCMAKE_MAKE_PROGRAM=$NINJA -G Ninja -S . -B ./cmake-build-release 9 | 10 | $CMAKE --build ./cmake-build-release --target all -j 4 11 | 12 | mkdir -p ./cmake-build-debug 13 | $CMAKE -DCMAKE_BUILD_TYPE=Debug -DCMAKE_MAKE_PROGRAM=$NINJA -G Ninja -S . -B ./cmake-build-debug 14 | 15 | $CMAKE --build ./cmake-build-debug --target all -j 4 16 | -------------------------------------------------------------------------------- /Chapter12/scripts/run_benchmarks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | bash scripts/build.sh 4 | 5 | date 6 | 7 | echo "---------------------------------------------------------------------------------------------------------------------------------------------------------" 8 | echo " Benchmark before and after optimization for Logger string handling. " 9 | echo "---------------------------------------------------------------------------------------------------------------------------------------------------------" 10 | ./cmake-build-release/logger_benchmark 11 | 12 | echo "---------------------------------------------------------------------------------------------------------------------------------------------------------" 13 | echo " Benchmark before and after optimization for release builds. " 14 | echo "---------------------------------------------------------------------------------------------------------------------------------------------------------" 15 | ./cmake-build-release/release_benchmark 16 | 17 | echo "---------------------------------------------------------------------------------------------------------------------------------------------------------" 18 | echo " Benchmark using std::arrays and std::unordered_maps as hash maps. " 19 | echo "---------------------------------------------------------------------------------------------------------------------------------------------------------" 20 | ./cmake-build-release/hash_benchmark -------------------------------------------------------------------------------- /Chapter12/scripts/run_exchange_and_clients.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | bash scripts/build.sh 4 | 5 | date 6 | 7 | echo "---------------------------------------------------------------------------------------------------------------------------------------------------------" 8 | echo "Starting Exchange..." 9 | echo "---------------------------------------------------------------------------------------------------------------------------------------------------------" 10 | ./cmake-build-release/exchange_main 2>&1 & 11 | sleep 10 12 | 13 | bash ./scripts/run_clients.sh 14 | 15 | sleep 5 16 | 17 | echo "---------------------------------------------------------------------------------------------------------------------------------------------------------" 18 | echo "Stopping Exchange..." 19 | echo "---------------------------------------------------------------------------------------------------------------------------------------------------------" 20 | pkill -2 exchange 21 | 22 | sleep 10 23 | 24 | wait 25 | date 26 | -------------------------------------------------------------------------------- /Chapter12/trading/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | set(CMAKE_CXX_COMPILER g++) 3 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 4 | set(CMAKE_VERBOSE_MAKEFILE on) 5 | 6 | file(GLOB SOURCES "*/*.cpp") 7 | 8 | include_directories(${PROJECT_SOURCE_DIR}) 9 | include_directories(${PROJECT_SOURCE_DIR}/trading) 10 | 11 | add_library(libtrading STATIC ${SOURCES} strategy/risk_manager.cpp) 12 | -------------------------------------------------------------------------------- /Chapter12/trading/strategy/liquidity_taker.cpp: -------------------------------------------------------------------------------- 1 | #include "liquidity_taker.h" 2 | 3 | #include "trade_engine.h" 4 | 5 | namespace Trading { 6 | LiquidityTaker::LiquidityTaker(Common::Logger *logger, TradeEngine *trade_engine, const FeatureEngine *feature_engine, 7 | OrderManager *order_manager, 8 | const TradeEngineCfgHashMap &ticker_cfg) 9 | : feature_engine_(feature_engine), order_manager_(order_manager), logger_(logger), 10 | ticker_cfg_(ticker_cfg) { 11 | trade_engine->algoOnOrderBookUpdate_ = [this](auto ticker_id, auto price, auto side, auto book) { 12 | onOrderBookUpdate(ticker_id, price, side, book); 13 | }; 14 | trade_engine->algoOnTradeUpdate_ = [this](auto market_update, auto book) { onTradeUpdate(market_update, book); }; 15 | trade_engine->algoOnOrderUpdate_ = [this](auto client_response) { onOrderUpdate(client_response); }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter12/trading/strategy/market_maker.cpp: -------------------------------------------------------------------------------- 1 | #include "market_maker.h" 2 | 3 | #include "trade_engine.h" 4 | 5 | namespace Trading { 6 | MarketMaker::MarketMaker(Common::Logger *logger, TradeEngine *trade_engine, const FeatureEngine *feature_engine, 7 | OrderManager *order_manager, const TradeEngineCfgHashMap &ticker_cfg) 8 | : feature_engine_(feature_engine), order_manager_(order_manager), logger_(logger), 9 | ticker_cfg_(ticker_cfg) { 10 | trade_engine->algoOnOrderBookUpdate_ = [this](auto ticker_id, auto price, auto side, auto book) { 11 | onOrderBookUpdate(ticker_id, price, side, book); 12 | }; 13 | trade_engine->algoOnTradeUpdate_ = [this](auto market_update, auto book) { onTradeUpdate(market_update, book); }; 14 | trade_engine->algoOnOrderUpdate_ = [this](auto client_response) { onOrderUpdate(client_response); }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Chapter12/trading/strategy/market_order.cpp: -------------------------------------------------------------------------------- 1 | #include "market_order.h" 2 | 3 | namespace Trading { 4 | auto MarketOrder::toString() const -> std::string { 5 | std::stringstream ss; 6 | ss << "MarketOrder" << "[" 7 | << "oid:" << orderIdToString(order_id_) << " " 8 | << "side:" << sideToString(side_) << " " 9 | << "price:" << priceToString(price_) << " " 10 | << "qty:" << qtyToString(qty_) << " " 11 | << "prio:" << priorityToString(priority_) << " " 12 | << "prev:" << orderIdToString(prev_order_ ? prev_order_->order_id_ : OrderId_INVALID) << " " 13 | << "next:" << orderIdToString(next_order_ ? next_order_->order_id_ : OrderId_INVALID) << "]"; 14 | 15 | return ss.str(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter12/trading/strategy/order_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "order_manager.h" 2 | #include "trade_engine.h" 3 | 4 | namespace Trading { 5 | /// Send a new order with specified attribute, and update the OMOrder object passed here. 6 | auto OrderManager::newOrder(OMOrder *order, TickerId ticker_id, Price price, Side side, Qty qty) noexcept -> void { 7 | const Exchange::MEClientRequest new_request{Exchange::ClientRequestType::NEW, trade_engine_->clientId(), ticker_id, 8 | next_order_id_, side, price, qty}; 9 | trade_engine_->sendClientRequest(&new_request); 10 | 11 | *order = {ticker_id, next_order_id_, side, price, qty, OMOrderState::PENDING_NEW}; 12 | ++next_order_id_; 13 | 14 | logger_->log("%:% %() % Sent new order % for %\n", __FILE__, __LINE__, __FUNCTION__, 15 | Common::getCurrentTimeStr(&time_str_), 16 | new_request.toString().c_str(), order->toString().c_str()); 17 | } 18 | 19 | /// Send a cancel for the specified order, and update the OMOrder object passed here. 20 | auto OrderManager::cancelOrder(OMOrder *order) noexcept -> void { 21 | const Exchange::MEClientRequest cancel_request{Exchange::ClientRequestType::CANCEL, trade_engine_->clientId(), 22 | order->ticker_id_, order->order_id_, order->side_, order->price_, 23 | order->qty_}; 24 | trade_engine_->sendClientRequest(&cancel_request); 25 | 26 | order->order_state_ = OMOrderState::PENDING_CANCEL; 27 | 28 | logger_->log("%:% %() % Sent cancel % for %\n", __FILE__, __LINE__, __FUNCTION__, 29 | Common::getCurrentTimeStr(&time_str_), 30 | cancel_request.toString().c_str(), order->toString().c_str()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Chapter12/trading/strategy/risk_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "risk_manager.h" 2 | 3 | #include "order_manager.h" 4 | 5 | namespace Trading { 6 | RiskManager::RiskManager(Common::Logger *logger, const PositionKeeper *position_keeper, const TradeEngineCfgHashMap &ticker_cfg) 7 | : logger_(logger) { 8 | for (TickerId i = 0; i < ME_MAX_TICKERS; ++i) { 9 | ticker_risk_.at(i).position_info_ = position_keeper->getPositionInfo(i); 10 | ticker_risk_.at(i).risk_cfg_ = ticker_cfg[i].risk_cfg_; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Chapter3/alignment.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | struct PoorlyAlignedData { 6 | char c; 7 | uint16_t u; 8 | double d; 9 | int16_t i; 10 | }; 11 | 12 | struct WellAlignedData { 13 | double d; 14 | uint16_t u; 15 | int16_t i; 16 | char c; 17 | }; 18 | 19 | #pragma pack(push, 1) 20 | struct PackedData { 21 | double d; 22 | uint16_t u; 23 | int16_t i; 24 | char c; 25 | }; 26 | #pragma pack(pop) 27 | 28 | int main() { 29 | printf("PoorlyAlignedData c:%lu u:%lu d:%lu i:%lu size:%lu\n", 30 | offsetof(struct PoorlyAlignedData,c), offsetof(struct PoorlyAlignedData,u), offsetof(struct PoorlyAlignedData,d), offsetof(struct PoorlyAlignedData,i), sizeof(PoorlyAlignedData)); 31 | printf("WellAlignedData d:%lu u:%lu i:%lu c:%lu size:%lu\n", 32 | offsetof(struct WellAlignedData,d), offsetof(struct WellAlignedData,u), offsetof(struct WellAlignedData,i), offsetof(struct WellAlignedData,c), sizeof(WellAlignedData)); 33 | printf("PackedData d:%lu u:%lu i:%lu c:%lu size:%lu\n", 34 | offsetof(struct PackedData,d), offsetof(struct PackedData,u), offsetof(struct PackedData,i), offsetof(struct PackedData,c), sizeof(PackedData)); 35 | } -------------------------------------------------------------------------------- /Chapter3/branch.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | enum class Side : int16_t { BUY = 1, SELL = -1 }; 6 | 7 | int main() { 8 | const auto fill_side = (rand() % 2 ? Side::BUY : Side::SELL); 9 | const int fill_qty = 10; 10 | printf("fill_side:%s fill_qty:%d.\n", (fill_side == Side::BUY ? "BUY" : (fill_side == Side::SELL ? "SELL" : "INVALID")), fill_qty); 11 | 12 | { // with branching 13 | int last_buy_qty = 0, last_sell_qty = 0, position = 0; 14 | 15 | if (fill_side == Side::BUY) { position += fill_qty; last_buy_qty = fill_qty; 16 | } else if (fill_side == Side::SELL) { position -= fill_qty; last_sell_qty = fill_qty; } 17 | 18 | printf("With branching - position:%d last-buy:%d last-sell:%d.\n", position, last_buy_qty, last_sell_qty); 19 | } 20 | 21 | { // without branching 22 | int last_qty[3] = {0, 0, 0}, position = 0; 23 | 24 | auto sideToInt = [](Side side) noexcept { return static_cast(side); }; 25 | 26 | const auto int_fill_side = sideToInt(fill_side); 27 | position += int_fill_side * fill_qty; 28 | last_qty[int_fill_side + 1] = fill_qty; 29 | 30 | printf("Without branching - position:%d last-buy:%d last-sell:%d.\n", position, last_qty[sideToInt(Side::BUY) + 1], last_qty[sideToInt(Side::SELL) + 1]); 31 | } 32 | } -------------------------------------------------------------------------------- /Chapter3/composition.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | struct Order { 5 | int id; 6 | double price; 7 | }; 8 | 9 | class InheritanceOrderBook : public std::vector { 10 | }; 11 | 12 | class CompositionOrderBook { 13 | std::vector orders_; 14 | 15 | public: 16 | auto size() const noexcept { 17 | return orders_.size(); 18 | } 19 | }; 20 | 21 | int main() { 22 | InheritanceOrderBook i_book; 23 | CompositionOrderBook c_book; 24 | 25 | printf("InheritanceOrderBook::size():%lu CompositionOrderBook:%lu\n", i_book.size(), c_book.size()); 26 | } -------------------------------------------------------------------------------- /Chapter3/crtp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class RuntimeExample { 4 | public: 5 | virtual void placeOrder() { 6 | printf("RuntimeExample::placeOrder()\n"); 7 | } 8 | }; 9 | 10 | class SpecificRuntimeExample : public RuntimeExample { 11 | public: 12 | void placeOrder() override { 13 | printf("SpecificRuntimeExample::placeOrder()\n"); 14 | } 15 | }; 16 | 17 | template 18 | class CRTPExample { 19 | public: 20 | void placeOrder() { 21 | static_cast(this)->actualPlaceOrder(); 22 | } 23 | 24 | void actualPlaceOrder() { 25 | printf("CRTPExample::actualPlaceOrder()\n"); 26 | } 27 | }; 28 | 29 | class SpecificCRTPExample : public CRTPExample { 30 | public: 31 | void actualPlaceOrder() { 32 | printf("SpecificCRTPExample::actualPlaceOrder()\n"); 33 | } 34 | }; 35 | 36 | int main(int, char **) { 37 | RuntimeExample *runtime_example = new SpecificRuntimeExample(); 38 | runtime_example->placeOrder(); 39 | 40 | CRTPExample crtp_example; 41 | crtp_example.placeOrder(); 42 | 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /Chapter3/induction.cpp: -------------------------------------------------------------------------------- 1 | int main() { 2 | [[maybe_unused]] int a[100]; 3 | 4 | // original 5 | for(auto i = 0; i < 100; ++i) 6 | a[i] = i * 10 + 12; 7 | 8 | // optimized 9 | int temp = 12; 10 | for(auto i = 0; i < 100; ++i) { 11 | a[i] = temp; 12 | temp += 10; 13 | } 14 | } -------------------------------------------------------------------------------- /Chapter3/loop_invariant.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | auto doSomething = [](double r) noexcept { return 3.14 * r * r; }; 5 | [[maybe_unused]] int a[100], b = rand(); 6 | 7 | // original 8 | for(auto i = 0; i < 100; ++i) 9 | a[i] = (doSomething(50) + b * 2) + 1; 10 | 11 | // loop invariant 12 | auto temp = (doSomething(50) + b * 2) + 1; 13 | for(auto i = 0; i < 100; ++i) 14 | a[i] = temp; 15 | } -------------------------------------------------------------------------------- /Chapter3/loop_unroll.cpp: -------------------------------------------------------------------------------- 1 | int main() { 2 | { // original code. 3 | int a[5]; 4 | a[0] = 0; 5 | for(int i = 1; i < 5; ++i) 6 | a[i] = a[i-1] + 1; 7 | } 8 | 9 | { // loop unrolled code. 10 | int a[5]; 11 | a[0] = 0; 12 | a[1] = a[0] + 1; 13 | a[2] = a[1] + 1; 14 | a[3] = a[2] + 1; 15 | a[4] = a[3] + 1; 16 | } 17 | } -------------------------------------------------------------------------------- /Chapter3/pointer_alias.cpp: -------------------------------------------------------------------------------- 1 | void func(int *a, int *b, int n) { 2 | for (int i = 0; i < n; ++i) { 3 | a[i] = *b; 4 | } 5 | } 6 | 7 | void func_restrict(int *__restrict a, int *__restrict b, int n) { 8 | for (int i = 0; i < n; ++i) { 9 | a[i] = *b; 10 | } 11 | } 12 | 13 | int main() { 14 | int a[10], b; 15 | func(a, &b, 10); 16 | func_restrict(a, &b, 10); 17 | } -------------------------------------------------------------------------------- /Chapter3/rvo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | struct LargeClass { 4 | int i; 5 | char c; 6 | double d; 7 | }; 8 | 9 | auto rvoExample(int i, char c, double d) { 10 | return LargeClass{i, c, d}; 11 | } 12 | 13 | int main() { 14 | LargeClass lc_obj = rvoExample(10, 'c', 3.14); 15 | } -------------------------------------------------------------------------------- /Chapter3/strength.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | const auto price = 10.125; // prices are like: 10.125, 10.130, 10.135... 5 | constexpr auto min_price_increment = 0.005; 6 | [[maybe_unused]] int64_t int_price = 0; 7 | 8 | // no strength reduction 9 | int_price = price / min_price_increment; 10 | 11 | // strength reduction 12 | constexpr auto inv_min_price_increment = 1 / min_price_increment; 13 | int_price = price * inv_min_price_increment; 14 | } -------------------------------------------------------------------------------- /Chapter3/strict_alias.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | double x = 100; 6 | const auto orig_x = x; 7 | 8 | auto x_as_ui = (uint64_t *) (&x); 9 | *x_as_ui |= 0x8000000000000000; 10 | 11 | printf("orig_x:%0.2f x:%0.2f &x:%p &x_as_ui:%p\n", orig_x, x, &x, x_as_ui); 12 | } 13 | -------------------------------------------------------------------------------- /Chapter3/tail_call.cpp: -------------------------------------------------------------------------------- 1 | auto __attribute__ ((noinline)) factorial(unsigned n) -> unsigned { 2 | return (n ? n * factorial(n - 1) : 1); 3 | } 4 | 5 | int main() { 6 | [[maybe_unused]] volatile auto res = factorial(100); 7 | } -------------------------------------------------------------------------------- /Chapter3/vector.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { 4 | const size_t size = 1024; 5 | [[maybe_unused]] float x[size], a[size], b[size]; 6 | 7 | // no vectorization 8 | for (size_t i = 0; i < size; ++i) { 9 | x[i] = a[i] + b[i]; 10 | } 11 | 12 | // vectorization 13 | for (size_t i = 0; i < size; i += 4) { 14 | x[i] = a[i] + b[i]; 15 | x[i + 1] = a[i + 1] + b[i + 1]; 16 | x[i + 2] = a[i + 2] + b[i + 2]; 17 | x[i + 3] = a[i + 3] + b[i + 3]; 18 | } 19 | } -------------------------------------------------------------------------------- /Chapter4/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(LowLatencyApp) 4 | 5 | set(CMAKE_CXX_STANDARD 20) 6 | set(CMAKE_CXX_COMPILER g++) 7 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 8 | set(CMAKE_VERBOSE_MAKEFILE on) 9 | 10 | file(GLOB SOURCES "*.cpp") 11 | 12 | include_directories(${PROJECT_SOURCE_DIR}) 13 | 14 | add_library(libcommon STATIC ${SOURCES}) 15 | 16 | list(APPEND LIBS libcommon) 17 | list(APPEND LIBS pthread) 18 | 19 | add_executable(thread_example thread_example.cpp) 20 | target_link_libraries(thread_example PUBLIC ${LIBS}) 21 | 22 | add_executable(mem_pool_example mem_pool_example.cpp) 23 | target_link_libraries(mem_pool_example PUBLIC ${LIBS}) 24 | 25 | add_executable(lf_queue_example lf_queue_example.cpp) 26 | target_link_libraries(lf_queue_example PUBLIC ${LIBS}) 27 | 28 | add_executable(logging_example logging_example.cpp) 29 | target_link_libraries(logging_example PUBLIC ${LIBS}) 30 | 31 | add_executable(socket_example socket_example.cpp) 32 | target_link_libraries(socket_example PUBLIC ${LIBS}) 33 | -------------------------------------------------------------------------------- /Chapter4/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # TODO - point these to the correct binary locations on your system. 4 | CMAKE=$(which cmake) 5 | NINJA=$(which ninja) 6 | 7 | mkdir -p cmake-build-release 8 | $CMAKE -DCMAKE_BUILD_TYPE=Release -DCMAKE_MAKE_PROGRAM=$NINJA -G Ninja -S . -B cmake-build-release 9 | 10 | $CMAKE --build cmake-build-release --target clean -j 4 11 | $CMAKE --build cmake-build-release --target all -j 4 12 | -------------------------------------------------------------------------------- /Chapter4/lf_queue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "macros.h" 8 | 9 | namespace Common { 10 | template 11 | class LFQueue final { 12 | public: 13 | LFQueue(std::size_t num_elems) : 14 | store_(num_elems, T()) /* pre-allocation of vector storage. */ { 15 | } 16 | 17 | auto getNextToWriteTo() noexcept { 18 | return &store_[next_write_index_]; 19 | } 20 | 21 | auto updateWriteIndex() noexcept { 22 | next_write_index_ = (next_write_index_ + 1) % store_.size(); 23 | num_elements_++; 24 | } 25 | 26 | auto getNextToRead() const noexcept -> const T * { 27 | return (size() ? &store_[next_read_index_] : nullptr); 28 | } 29 | 30 | auto updateReadIndex() noexcept { 31 | next_read_index_ = (next_read_index_ + 1) % store_.size(); 32 | ASSERT(num_elements_ != 0, "Read an invalid element in:" + std::to_string(pthread_self())); 33 | num_elements_--; 34 | } 35 | 36 | auto size() const noexcept { 37 | return num_elements_.load(); 38 | } 39 | 40 | // Deleted default, copy & move constructors and assignment-operators. 41 | LFQueue() = delete; 42 | 43 | LFQueue(const LFQueue &) = delete; 44 | 45 | LFQueue(const LFQueue &&) = delete; 46 | 47 | LFQueue &operator=(const LFQueue &) = delete; 48 | 49 | LFQueue &operator=(const LFQueue &&) = delete; 50 | 51 | private: 52 | std::vector store_; 53 | 54 | std::atomic next_write_index_ = {0}; 55 | std::atomic next_read_index_ = {0}; 56 | 57 | std::atomic num_elements_ = {0}; 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /Chapter4/lf_queue_example.cpp: -------------------------------------------------------------------------------- 1 | #include "thread_utils.h" 2 | #include "lf_queue.h" 3 | 4 | struct MyStruct { 5 | int d_[3]; 6 | }; 7 | 8 | using namespace Common; 9 | 10 | auto consumeFunction(LFQueue* lfq) { 11 | using namespace std::literals::chrono_literals; 12 | std::this_thread::sleep_for(5s); 13 | 14 | while(lfq->size()) { 15 | const auto d = lfq->getNextToRead(); 16 | lfq->updateReadIndex(); 17 | 18 | std::cout << "consumeFunction read elem:" << d->d_[0] << "," << d->d_[1] << "," << d->d_[2] << " lfq-size:" << lfq->size() << std::endl; 19 | 20 | std::this_thread::sleep_for(1s); 21 | } 22 | 23 | std::cout << "consumeFunction exiting." << std::endl; 24 | } 25 | 26 | int main(int, char **) { 27 | LFQueue lfq(20); 28 | 29 | auto ct = createAndStartThread(-1, "", consumeFunction, &lfq); 30 | 31 | for(auto i = 0; i < 50; ++i) { 32 | const MyStruct d{i, i * 10, i * 100}; 33 | *(lfq.getNextToWriteTo()) = d; 34 | lfq.updateWriteIndex(); 35 | 36 | std::cout << "main constructed elem:" << d.d_[0] << "," << d.d_[1] << "," << d.d_[2] << " lfq-size:" << lfq.size() << std::endl; 37 | 38 | using namespace std::literals::chrono_literals; 39 | std::this_thread::sleep_for(1s); 40 | } 41 | 42 | ct->join(); 43 | 44 | std::cout << "main exiting." << std::endl; 45 | 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /Chapter4/logging_example.cpp: -------------------------------------------------------------------------------- 1 | #include "logging.h" 2 | 3 | int main(int, char **) { 4 | using namespace Common; 5 | 6 | char c = 'd'; 7 | int i = 3; 8 | unsigned long ul = 65; 9 | float f = 3.4; 10 | double d = 34.56; 11 | const char* s = "test C-string"; 12 | std::string ss = "test string"; 13 | 14 | Logger logger("logging_example.log"); 15 | 16 | logger.log("Logging a char:% an int:% and an unsigned:%\n", c, i, ul); 17 | logger.log("Logging a float:% and a double:%\n", f, d); 18 | logger.log("Logging a C-string:'%'\n", s); 19 | logger.log("Logging a string:'%'\n", ss); 20 | 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /Chapter4/macros.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #define LIKELY(x) __builtin_expect(!!(x), 1) 7 | #define UNLIKELY(x) __builtin_expect(!!(x), 0) 8 | 9 | inline auto ASSERT(bool cond, const std::string &msg) noexcept { 10 | if (UNLIKELY(!cond)) { 11 | std::cerr << "ASSERT : " << msg << std::endl; 12 | 13 | exit(EXIT_FAILURE); 14 | } 15 | } 16 | 17 | inline auto FATAL(const std::string &msg) noexcept { 18 | std::cerr << "FATAL : " << msg << std::endl; 19 | 20 | exit(EXIT_FAILURE); 21 | } 22 | -------------------------------------------------------------------------------- /Chapter4/mcast_socket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "socket_utils.h" 6 | 7 | #include "logging.h" 8 | 9 | namespace Common { 10 | /// Size of send and receive buffers in bytes. 11 | constexpr size_t McastBufferSize = 64 * 1024 * 1024; 12 | 13 | struct McastSocket { 14 | McastSocket(Logger &logger) 15 | : logger_(logger) { 16 | outbound_data_.resize(McastBufferSize); 17 | inbound_data_.resize(McastBufferSize); 18 | } 19 | 20 | /// Initialize multicast socket to read from or publish to a stream. 21 | /// Does not join the multicast stream yet. 22 | auto init(const std::string &ip, const std::string &iface, int port, bool is_listening) -> int; 23 | 24 | /// Add / Join membership / subscription to a multicast stream. 25 | auto join(const std::string &ip) -> bool; 26 | 27 | /// Remove / Leave membership / subscription to a multicast stream. 28 | auto leave(const std::string &ip, int port) -> void; 29 | 30 | /// Publish outgoing data and read incoming data. 31 | auto sendAndRecv() noexcept -> bool; 32 | 33 | /// Copy data to send buffers - does not send them out yet. 34 | auto send(const void *data, size_t len) noexcept -> void; 35 | 36 | int socket_fd_ = -1; 37 | 38 | /// Send and receive buffers, typically only one or the other is needed, not both. 39 | std::vector outbound_data_; 40 | size_t next_send_valid_index_ = 0; 41 | std::vector inbound_data_; 42 | size_t next_rcv_valid_index_ = 0; 43 | 44 | /// Function wrapper for the method to call when data is read. 45 | std::function recv_callback_ = nullptr; 46 | 47 | std::string time_str_; 48 | Logger &logger_; 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /Chapter4/mem_pool_example.cpp: -------------------------------------------------------------------------------- 1 | #include "mem_pool.h" 2 | 3 | struct MyStruct { 4 | int d_[3]; 5 | }; 6 | 7 | int main(int, char **) { 8 | using namespace Common; 9 | 10 | MemPool prim_pool(50); 11 | MemPool struct_pool(50); 12 | 13 | for(auto i = 0; i < 50; ++i) { 14 | auto p_ret = prim_pool.allocate(i); 15 | auto s_ret = struct_pool.allocate(MyStruct{i, i+1, i+2}); 16 | 17 | std::cout << "prim elem:" << *p_ret << " allocated at:" << p_ret << std::endl; 18 | std::cout << "struct elem:" << s_ret->d_[0] << "," << s_ret->d_[1] << "," << s_ret->d_[2] << " allocated at:" << s_ret << std::endl; 19 | 20 | if(i % 5 == 0) { 21 | std::cout << "deallocating prim elem:" << *p_ret << " from:" << p_ret << std::endl; 22 | std::cout << "deallocating struct elem:" << s_ret->d_[0] << "," << s_ret->d_[1] << "," << s_ret->d_[2] << " from:" << s_ret << std::endl; 23 | 24 | prim_pool.deallocate(p_ret); 25 | struct_pool.deallocate(s_ret); 26 | } 27 | } 28 | 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /Chapter4/run_examples.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for f in $(ls cmake-build*/*_example); do 4 | echo "Running "$f"..."; 5 | ./$f 6 | done 7 | -------------------------------------------------------------------------------- /Chapter4/tcp_server.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "tcp_socket.h" 4 | 5 | namespace Common { 6 | struct TCPServer { 7 | explicit TCPServer(Logger &logger) 8 | : listener_socket_(logger), logger_(logger) { 9 | } 10 | 11 | /// Start listening for connections on the provided interface and port. 12 | auto listen(const std::string &iface, int port) -> void; 13 | 14 | /// Check for new connections or dead connections and update containers that track the sockets. 15 | auto poll() noexcept -> void; 16 | 17 | /// Publish outgoing data from the send buffer and read incoming data from the receive buffer. 18 | auto sendAndRecv() noexcept -> void; 19 | 20 | private: 21 | /// Add and remove socket file descriptors to and from the EPOLL list. 22 | auto addToEpollList(TCPSocket *socket); 23 | 24 | public: 25 | /// Socket on which this server is listening for new connections on. 26 | int epoll_fd_ = -1; 27 | TCPSocket listener_socket_; 28 | 29 | epoll_event events_[1024]; 30 | 31 | /// Collection of all sockets, sockets for incoming data, sockets for outgoing data and dead connections. 32 | std::vector receive_sockets_, send_sockets_; 33 | 34 | /// Function wrapper to call back when data is available. 35 | std::function recv_callback_ = nullptr; 36 | /// Function wrapper to call back when all data across all TCPSockets has been read and dispatched this round. 37 | std::function recv_finished_callback_ = nullptr; 38 | 39 | std::string time_str_; 40 | Logger &logger_; 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /Chapter4/tcp_socket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "socket_utils.h" 6 | #include "logging.h" 7 | 8 | namespace Common { 9 | /// Size of our send and receive buffers in bytes. 10 | constexpr size_t TCPBufferSize = 64 * 1024 * 1024; 11 | 12 | struct TCPSocket { 13 | explicit TCPSocket(Logger &logger) 14 | : logger_(logger) { 15 | outbound_data_.resize(TCPBufferSize); 16 | inbound_data_.resize(TCPBufferSize); 17 | } 18 | 19 | /// Create TCPSocket with provided attributes to either listen-on / connect-to. 20 | auto connect(const std::string &ip, const std::string &iface, int port, bool is_listening) -> int; 21 | 22 | /// Called to publish outgoing data from the buffers as well as check for and callback if data is available in the read buffers. 23 | auto sendAndRecv() noexcept -> bool; 24 | 25 | /// Write outgoing data to the send buffers. 26 | auto send(const void *data, size_t len) noexcept -> void; 27 | 28 | /// Deleted default, copy & move constructors and assignment-operators. 29 | TCPSocket() = delete; 30 | 31 | TCPSocket(const TCPSocket &) = delete; 32 | 33 | TCPSocket(const TCPSocket &&) = delete; 34 | 35 | TCPSocket &operator=(const TCPSocket &) = delete; 36 | 37 | TCPSocket &operator=(const TCPSocket &&) = delete; 38 | 39 | /// File descriptor for the socket. 40 | int socket_fd_ = -1; 41 | 42 | /// Send and receive buffers and trackers for read/write indices. 43 | std::vector outbound_data_; 44 | size_t next_send_valid_index_ = 0; 45 | std::vector inbound_data_; 46 | size_t next_rcv_valid_index_ = 0; 47 | 48 | /// Socket attributes. 49 | struct sockaddr_in socket_attrib_{}; 50 | 51 | /// Function wrapper to callback when there is data to be processed. 52 | std::function recv_callback_ = nullptr; 53 | 54 | std::string time_str_; 55 | Logger &logger_; 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /Chapter4/thread_example.cpp: -------------------------------------------------------------------------------- 1 | #include "thread_utils.h" 2 | 3 | auto dummyFunction(int a, int b, bool sleep) { 4 | std::cout << "dummyFunction(" << a << "," << b << ")" << std::endl; 5 | std::cout << "dummyFunction output=" << a + b << std::endl; 6 | 7 | if(sleep) { 8 | std::cout << "dummyFunction sleeping..." << std::endl; 9 | 10 | using namespace std::literals::chrono_literals; 11 | std::this_thread::sleep_for(5s); 12 | } 13 | 14 | std::cout << "dummyFunction done." << std::endl; 15 | } 16 | 17 | int main(int, char **) { 18 | using namespace Common; 19 | 20 | auto t1 = createAndStartThread(-1, "dummyFunction1", dummyFunction, 12, 21, false); 21 | auto t2 = createAndStartThread(1, "dummyFunction2", dummyFunction, 15, 51, true); 22 | 23 | std::cout << "main waiting for threads to be done." << std::endl; 24 | t1->join(); 25 | t2->join(); 26 | std::cout << "main exiting." << std::endl; 27 | 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /Chapter4/thread_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace Common { 11 | /// Set affinity for current thread to be pinned to the provided core_id. 12 | inline auto setThreadCore(int core_id) noexcept { 13 | cpu_set_t cpuset; 14 | 15 | CPU_ZERO(&cpuset); 16 | CPU_SET(core_id, &cpuset); 17 | 18 | return (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) == 0); 19 | } 20 | 21 | /// Creates a thread instance, sets affinity on it, assigns it a name and 22 | /// passes the function to be run on that thread as well as the arguments to the function. 23 | template 24 | inline auto createAndStartThread(int core_id, const std::string &name, T &&func, A &&... args) noexcept { 25 | auto t = new std::thread([&]() { 26 | if (core_id >= 0 && !setThreadCore(core_id)) { 27 | std::cerr << "Failed to set core affinity for " << name << " " << pthread_self() << " to " << core_id << std::endl; 28 | exit(EXIT_FAILURE); 29 | } 30 | std::cerr << "Set core affinity for " << name << " " << pthread_self() << " to " << core_id << std::endl; 31 | 32 | std::forward(func)((std::forward(args))...); 33 | }); 34 | 35 | using namespace std::literals::chrono_literals; 36 | std::this_thread::sleep_for(1s); 37 | 38 | return t; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Chapter4/time_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Common { 8 | typedef int64_t Nanos; 9 | 10 | constexpr Nanos NANOS_TO_MICROS = 1000; 11 | constexpr Nanos MICROS_TO_MILLIS = 1000; 12 | constexpr Nanos MILLIS_TO_SECS = 1000; 13 | constexpr Nanos NANOS_TO_MILLIS = NANOS_TO_MICROS * MICROS_TO_MILLIS; 14 | constexpr Nanos NANOS_TO_SECS = NANOS_TO_MILLIS * MILLIS_TO_SECS; 15 | 16 | inline auto getCurrentNanos() noexcept { 17 | return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); 18 | } 19 | 20 | inline auto& getCurrentTimeStr(std::string* time_str) { 21 | const auto time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); 22 | time_str->assign(ctime(&time)); 23 | if(!time_str->empty()) 24 | time_str->at(time_str->length()-1) = '\0'; 25 | return *time_str; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Chapter6/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(LowLatencyApp) 4 | 5 | set(CMAKE_CXX_STANDARD 20) 6 | set(CMAKE_CXX_COMPILER g++) 7 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 8 | set(CMAKE_VERBOSE_MAKEFILE on) 9 | 10 | add_subdirectory(common) 11 | add_subdirectory(exchange) 12 | 13 | list(APPEND LIBS libcommon) 14 | list(APPEND LIBS libexchange) 15 | list(APPEND LIBS pthread) 16 | 17 | include_directories(${PROJECT_SOURCE_DIR}) 18 | include_directories(${PROJECT_SOURCE_DIR}/exchange) 19 | 20 | add_executable(exchange_main exchange/exchange_main.cpp) 21 | target_link_libraries(exchange_main PUBLIC ${LIBS}) 22 | 23 | -------------------------------------------------------------------------------- /Chapter6/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # TODO - point these to the correct binary locations on your system. 4 | CMAKE=$(which cmake) 5 | NINJA=$(which ninja) 6 | 7 | mkdir -p cmake-build-release 8 | $CMAKE -DCMAKE_BUILD_TYPE=Release -DCMAKE_MAKE_PROGRAM=$NINJA -G Ninja -S . -B cmake-build-release 9 | 10 | $CMAKE --build cmake-build-release --target clean -j 4 11 | $CMAKE --build cmake-build-release --target all -j 4 12 | -------------------------------------------------------------------------------- /Chapter6/common/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | set(CMAKE_CXX_COMPILER g++) 3 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 4 | set(CMAKE_VERBOSE_MAKEFILE on) 5 | 6 | file(GLOB SOURCES "*.cpp") 7 | 8 | include_directories(${PROJECT_SOURCE_DIR}) 9 | 10 | add_library(libcommon STATIC ${SOURCES}) 11 | -------------------------------------------------------------------------------- /Chapter6/common/lf_queue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "macros.h" 8 | 9 | namespace Common { 10 | template 11 | class LFQueue final { 12 | public: 13 | LFQueue(std::size_t num_elems) : 14 | store_(num_elems, T()) /* pre-allocation of vector storage. */ { 15 | } 16 | 17 | auto getNextToWriteTo() noexcept { 18 | return &store_[next_write_index_]; 19 | } 20 | 21 | auto updateWriteIndex() noexcept { 22 | next_write_index_ = (next_write_index_ + 1) % store_.size(); 23 | num_elements_++; 24 | } 25 | 26 | auto getNextToRead() const noexcept -> const T * { 27 | return (size() ? &store_[next_read_index_] : nullptr); 28 | } 29 | 30 | auto updateReadIndex() noexcept { 31 | next_read_index_ = (next_read_index_ + 1) % store_.size(); 32 | ASSERT(num_elements_ != 0, "Read an invalid element in:" + std::to_string(pthread_self())); 33 | num_elements_--; 34 | } 35 | 36 | auto size() const noexcept { 37 | return num_elements_.load(); 38 | } 39 | 40 | // Deleted default, copy & move constructors and assignment-operators. 41 | LFQueue() = delete; 42 | 43 | LFQueue(const LFQueue &) = delete; 44 | 45 | LFQueue(const LFQueue &&) = delete; 46 | 47 | LFQueue &operator=(const LFQueue &) = delete; 48 | 49 | LFQueue &operator=(const LFQueue &&) = delete; 50 | 51 | private: 52 | std::vector store_; 53 | 54 | std::atomic next_write_index_ = {0}; 55 | std::atomic next_read_index_ = {0}; 56 | 57 | std::atomic num_elements_ = {0}; 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /Chapter6/common/logging.cpp: -------------------------------------------------------------------------------- 1 | #include "common/logging.h" 2 | 3 | namespace Common { 4 | } -------------------------------------------------------------------------------- /Chapter6/common/macros.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #define LIKELY(x) __builtin_expect(!!(x), 1) 7 | #define UNLIKELY(x) __builtin_expect(!!(x), 0) 8 | 9 | inline auto ASSERT(bool cond, const std::string &msg) noexcept { 10 | if (UNLIKELY(!cond)) { 11 | std::cerr << "ASSERT : " << msg << std::endl; 12 | 13 | exit(EXIT_FAILURE); 14 | } 15 | } 16 | 17 | inline auto FATAL(const std::string &msg) noexcept { 18 | std::cerr << "FATAL : " << msg << std::endl; 19 | 20 | exit(EXIT_FAILURE); 21 | } 22 | -------------------------------------------------------------------------------- /Chapter6/common/thread_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace Common { 11 | /// Set affinity for current thread to be pinned to the provided core_id. 12 | inline auto setThreadCore(int core_id) noexcept { 13 | cpu_set_t cpuset; 14 | 15 | CPU_ZERO(&cpuset); 16 | CPU_SET(core_id, &cpuset); 17 | 18 | return (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) == 0); 19 | } 20 | 21 | /// Creates a thread instance, sets affinity on it, assigns it a name and 22 | /// passes the function to be run on that thread as well as the arguments to the function. 23 | template 24 | inline auto createAndStartThread(int core_id, const std::string &name, T &&func, A &&... args) noexcept { 25 | auto t = new std::thread([&]() { 26 | if (core_id >= 0 && !setThreadCore(core_id)) { 27 | std::cerr << "Failed to set core affinity for " << name << " " << pthread_self() << " to " << core_id << std::endl; 28 | exit(EXIT_FAILURE); 29 | } 30 | std::cerr << "Set core affinity for " << name << " " << pthread_self() << " to " << core_id << std::endl; 31 | 32 | std::forward(func)((std::forward(args))...); 33 | }); 34 | 35 | using namespace std::literals::chrono_literals; 36 | std::this_thread::sleep_for(1s); 37 | 38 | return t; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Chapter6/common/time_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace Common { 7 | typedef int64_t Nanos; 8 | 9 | constexpr Nanos NANO_TO_MICROS = 1000; 10 | constexpr Nanos MICROS_TO_MILLIS = 1000; 11 | constexpr Nanos MILLIS_TO_SECS = 1000; 12 | constexpr Nanos NANOS_TO_MILLIS = NANO_TO_MICROS * MICROS_TO_MILLIS; 13 | constexpr Nanos NANOS_TO_SECS = NANOS_TO_MILLIS * MILLIS_TO_SECS; 14 | 15 | inline auto getCurrentNanos() noexcept { 16 | return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); 17 | } 18 | 19 | inline auto& getCurrentTimeStr(std::string* time_str) { 20 | const auto time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); 21 | time_str->assign(ctime(&time)); 22 | if(!time_str->empty()) 23 | time_str->at(time_str->length()-1) = '\0'; 24 | return *time_str; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Chapter6/exchange/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | set(CMAKE_CXX_COMPILER g++) 3 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 4 | set(CMAKE_VERBOSE_MAKEFILE on) 5 | 6 | file(GLOB SOURCES "*/*.cpp") 7 | 8 | include_directories(${PROJECT_SOURCE_DIR}) 9 | include_directories(${PROJECT_SOURCE_DIR}/exchange) 10 | 11 | add_library(libexchange STATIC ${SOURCES}) 12 | -------------------------------------------------------------------------------- /Chapter6/exchange/exchange_main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "matcher/matching_engine.h" 4 | 5 | Common::Logger* logger = nullptr; 6 | Exchange::MatchingEngine* matching_engine = nullptr; 7 | 8 | void signal_handler(int) { 9 | using namespace std::literals::chrono_literals; 10 | std::this_thread::sleep_for(10s); 11 | 12 | delete logger; logger = nullptr; 13 | delete matching_engine; matching_engine = nullptr; 14 | 15 | std::this_thread::sleep_for(10s); 16 | 17 | exit(EXIT_SUCCESS); 18 | } 19 | 20 | int main(int, char **) { 21 | logger = new Common::Logger("exchange_main.log"); 22 | 23 | std::signal(SIGINT, signal_handler); 24 | 25 | const int sleep_time = 100 * 1000; 26 | 27 | Exchange::ClientRequestLFQueue client_requests(ME_MAX_CLIENT_UPDATES); 28 | Exchange::ClientResponseLFQueue client_responses(ME_MAX_CLIENT_UPDATES); 29 | Exchange::MEMarketUpdateLFQueue market_updates(ME_MAX_MARKET_UPDATES); 30 | 31 | std::string time_str; 32 | 33 | logger->log("%:% %() % Starting Matching Engine...\n", __FILE__, __LINE__, __FUNCTION__, Common::getCurrentTimeStr(&time_str)); 34 | matching_engine = new Exchange::MatchingEngine(&client_requests, &client_responses, &market_updates); 35 | matching_engine->start(); 36 | 37 | while (true) { 38 | logger->log("%:% %() % Sleeping for a few milliseconds..\n", __FILE__, __LINE__, __FUNCTION__, Common::getCurrentTimeStr(&time_str)); 39 | usleep(sleep_time * 1000); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Chapter6/exchange/market_data/market_update.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "common/types.h" 6 | 7 | using namespace Common; 8 | 9 | namespace Exchange { 10 | #pragma pack(push, 1) 11 | enum class MarketUpdateType : uint8_t { 12 | INVALID = 0, 13 | ADD = 1, 14 | MODIFY = 2, 15 | CANCEL = 3, 16 | TRADE = 4 17 | }; 18 | 19 | inline std::string marketUpdateTypeToString(MarketUpdateType type) { 20 | switch (type) { 21 | case MarketUpdateType::ADD: 22 | return "ADD"; 23 | case MarketUpdateType::MODIFY: 24 | return "MODIFY"; 25 | case MarketUpdateType::CANCEL: 26 | return "CANCEL"; 27 | case MarketUpdateType::TRADE: 28 | return "TRADE"; 29 | case MarketUpdateType::INVALID: 30 | return "INVALID"; 31 | } 32 | return "UNKNOWN"; 33 | } 34 | 35 | struct MEMarketUpdate { 36 | MarketUpdateType type_ = MarketUpdateType::INVALID; 37 | 38 | OrderId order_id_ = OrderId_INVALID; 39 | TickerId ticker_id_ = TickerId_INVALID; 40 | Side side_ = Side::INVALID; 41 | Price price_ = Price_INVALID; 42 | Qty qty_ = Qty_INVALID; 43 | Priority priority_ = Priority_INVALID; 44 | 45 | auto toString() const { 46 | std::stringstream ss; 47 | ss << "MEMarketUpdate" 48 | << " [" 49 | << " type:" << marketUpdateTypeToString(type_) 50 | << " ticker:" << tickerIdToString(ticker_id_) 51 | << " oid:" << orderIdToString(order_id_) 52 | << " side:" << sideToString(side_) 53 | << " qty:" << qtyToString(qty_) 54 | << " price:" << priceToString(price_) 55 | << " priority:" << priorityToString(priority_) 56 | << "]"; 57 | return ss.str(); 58 | } 59 | }; 60 | 61 | #pragma pack(pop) 62 | 63 | typedef Common::LFQueue MEMarketUpdateLFQueue; 64 | } -------------------------------------------------------------------------------- /Chapter6/exchange/matcher/matching_engine.cpp: -------------------------------------------------------------------------------- 1 | #include "matching_engine.h" 2 | 3 | namespace Exchange { 4 | MatchingEngine::MatchingEngine(ClientRequestLFQueue *client_requests, ClientResponseLFQueue *client_responses, 5 | MEMarketUpdateLFQueue *market_updates) 6 | : incoming_requests_(client_requests), outgoing_ogw_responses_(client_responses), outgoing_md_updates_(market_updates), 7 | logger_("exchange_matching_engine.log") { 8 | for(size_t i = 0; i < ticker_order_book_.size(); ++i) { 9 | ticker_order_book_[i] = new MEOrderBook(i, &logger_, this); 10 | } 11 | } 12 | 13 | MatchingEngine::~MatchingEngine() { 14 | run_ = false; 15 | 16 | using namespace std::literals::chrono_literals; 17 | std::this_thread::sleep_for(1s); 18 | 19 | incoming_requests_ = nullptr; 20 | outgoing_ogw_responses_ = nullptr; 21 | outgoing_md_updates_ = nullptr; 22 | 23 | for(auto& order_book : ticker_order_book_) { 24 | delete order_book; 25 | order_book = nullptr; 26 | } 27 | } 28 | 29 | auto MatchingEngine::start() -> void { 30 | run_ = true; 31 | ASSERT(Common::createAndStartThread(-1, "Exchange/MatchingEngine", [this]() { run(); }) != nullptr, "Failed to start MatchingEngine thread."); 32 | } 33 | 34 | auto MatchingEngine::stop() -> void { 35 | run_ = false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Chapter6/exchange/matcher/me_order.cpp: -------------------------------------------------------------------------------- 1 | #include "me_order.h" 2 | 3 | namespace Exchange { 4 | auto MEOrder::toString() const -> std::string { 5 | std::stringstream ss; 6 | ss << "MEOrder" << "[" 7 | << "ticker:" << tickerIdToString(ticker_id_) << " " 8 | << "cid:" << clientIdToString(client_id_) << " " 9 | << "oid:" << orderIdToString(client_order_id_) << " " 10 | << "moid:" << orderIdToString(market_order_id_) << " " 11 | << "side:" << sideToString(side_) << " " 12 | << "price:" << priceToString(price_) << " " 13 | << "qty:" << qtyToString(qty_) << " " 14 | << "prio:" << priorityToString(priority_) << " " 15 | << "prev:" << orderIdToString(prev_order_ ? prev_order_->market_order_id_ : OrderId_INVALID) << " " 16 | << "next:" << orderIdToString(next_order_ ? next_order_->market_order_id_ : OrderId_INVALID) << "]"; 17 | 18 | return ss.str(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter6/exchange/order_server/client_request.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "common/types.h" 6 | #include "common/lf_queue.h" 7 | 8 | using namespace Common; 9 | 10 | namespace Exchange { 11 | #pragma pack(push, 1) 12 | enum class ClientRequestType : uint8_t { 13 | INVALID = 0, 14 | NEW = 1, 15 | CANCEL = 2 16 | }; 17 | 18 | inline std::string clientRequestTypeToString(ClientRequestType type) { 19 | switch (type) { 20 | case ClientRequestType::NEW: 21 | return "NEW"; 22 | case ClientRequestType::CANCEL: 23 | return "CANCEL"; 24 | case ClientRequestType::INVALID: 25 | return "INVALID"; 26 | } 27 | return "UNKNOWN"; 28 | } 29 | 30 | struct MEClientRequest { 31 | ClientRequestType type_ = ClientRequestType::INVALID; 32 | 33 | ClientId client_id_ = ClientId_INVALID; 34 | TickerId ticker_id_ = TickerId_INVALID; 35 | OrderId order_id_ = OrderId_INVALID; 36 | Side side_ = Side::INVALID; 37 | Price price_ = Price_INVALID; 38 | Qty qty_ = Qty_INVALID; 39 | 40 | auto toString() const { 41 | std::stringstream ss; 42 | ss << "MEClientRequest" 43 | << " [" 44 | << "type:" << clientRequestTypeToString(type_) 45 | << " client:" << clientIdToString(client_id_) 46 | << " ticker:" << tickerIdToString(ticker_id_) 47 | << " oid:" << orderIdToString(order_id_) 48 | << " side:" << sideToString(side_) 49 | << " qty:" << qtyToString(qty_) 50 | << " price:" << priceToString(price_) 51 | << "]"; 52 | return ss.str(); 53 | } 54 | }; 55 | 56 | #pragma pack(pop) 57 | 58 | typedef LFQueue ClientRequestLFQueue; 59 | } 60 | -------------------------------------------------------------------------------- /Chapter7/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(LowLatencyApp) 4 | 5 | set(CMAKE_CXX_STANDARD 20) 6 | set(CMAKE_CXX_COMPILER g++) 7 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 8 | set(CMAKE_VERBOSE_MAKEFILE on) 9 | 10 | add_subdirectory(common) 11 | add_subdirectory(exchange) 12 | 13 | list(APPEND LIBS libexchange) 14 | list(APPEND LIBS libcommon) 15 | list(APPEND LIBS pthread) 16 | 17 | include_directories(${PROJECT_SOURCE_DIR}) 18 | include_directories(${PROJECT_SOURCE_DIR}/exchange) 19 | 20 | add_executable(exchange_main exchange/exchange_main.cpp) 21 | target_link_libraries(exchange_main PUBLIC ${LIBS}) 22 | 23 | -------------------------------------------------------------------------------- /Chapter7/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # TODO - point these to the correct binary locations on your system. 4 | CMAKE=$(which cmake) 5 | NINJA=$(which ninja) 6 | 7 | mkdir -p cmake-build-release 8 | $CMAKE -DCMAKE_BUILD_TYPE=Release -DCMAKE_MAKE_PROGRAM=$NINJA -G Ninja -S . -B cmake-build-release 9 | 10 | $CMAKE --build cmake-build-release --target clean -j 4 11 | $CMAKE --build cmake-build-release --target all -j 4 12 | -------------------------------------------------------------------------------- /Chapter7/common/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | set(CMAKE_CXX_COMPILER g++) 3 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 4 | set(CMAKE_VERBOSE_MAKEFILE on) 5 | 6 | file(GLOB SOURCES "*.cpp") 7 | 8 | include_directories(${PROJECT_SOURCE_DIR}) 9 | 10 | add_library(libcommon STATIC ${SOURCES}) 11 | 12 | list(APPEND LIBS libcommon) 13 | list(APPEND LIBS pthread) 14 | 15 | add_executable(thread_example thread_example.cpp) 16 | target_link_libraries(thread_example PUBLIC ${LIBS}) 17 | 18 | add_executable(mem_pool_example mem_pool_example.cpp) 19 | target_link_libraries(mem_pool_example PUBLIC ${LIBS}) 20 | 21 | add_executable(lf_queue_example lf_queue_example.cpp) 22 | target_link_libraries(lf_queue_example PUBLIC ${LIBS}) 23 | 24 | add_executable(logging_example logging_example.cpp) 25 | target_link_libraries(logging_example PUBLIC ${LIBS}) 26 | 27 | add_executable(socket_example socket_example.cpp) 28 | target_link_libraries(socket_example PUBLIC ${LIBS}) 29 | -------------------------------------------------------------------------------- /Chapter7/common/lf_queue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "macros.h" 8 | 9 | namespace Common { 10 | template 11 | class LFQueue final { 12 | public: 13 | LFQueue(std::size_t num_elems) : 14 | store_(num_elems, T()) /* pre-allocation of vector storage. */ { 15 | } 16 | 17 | auto getNextToWriteTo() noexcept { 18 | return &store_[next_write_index_]; 19 | } 20 | 21 | auto updateWriteIndex() noexcept { 22 | next_write_index_ = (next_write_index_ + 1) % store_.size(); 23 | num_elements_++; 24 | } 25 | 26 | auto getNextToRead() const noexcept -> const T * { 27 | return (size() ? &store_[next_read_index_] : nullptr); 28 | } 29 | 30 | auto updateReadIndex() noexcept { 31 | next_read_index_ = (next_read_index_ + 1) % store_.size(); 32 | ASSERT(num_elements_ != 0, "Read an invalid element in:" + std::to_string(pthread_self())); 33 | num_elements_--; 34 | } 35 | 36 | auto size() const noexcept { 37 | return num_elements_.load(); 38 | } 39 | 40 | // Deleted default, copy & move constructors and assignment-operators. 41 | LFQueue() = delete; 42 | 43 | LFQueue(const LFQueue &) = delete; 44 | 45 | LFQueue(const LFQueue &&) = delete; 46 | 47 | LFQueue &operator=(const LFQueue &) = delete; 48 | 49 | LFQueue &operator=(const LFQueue &&) = delete; 50 | 51 | private: 52 | std::vector store_; 53 | 54 | std::atomic next_write_index_ = {0}; 55 | std::atomic next_read_index_ = {0}; 56 | 57 | std::atomic num_elements_ = {0}; 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /Chapter7/common/lf_queue_example.cpp: -------------------------------------------------------------------------------- 1 | #include "thread_utils.h" 2 | #include "lf_queue.h" 3 | 4 | struct MyStruct { 5 | int d_[3]; 6 | }; 7 | 8 | using namespace Common; 9 | 10 | auto consumeFunction(LFQueue* lfq) { 11 | using namespace std::literals::chrono_literals; 12 | std::this_thread::sleep_for(5s); 13 | 14 | while(lfq->size()) { 15 | const auto d = lfq->getNextToRead(); 16 | lfq->updateReadIndex(); 17 | 18 | std::cout << "consumeFunction read elem:" << d->d_[0] << "," << d->d_[1] << "," << d->d_[2] << " lfq-size:" << lfq->size() << std::endl; 19 | 20 | std::this_thread::sleep_for(1s); 21 | } 22 | 23 | std::cout << "consumeFunction exiting." << std::endl; 24 | } 25 | 26 | int main(int, char **) { 27 | LFQueue lfq(20); 28 | 29 | auto ct = createAndStartThread(-1, "", consumeFunction, &lfq); 30 | 31 | for(auto i = 0; i < 50; ++i) { 32 | const MyStruct d{i, i * 10, i * 100}; 33 | *(lfq.getNextToWriteTo()) = d; 34 | lfq.updateWriteIndex(); 35 | 36 | std::cout << "main constructed elem:" << d.d_[0] << "," << d.d_[1] << "," << d.d_[2] << " lfq-size:" << lfq.size() << std::endl; 37 | 38 | using namespace std::literals::chrono_literals; 39 | std::this_thread::sleep_for(1s); 40 | } 41 | 42 | ct->join(); 43 | 44 | std::cout << "main exiting." << std::endl; 45 | 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /Chapter7/common/logging_example.cpp: -------------------------------------------------------------------------------- 1 | #include "logging.h" 2 | 3 | int main(int, char **) { 4 | using namespace Common; 5 | 6 | char c = 'd'; 7 | int i = 3; 8 | unsigned long ul = 65; 9 | float f = 3.4; 10 | double d = 34.56; 11 | const char* s = "test C-string"; 12 | std::string ss = "test string"; 13 | 14 | Logger logger("logging_example.log"); 15 | 16 | logger.log("Logging a char:% an int:% and an unsigned:%\n", c, i, ul); 17 | logger.log("Logging a float:% and a double:%\n", f, d); 18 | logger.log("Logging a C-string:'%'\n", s); 19 | logger.log("Logging a string:'%'\n", ss); 20 | 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /Chapter7/common/macros.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #define LIKELY(x) __builtin_expect(!!(x), 1) 7 | #define UNLIKELY(x) __builtin_expect(!!(x), 0) 8 | 9 | inline auto ASSERT(bool cond, const std::string &msg) noexcept { 10 | if (UNLIKELY(!cond)) { 11 | std::cerr << "ASSERT : " << msg << std::endl; 12 | 13 | exit(EXIT_FAILURE); 14 | } 15 | } 16 | 17 | inline auto FATAL(const std::string &msg) noexcept { 18 | std::cerr << "FATAL : " << msg << std::endl; 19 | 20 | exit(EXIT_FAILURE); 21 | } 22 | -------------------------------------------------------------------------------- /Chapter7/common/mcast_socket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "socket_utils.h" 6 | 7 | #include "logging.h" 8 | 9 | namespace Common { 10 | /// Size of send and receive buffers in bytes. 11 | constexpr size_t McastBufferSize = 64 * 1024 * 1024; 12 | 13 | struct McastSocket { 14 | McastSocket(Logger &logger) 15 | : logger_(logger) { 16 | outbound_data_.resize(McastBufferSize); 17 | inbound_data_.resize(McastBufferSize); 18 | } 19 | 20 | /// Initialize multicast socket to read from or publish to a stream. 21 | /// Does not join the multicast stream yet. 22 | auto init(const std::string &ip, const std::string &iface, int port, bool is_listening) -> int; 23 | 24 | /// Add / Join membership / subscription to a multicast stream. 25 | auto join(const std::string &ip) -> bool; 26 | 27 | /// Remove / Leave membership / subscription to a multicast stream. 28 | auto leave(const std::string &ip, int port) -> void; 29 | 30 | /// Publish outgoing data and read incoming data. 31 | auto sendAndRecv() noexcept -> bool; 32 | 33 | /// Copy data to send buffers - does not send them out yet. 34 | auto send(const void *data, size_t len) noexcept -> void; 35 | 36 | int socket_fd_ = -1; 37 | 38 | /// Send and receive buffers, typically only one or the other is needed, not both. 39 | std::vector outbound_data_; 40 | size_t next_send_valid_index_ = 0; 41 | std::vector inbound_data_; 42 | size_t next_rcv_valid_index_ = 0; 43 | 44 | /// Function wrapper for the method to call when data is read. 45 | std::function recv_callback_ = nullptr; 46 | 47 | std::string time_str_; 48 | Logger &logger_; 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /Chapter7/common/mem_pool_example.cpp: -------------------------------------------------------------------------------- 1 | #include "mem_pool.h" 2 | 3 | struct MyStruct { 4 | int d_[3]; 5 | }; 6 | 7 | int main(int, char **) { 8 | using namespace Common; 9 | 10 | MemPool prim_pool(50); 11 | MemPool struct_pool(50); 12 | 13 | for(auto i = 0; i < 50; ++i) { 14 | auto p_ret = prim_pool.allocate(i); 15 | auto s_ret = struct_pool.allocate(MyStruct{i, i+1, i+2}); 16 | 17 | std::cout << "prim elem:" << *p_ret << " allocated at:" << p_ret << std::endl; 18 | std::cout << "struct elem:" << s_ret->d_[0] << "," << s_ret->d_[1] << "," << s_ret->d_[2] << " allocated at:" << s_ret << std::endl; 19 | 20 | if(i % 5 == 0) { 21 | std::cout << "deallocating prim elem:" << *p_ret << " from:" << p_ret << std::endl; 22 | std::cout << "deallocating struct elem:" << s_ret->d_[0] << "," << s_ret->d_[1] << "," << s_ret->d_[2] << " from:" << s_ret << std::endl; 23 | 24 | prim_pool.deallocate(p_ret); 25 | struct_pool.deallocate(s_ret); 26 | } 27 | } 28 | 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /Chapter7/common/tcp_server.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "tcp_socket.h" 4 | 5 | namespace Common { 6 | struct TCPServer { 7 | explicit TCPServer(Logger &logger) 8 | : listener_socket_(logger), logger_(logger) { 9 | } 10 | 11 | /// Start listening for connections on the provided interface and port. 12 | auto listen(const std::string &iface, int port) -> void; 13 | 14 | /// Check for new connections or dead connections and update containers that track the sockets. 15 | auto poll() noexcept -> void; 16 | 17 | /// Publish outgoing data from the send buffer and read incoming data from the receive buffer. 18 | auto sendAndRecv() noexcept -> void; 19 | 20 | private: 21 | /// Add and remove socket file descriptors to and from the EPOLL list. 22 | auto addToEpollList(TCPSocket *socket); 23 | 24 | public: 25 | /// Socket on which this server is listening for new connections on. 26 | int epoll_fd_ = -1; 27 | TCPSocket listener_socket_; 28 | 29 | epoll_event events_[1024]; 30 | 31 | /// Collection of all sockets, sockets for incoming data, sockets for outgoing data and dead connections. 32 | std::vector receive_sockets_, send_sockets_; 33 | 34 | /// Function wrapper to call back when data is available. 35 | std::function recv_callback_ = nullptr; 36 | /// Function wrapper to call back when all data across all TCPSockets has been read and dispatched this round. 37 | std::function recv_finished_callback_ = nullptr; 38 | 39 | std::string time_str_; 40 | Logger &logger_; 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /Chapter7/common/thread_example.cpp: -------------------------------------------------------------------------------- 1 | #include "thread_utils.h" 2 | 3 | auto dummyFunction(int a, int b, bool sleep) { 4 | std::cout << "dummyFunction(" << a << "," << b << ")" << std::endl; 5 | std::cout << "dummyFunction output=" << a + b << std::endl; 6 | 7 | if(sleep) { 8 | std::cout << "dummyFunction sleeping..." << std::endl; 9 | 10 | using namespace std::literals::chrono_literals; 11 | std::this_thread::sleep_for(5s); 12 | } 13 | 14 | std::cout << "dummyFunction done." << std::endl; 15 | } 16 | 17 | int main(int, char **) { 18 | using namespace Common; 19 | 20 | auto t1 = createAndStartThread(-1, "dummyFunction1", dummyFunction, 12, 21, false); 21 | auto t2 = createAndStartThread(1, "dummyFunction2", dummyFunction, 15, 51, true); 22 | 23 | std::cout << "main waiting for threads to be done." << std::endl; 24 | t1->join(); 25 | t2->join(); 26 | std::cout << "main exiting." << std::endl; 27 | 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /Chapter7/common/thread_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace Common { 11 | /// Set affinity for current thread to be pinned to the provided core_id. 12 | inline auto setThreadCore(int core_id) noexcept { 13 | cpu_set_t cpuset; 14 | 15 | CPU_ZERO(&cpuset); 16 | CPU_SET(core_id, &cpuset); 17 | 18 | return (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) == 0); 19 | } 20 | 21 | /// Creates a thread instance, sets affinity on it, assigns it a name and 22 | /// passes the function to be run on that thread as well as the arguments to the function. 23 | template 24 | inline auto createAndStartThread(int core_id, const std::string &name, T &&func, A &&... args) noexcept { 25 | auto t = new std::thread([&]() { 26 | if (core_id >= 0 && !setThreadCore(core_id)) { 27 | std::cerr << "Failed to set core affinity for " << name << " " << pthread_self() << " to " << core_id << std::endl; 28 | exit(EXIT_FAILURE); 29 | } 30 | std::cerr << "Set core affinity for " << name << " " << pthread_self() << " to " << core_id << std::endl; 31 | 32 | std::forward(func)((std::forward(args))...); 33 | }); 34 | 35 | using namespace std::literals::chrono_literals; 36 | std::this_thread::sleep_for(1s); 37 | 38 | return t; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Chapter7/common/time_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Common { 8 | typedef int64_t Nanos; 9 | 10 | constexpr Nanos NANOS_TO_MICROS = 1000; 11 | constexpr Nanos MICROS_TO_MILLIS = 1000; 12 | constexpr Nanos MILLIS_TO_SECS = 1000; 13 | constexpr Nanos NANOS_TO_MILLIS = NANOS_TO_MICROS * MICROS_TO_MILLIS; 14 | constexpr Nanos NANOS_TO_SECS = NANOS_TO_MILLIS * MILLIS_TO_SECS; 15 | 16 | inline auto getCurrentNanos() noexcept { 17 | return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); 18 | } 19 | 20 | inline auto& getCurrentTimeStr(std::string* time_str) { 21 | const auto time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); 22 | time_str->assign(ctime(&time)); 23 | if(!time_str->empty()) 24 | time_str->at(time_str->length()-1) = '\0'; 25 | return *time_str; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Chapter7/exchange/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | set(CMAKE_CXX_COMPILER g++) 3 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 4 | set(CMAKE_VERBOSE_MAKEFILE on) 5 | 6 | file(GLOB SOURCES "*/*.cpp") 7 | 8 | include_directories(${PROJECT_SOURCE_DIR}) 9 | include_directories(${PROJECT_SOURCE_DIR}/exchange) 10 | 11 | add_library(libexchange STATIC ${SOURCES}) 12 | -------------------------------------------------------------------------------- /Chapter7/exchange/market_data/snapshot_synthesizer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/types.h" 4 | #include "common/thread_utils.h" 5 | #include "common/lf_queue.h" 6 | #include "common/macros.h" 7 | #include "common/mcast_socket.h" 8 | #include "common/mem_pool.h" 9 | #include "common/logging.h" 10 | 11 | #include "market_data/market_update.h" 12 | #include "matcher/me_order.h" 13 | 14 | using namespace Common; 15 | 16 | namespace Exchange { 17 | class SnapshotSynthesizer { 18 | public: 19 | SnapshotSynthesizer(MDPMarketUpdateLFQueue *market_updates, const std::string &iface, 20 | const std::string &snapshot_ip, int snapshot_port); 21 | 22 | ~SnapshotSynthesizer(); 23 | 24 | auto start() -> void; 25 | 26 | auto stop() -> void; 27 | 28 | auto addToSnapshot(const MDPMarketUpdate *market_update); 29 | 30 | auto publishSnapshot(); 31 | 32 | auto run() -> void; 33 | 34 | // Deleted default, copy & move constructors and assignment-operators. 35 | SnapshotSynthesizer() = delete; 36 | 37 | SnapshotSynthesizer(const SnapshotSynthesizer &) = delete; 38 | 39 | SnapshotSynthesizer(const SnapshotSynthesizer &&) = delete; 40 | 41 | SnapshotSynthesizer &operator=(const SnapshotSynthesizer &) = delete; 42 | 43 | SnapshotSynthesizer &operator=(const SnapshotSynthesizer &&) = delete; 44 | 45 | private: 46 | MDPMarketUpdateLFQueue *snapshot_md_updates_ = nullptr; 47 | 48 | Logger logger_; 49 | 50 | volatile bool run_ = false; 51 | 52 | std::string time_str_; 53 | 54 | McastSocket snapshot_socket_; 55 | 56 | std::array, ME_MAX_TICKERS> ticker_orders_; 57 | size_t last_inc_seq_num_ = 0; 58 | Nanos last_snapshot_time_ = 0; 59 | 60 | MemPool order_pool_; 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /Chapter7/exchange/matcher/matching_engine.cpp: -------------------------------------------------------------------------------- 1 | #include "matching_engine.h" 2 | 3 | namespace Exchange { 4 | MatchingEngine::MatchingEngine(ClientRequestLFQueue *client_requests, ClientResponseLFQueue *client_responses, 5 | MEMarketUpdateLFQueue *market_updates) 6 | : incoming_requests_(client_requests), outgoing_ogw_responses_(client_responses), outgoing_md_updates_(market_updates), 7 | logger_("exchange_matching_engine.log") { 8 | for(size_t i = 0; i < ticker_order_book_.size(); ++i) { 9 | ticker_order_book_[i] = new MEOrderBook(i, &logger_, this); 10 | } 11 | } 12 | 13 | MatchingEngine::~MatchingEngine() { 14 | stop(); 15 | 16 | using namespace std::literals::chrono_literals; 17 | std::this_thread::sleep_for(1s); 18 | 19 | incoming_requests_ = nullptr; 20 | outgoing_ogw_responses_ = nullptr; 21 | outgoing_md_updates_ = nullptr; 22 | 23 | for(auto& order_book : ticker_order_book_) { 24 | delete order_book; 25 | order_book = nullptr; 26 | } 27 | } 28 | 29 | auto MatchingEngine::start() -> void { 30 | run_ = true; 31 | ASSERT(Common::createAndStartThread(-1, "Exchange/MatchingEngine", [this]() { run(); }) != nullptr, "Failed to start MatchingEngine thread."); 32 | } 33 | 34 | auto MatchingEngine::stop() -> void { 35 | run_ = false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Chapter7/exchange/matcher/me_order.cpp: -------------------------------------------------------------------------------- 1 | #include "me_order.h" 2 | 3 | namespace Exchange { 4 | auto MEOrder::toString() const -> std::string { 5 | std::stringstream ss; 6 | ss << "MEOrder" << "[" 7 | << "ticker:" << tickerIdToString(ticker_id_) << " " 8 | << "cid:" << clientIdToString(client_id_) << " " 9 | << "oid:" << orderIdToString(client_order_id_) << " " 10 | << "moid:" << orderIdToString(market_order_id_) << " " 11 | << "side:" << sideToString(side_) << " " 12 | << "price:" << priceToString(price_) << " " 13 | << "qty:" << qtyToString(qty_) << " " 14 | << "prio:" << priorityToString(priority_) << " " 15 | << "prev:" << orderIdToString(prev_order_ ? prev_order_->market_order_id_ : OrderId_INVALID) << " " 16 | << "next:" << orderIdToString(next_order_ ? next_order_->market_order_id_ : OrderId_INVALID) << "]"; 17 | 18 | return ss.str(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter7/exchange/order_server/order_server.cpp: -------------------------------------------------------------------------------- 1 | #include "order_server.h" 2 | 3 | namespace Exchange { 4 | OrderServer::OrderServer(ClientRequestLFQueue *client_requests, ClientResponseLFQueue *client_responses, const std::string &iface, int port) 5 | : iface_(iface), port_(port), outgoing_responses_(client_responses), logger_("exchange_order_server.log"), 6 | tcp_server_(logger_), fifo_sequencer_(client_requests, &logger_) { 7 | cid_next_outgoing_seq_num_.fill(1); 8 | cid_next_exp_seq_num_.fill(1); 9 | cid_tcp_socket_.fill(nullptr); 10 | 11 | tcp_server_.recv_callback_ = [this](auto socket, auto rx_time) { recvCallback(socket, rx_time); }; 12 | tcp_server_.recv_finished_callback_ = [this]() { recvFinishedCallback(); }; 13 | } 14 | 15 | OrderServer::~OrderServer() { 16 | stop(); 17 | 18 | using namespace std::literals::chrono_literals; 19 | std::this_thread::sleep_for(1s); 20 | } 21 | 22 | auto OrderServer::start() -> void { 23 | run_ = true; 24 | tcp_server_.listen(iface_, port_); 25 | 26 | ASSERT(Common::createAndStartThread(-1, "Exchange/OrderServer", [this]() { run(); }) != nullptr, "Failed to start OrderServer thread."); 27 | } 28 | 29 | auto OrderServer::stop() -> void { 30 | run_ = false; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Chapter8/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(LowLatencyApp) 4 | 5 | set(CMAKE_CXX_STANDARD 20) 6 | set(CMAKE_CXX_COMPILER g++) 7 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 8 | set(CMAKE_VERBOSE_MAKEFILE on) 9 | 10 | add_subdirectory(common) 11 | add_subdirectory(exchange) 12 | add_subdirectory(trading) 13 | 14 | list(APPEND LIBS libexchange) 15 | list(APPEND LIBS libcommon) 16 | list(APPEND LIBS libtrading) 17 | list(APPEND LIBS pthread) 18 | 19 | include_directories(${PROJECT_SOURCE_DIR}) 20 | include_directories(${PROJECT_SOURCE_DIR}/exchange) 21 | include_directories(${PROJECT_SOURCE_DIR}/trading) 22 | 23 | add_executable(exchange_main exchange/exchange_main.cpp) 24 | target_link_libraries(exchange_main PUBLIC ${LIBS}) 25 | 26 | -------------------------------------------------------------------------------- /Chapter8/common/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | set(CMAKE_CXX_COMPILER g++) 3 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 4 | set(CMAKE_VERBOSE_MAKEFILE on) 5 | 6 | file(GLOB SOURCES "*.cpp") 7 | 8 | include_directories(${PROJECT_SOURCE_DIR}) 9 | 10 | add_library(libcommon STATIC ${SOURCES}) 11 | 12 | list(APPEND LIBS libcommon) 13 | list(APPEND LIBS pthread) 14 | 15 | add_executable(thread_example thread_example.cpp) 16 | target_link_libraries(thread_example PUBLIC ${LIBS}) 17 | 18 | add_executable(mem_pool_example mem_pool_example.cpp) 19 | target_link_libraries(mem_pool_example PUBLIC ${LIBS}) 20 | 21 | add_executable(lf_queue_example lf_queue_example.cpp) 22 | target_link_libraries(lf_queue_example PUBLIC ${LIBS}) 23 | 24 | add_executable(logging_example logging_example.cpp) 25 | target_link_libraries(logging_example PUBLIC ${LIBS}) 26 | 27 | add_executable(socket_example socket_example.cpp) 28 | target_link_libraries(socket_example PUBLIC ${LIBS}) 29 | -------------------------------------------------------------------------------- /Chapter8/common/lf_queue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "macros.h" 8 | 9 | namespace Common { 10 | template 11 | class LFQueue final { 12 | public: 13 | LFQueue(std::size_t num_elems) : 14 | store_(num_elems, T()) /* pre-allocation of vector storage. */ { 15 | } 16 | 17 | auto getNextToWriteTo() noexcept { 18 | return &store_[next_write_index_]; 19 | } 20 | 21 | auto updateWriteIndex() noexcept { 22 | next_write_index_ = (next_write_index_ + 1) % store_.size(); 23 | num_elements_++; 24 | } 25 | 26 | auto getNextToRead() const noexcept -> const T * { 27 | return (size() ? &store_[next_read_index_] : nullptr); 28 | } 29 | 30 | auto updateReadIndex() noexcept { 31 | next_read_index_ = (next_read_index_ + 1) % store_.size(); 32 | ASSERT(num_elements_ != 0, "Read an invalid element in:" + std::to_string(pthread_self())); 33 | num_elements_--; 34 | } 35 | 36 | auto size() const noexcept { 37 | return num_elements_.load(); 38 | } 39 | 40 | // Deleted default, copy & move constructors and assignment-operators. 41 | LFQueue() = delete; 42 | 43 | LFQueue(const LFQueue &) = delete; 44 | 45 | LFQueue(const LFQueue &&) = delete; 46 | 47 | LFQueue &operator=(const LFQueue &) = delete; 48 | 49 | LFQueue &operator=(const LFQueue &&) = delete; 50 | 51 | private: 52 | std::vector store_; 53 | 54 | std::atomic next_write_index_ = {0}; 55 | std::atomic next_read_index_ = {0}; 56 | 57 | std::atomic num_elements_ = {0}; 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /Chapter8/common/lf_queue_example.cpp: -------------------------------------------------------------------------------- 1 | #include "thread_utils.h" 2 | #include "lf_queue.h" 3 | 4 | struct MyStruct { 5 | int d_[3]; 6 | }; 7 | 8 | using namespace Common; 9 | 10 | auto consumeFunction(LFQueue* lfq) { 11 | using namespace std::literals::chrono_literals; 12 | std::this_thread::sleep_for(5s); 13 | 14 | while(lfq->size()) { 15 | const auto d = lfq->getNextToRead(); 16 | lfq->updateReadIndex(); 17 | 18 | std::cout << "consumeFunction read elem:" << d->d_[0] << "," << d->d_[1] << "," << d->d_[2] << " lfq-size:" << lfq->size() << std::endl; 19 | 20 | std::this_thread::sleep_for(1s); 21 | } 22 | 23 | std::cout << "consumeFunction exiting." << std::endl; 24 | } 25 | 26 | int main(int, char **) { 27 | LFQueue lfq(20); 28 | 29 | auto ct = createAndStartThread(-1, "", consumeFunction, &lfq); 30 | 31 | for(auto i = 0; i < 50; ++i) { 32 | const MyStruct d{i, i * 10, i * 100}; 33 | *(lfq.getNextToWriteTo()) = d; 34 | lfq.updateWriteIndex(); 35 | 36 | std::cout << "main constructed elem:" << d.d_[0] << "," << d.d_[1] << "," << d.d_[2] << " lfq-size:" << lfq.size() << std::endl; 37 | 38 | using namespace std::literals::chrono_literals; 39 | std::this_thread::sleep_for(1s); 40 | } 41 | 42 | ct->join(); 43 | 44 | std::cout << "main exiting." << std::endl; 45 | 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /Chapter8/common/logging_example.cpp: -------------------------------------------------------------------------------- 1 | #include "logging.h" 2 | 3 | int main(int, char **) { 4 | using namespace Common; 5 | 6 | char c = 'd'; 7 | int i = 3; 8 | unsigned long ul = 65; 9 | float f = 3.4; 10 | double d = 34.56; 11 | const char* s = "test C-string"; 12 | std::string ss = "test string"; 13 | 14 | Logger logger("logging_example.log"); 15 | 16 | logger.log("Logging a char:% an int:% and an unsigned:%\n", c, i, ul); 17 | logger.log("Logging a float:% and a double:%\n", f, d); 18 | logger.log("Logging a C-string:'%'\n", s); 19 | logger.log("Logging a string:'%'\n", ss); 20 | 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /Chapter8/common/macros.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #define LIKELY(x) __builtin_expect(!!(x), 1) 7 | #define UNLIKELY(x) __builtin_expect(!!(x), 0) 8 | 9 | inline auto ASSERT(bool cond, const std::string &msg) noexcept { 10 | if (UNLIKELY(!cond)) { 11 | std::cerr << "ASSERT : " << msg << std::endl; 12 | 13 | exit(EXIT_FAILURE); 14 | } 15 | } 16 | 17 | inline auto FATAL(const std::string &msg) noexcept { 18 | std::cerr << "FATAL : " << msg << std::endl; 19 | 20 | exit(EXIT_FAILURE); 21 | } 22 | -------------------------------------------------------------------------------- /Chapter8/common/mcast_socket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "socket_utils.h" 6 | 7 | #include "logging.h" 8 | 9 | namespace Common { 10 | /// Size of send and receive buffers in bytes. 11 | constexpr size_t McastBufferSize = 64 * 1024 * 1024; 12 | 13 | struct McastSocket { 14 | McastSocket(Logger &logger) 15 | : logger_(logger) { 16 | outbound_data_.resize(McastBufferSize); 17 | inbound_data_.resize(McastBufferSize); 18 | } 19 | 20 | /// Initialize multicast socket to read from or publish to a stream. 21 | /// Does not join the multicast stream yet. 22 | auto init(const std::string &ip, const std::string &iface, int port, bool is_listening) -> int; 23 | 24 | /// Add / Join membership / subscription to a multicast stream. 25 | auto join(const std::string &ip) -> bool; 26 | 27 | /// Remove / Leave membership / subscription to a multicast stream. 28 | auto leave(const std::string &ip, int port) -> void; 29 | 30 | /// Publish outgoing data and read incoming data. 31 | auto sendAndRecv() noexcept -> bool; 32 | 33 | /// Copy data to send buffers - does not send them out yet. 34 | auto send(const void *data, size_t len) noexcept -> void; 35 | 36 | int socket_fd_ = -1; 37 | 38 | /// Send and receive buffers, typically only one or the other is needed, not both. 39 | std::vector outbound_data_; 40 | size_t next_send_valid_index_ = 0; 41 | std::vector inbound_data_; 42 | size_t next_rcv_valid_index_ = 0; 43 | 44 | /// Function wrapper for the method to call when data is read. 45 | std::function recv_callback_ = nullptr; 46 | 47 | std::string time_str_; 48 | Logger &logger_; 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /Chapter8/common/mem_pool_example.cpp: -------------------------------------------------------------------------------- 1 | #include "mem_pool.h" 2 | 3 | struct MyStruct { 4 | int d_[3]; 5 | }; 6 | 7 | int main(int, char **) { 8 | using namespace Common; 9 | 10 | MemPool prim_pool(50); 11 | MemPool struct_pool(50); 12 | 13 | for(auto i = 0; i < 50; ++i) { 14 | auto p_ret = prim_pool.allocate(i); 15 | auto s_ret = struct_pool.allocate(MyStruct{i, i+1, i+2}); 16 | 17 | std::cout << "prim elem:" << *p_ret << " allocated at:" << p_ret << std::endl; 18 | std::cout << "struct elem:" << s_ret->d_[0] << "," << s_ret->d_[1] << "," << s_ret->d_[2] << " allocated at:" << s_ret << std::endl; 19 | 20 | if(i % 5 == 0) { 21 | std::cout << "deallocating prim elem:" << *p_ret << " from:" << p_ret << std::endl; 22 | std::cout << "deallocating struct elem:" << s_ret->d_[0] << "," << s_ret->d_[1] << "," << s_ret->d_[2] << " from:" << s_ret << std::endl; 23 | 24 | prim_pool.deallocate(p_ret); 25 | struct_pool.deallocate(s_ret); 26 | } 27 | } 28 | 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /Chapter8/common/tcp_server.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "tcp_socket.h" 4 | 5 | namespace Common { 6 | struct TCPServer { 7 | explicit TCPServer(Logger &logger) 8 | : listener_socket_(logger), logger_(logger) { 9 | } 10 | 11 | /// Start listening for connections on the provided interface and port. 12 | auto listen(const std::string &iface, int port) -> void; 13 | 14 | /// Check for new connections or dead connections and update containers that track the sockets. 15 | auto poll() noexcept -> void; 16 | 17 | /// Publish outgoing data from the send buffer and read incoming data from the receive buffer. 18 | auto sendAndRecv() noexcept -> void; 19 | 20 | private: 21 | /// Add and remove socket file descriptors to and from the EPOLL list. 22 | auto addToEpollList(TCPSocket *socket); 23 | 24 | public: 25 | /// Socket on which this server is listening for new connections on. 26 | int epoll_fd_ = -1; 27 | TCPSocket listener_socket_; 28 | 29 | epoll_event events_[1024]; 30 | 31 | /// Collection of all sockets, sockets for incoming data, sockets for outgoing data and dead connections. 32 | std::vector receive_sockets_, send_sockets_; 33 | 34 | /// Function wrapper to call back when data is available. 35 | std::function recv_callback_ = nullptr; 36 | /// Function wrapper to call back when all data across all TCPSockets has been read and dispatched this round. 37 | std::function recv_finished_callback_ = nullptr; 38 | 39 | std::string time_str_; 40 | Logger &logger_; 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /Chapter8/common/thread_example.cpp: -------------------------------------------------------------------------------- 1 | #include "thread_utils.h" 2 | 3 | auto dummyFunction(int a, int b, bool sleep) { 4 | std::cout << "dummyFunction(" << a << "," << b << ")" << std::endl; 5 | std::cout << "dummyFunction output=" << a + b << std::endl; 6 | 7 | if(sleep) { 8 | std::cout << "dummyFunction sleeping..." << std::endl; 9 | 10 | using namespace std::literals::chrono_literals; 11 | std::this_thread::sleep_for(5s); 12 | } 13 | 14 | std::cout << "dummyFunction done." << std::endl; 15 | } 16 | 17 | int main(int, char **) { 18 | using namespace Common; 19 | 20 | auto t1 = createAndStartThread(-1, "dummyFunction1", dummyFunction, 12, 21, false); 21 | auto t2 = createAndStartThread(1, "dummyFunction2", dummyFunction, 15, 51, true); 22 | 23 | std::cout << "main waiting for threads to be done." << std::endl; 24 | t1->join(); 25 | t2->join(); 26 | std::cout << "main exiting." << std::endl; 27 | 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /Chapter8/common/thread_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace Common { 11 | /// Set affinity for current thread to be pinned to the provided core_id. 12 | inline auto setThreadCore(int core_id) noexcept { 13 | cpu_set_t cpuset; 14 | 15 | CPU_ZERO(&cpuset); 16 | CPU_SET(core_id, &cpuset); 17 | 18 | return (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) == 0); 19 | } 20 | 21 | /// Creates a thread instance, sets affinity on it, assigns it a name and 22 | /// passes the function to be run on that thread as well as the arguments to the function. 23 | template 24 | inline auto createAndStartThread(int core_id, const std::string &name, T &&func, A &&... args) noexcept { 25 | auto t = new std::thread([&]() { 26 | if (core_id >= 0 && !setThreadCore(core_id)) { 27 | std::cerr << "Failed to set core affinity for " << name << " " << pthread_self() << " to " << core_id << std::endl; 28 | exit(EXIT_FAILURE); 29 | } 30 | std::cerr << "Set core affinity for " << name << " " << pthread_self() << " to " << core_id << std::endl; 31 | 32 | std::forward(func)((std::forward(args))...); 33 | }); 34 | 35 | using namespace std::literals::chrono_literals; 36 | std::this_thread::sleep_for(1s); 37 | 38 | return t; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Chapter8/common/time_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Common { 8 | typedef int64_t Nanos; 9 | 10 | constexpr Nanos NANOS_TO_MICROS = 1000; 11 | constexpr Nanos MICROS_TO_MILLIS = 1000; 12 | constexpr Nanos MILLIS_TO_SECS = 1000; 13 | constexpr Nanos NANOS_TO_MILLIS = NANOS_TO_MICROS * MICROS_TO_MILLIS; 14 | constexpr Nanos NANOS_TO_SECS = NANOS_TO_MILLIS * MILLIS_TO_SECS; 15 | 16 | inline auto getCurrentNanos() noexcept { 17 | return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); 18 | } 19 | 20 | inline auto& getCurrentTimeStr(std::string* time_str) { 21 | const auto time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); 22 | time_str->assign(ctime(&time)); 23 | if(!time_str->empty()) 24 | time_str->at(time_str->length()-1) = '\0'; 25 | return *time_str; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Chapter8/exchange/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | set(CMAKE_CXX_COMPILER g++) 3 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 4 | set(CMAKE_VERBOSE_MAKEFILE on) 5 | 6 | file(GLOB SOURCES "*/*.cpp") 7 | 8 | include_directories(${PROJECT_SOURCE_DIR}) 9 | include_directories(${PROJECT_SOURCE_DIR}/exchange) 10 | 11 | add_library(libexchange STATIC ${SOURCES}) 12 | -------------------------------------------------------------------------------- /Chapter8/exchange/market_data/snapshot_synthesizer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/types.h" 4 | #include "common/thread_utils.h" 5 | #include "common/lf_queue.h" 6 | #include "common/macros.h" 7 | #include "common/mcast_socket.h" 8 | #include "common/mem_pool.h" 9 | #include "common/logging.h" 10 | 11 | #include "market_data/market_update.h" 12 | #include "matcher/me_order.h" 13 | 14 | using namespace Common; 15 | 16 | namespace Exchange { 17 | class SnapshotSynthesizer { 18 | public: 19 | SnapshotSynthesizer(MDPMarketUpdateLFQueue *market_updates, const std::string &iface, 20 | const std::string &snapshot_ip, int snapshot_port); 21 | 22 | ~SnapshotSynthesizer(); 23 | 24 | auto start() -> void; 25 | 26 | auto stop() -> void; 27 | 28 | auto addToSnapshot(const MDPMarketUpdate *market_update); 29 | 30 | auto publishSnapshot(); 31 | 32 | auto run() -> void; 33 | 34 | // Deleted default, copy & move constructors and assignment-operators. 35 | SnapshotSynthesizer() = delete; 36 | 37 | SnapshotSynthesizer(const SnapshotSynthesizer &) = delete; 38 | 39 | SnapshotSynthesizer(const SnapshotSynthesizer &&) = delete; 40 | 41 | SnapshotSynthesizer &operator=(const SnapshotSynthesizer &) = delete; 42 | 43 | SnapshotSynthesizer &operator=(const SnapshotSynthesizer &&) = delete; 44 | 45 | private: 46 | MDPMarketUpdateLFQueue *snapshot_md_updates_ = nullptr; 47 | 48 | Logger logger_; 49 | 50 | volatile bool run_ = false; 51 | 52 | std::string time_str_; 53 | 54 | McastSocket snapshot_socket_; 55 | 56 | std::array, ME_MAX_TICKERS> ticker_orders_; 57 | size_t last_inc_seq_num_ = 0; 58 | Nanos last_snapshot_time_ = 0; 59 | 60 | MemPool order_pool_; 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /Chapter8/exchange/matcher/matching_engine.cpp: -------------------------------------------------------------------------------- 1 | #include "matching_engine.h" 2 | 3 | namespace Exchange { 4 | MatchingEngine::MatchingEngine(ClientRequestLFQueue *client_requests, ClientResponseLFQueue *client_responses, 5 | MEMarketUpdateLFQueue *market_updates) 6 | : incoming_requests_(client_requests), outgoing_ogw_responses_(client_responses), outgoing_md_updates_(market_updates), 7 | logger_("exchange_matching_engine.log") { 8 | for(size_t i = 0; i < ticker_order_book_.size(); ++i) { 9 | ticker_order_book_[i] = new MEOrderBook(i, &logger_, this); 10 | } 11 | } 12 | 13 | MatchingEngine::~MatchingEngine() { 14 | stop(); 15 | 16 | using namespace std::literals::chrono_literals; 17 | std::this_thread::sleep_for(1s); 18 | 19 | incoming_requests_ = nullptr; 20 | outgoing_ogw_responses_ = nullptr; 21 | outgoing_md_updates_ = nullptr; 22 | 23 | for(auto& order_book : ticker_order_book_) { 24 | delete order_book; 25 | order_book = nullptr; 26 | } 27 | } 28 | 29 | auto MatchingEngine::start() -> void { 30 | run_ = true; 31 | ASSERT(Common::createAndStartThread(-1, "Exchange/MatchingEngine", [this]() { run(); }) != nullptr, "Failed to start MatchingEngine thread."); 32 | } 33 | 34 | auto MatchingEngine::stop() -> void { 35 | run_ = false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Chapter8/exchange/matcher/me_order.cpp: -------------------------------------------------------------------------------- 1 | #include "me_order.h" 2 | 3 | namespace Exchange { 4 | auto MEOrder::toString() const -> std::string { 5 | std::stringstream ss; 6 | ss << "MEOrder" << "[" 7 | << "ticker:" << tickerIdToString(ticker_id_) << " " 8 | << "cid:" << clientIdToString(client_id_) << " " 9 | << "oid:" << orderIdToString(client_order_id_) << " " 10 | << "moid:" << orderIdToString(market_order_id_) << " " 11 | << "side:" << sideToString(side_) << " " 12 | << "price:" << priceToString(price_) << " " 13 | << "qty:" << qtyToString(qty_) << " " 14 | << "prio:" << priorityToString(priority_) << " " 15 | << "prev:" << orderIdToString(prev_order_ ? prev_order_->market_order_id_ : OrderId_INVALID) << " " 16 | << "next:" << orderIdToString(next_order_ ? next_order_->market_order_id_ : OrderId_INVALID) << "]"; 17 | 18 | return ss.str(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter8/exchange/order_server/order_server.cpp: -------------------------------------------------------------------------------- 1 | #include "order_server.h" 2 | 3 | namespace Exchange { 4 | OrderServer::OrderServer(ClientRequestLFQueue *client_requests, ClientResponseLFQueue *client_responses, const std::string &iface, int port) 5 | : iface_(iface), port_(port), outgoing_responses_(client_responses), logger_("exchange_order_server.log"), 6 | tcp_server_(logger_), fifo_sequencer_(client_requests, &logger_) { 7 | cid_next_outgoing_seq_num_.fill(1); 8 | cid_next_exp_seq_num_.fill(1); 9 | cid_tcp_socket_.fill(nullptr); 10 | 11 | tcp_server_.recv_callback_ = [this](auto socket, auto rx_time) { recvCallback(socket, rx_time); }; 12 | tcp_server_.recv_finished_callback_ = [this]() { recvFinishedCallback(); }; 13 | } 14 | 15 | OrderServer::~OrderServer() { 16 | stop(); 17 | 18 | using namespace std::literals::chrono_literals; 19 | std::this_thread::sleep_for(1s); 20 | } 21 | 22 | auto OrderServer::start() -> void { 23 | run_ = true; 24 | tcp_server_.listen(iface_, port_); 25 | 26 | ASSERT(Common::createAndStartThread(-1, "Exchange/OrderServer", [this]() { run(); }) != nullptr, "Failed to start OrderServer thread."); 27 | } 28 | 29 | auto OrderServer::stop() -> void { 30 | run_ = false; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Chapter8/scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # TODO - point these to the correct binary locations on your system. 4 | CMAKE=$(which cmake) 5 | NINJA=$(which ninja) 6 | 7 | mkdir -p ./cmake-build-release 8 | $CMAKE -DCMAKE_BUILD_TYPE=Release -DCMAKE_MAKE_PROGRAM=$NINJA -G Ninja -S . -B ./cmake-build-release 9 | 10 | $CMAKE --build ./cmake-build-release --target clean -j 4 11 | $CMAKE --build ./cmake-build-release --target all -j 4 12 | 13 | mkdir -p ./cmake-build-debug 14 | $CMAKE -DCMAKE_BUILD_TYPE=Debug -DCMAKE_MAKE_PROGRAM=$NINJA -G Ninja -S . -B ./cmake-build-debug 15 | 16 | $CMAKE --build ./cmake-build-debug --target clean -j 4 17 | $CMAKE --build ./cmake-build-debug --target all -j 4 18 | -------------------------------------------------------------------------------- /Chapter8/scripts/no_clean_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # TODO - point these to the correct binary locations on your system. 4 | CMAKE=$(which cmake) 5 | NINJA=$(which ninja) 6 | 7 | mkdir -p ./cmake-build-release 8 | $CMAKE -DCMAKE_BUILD_TYPE=Release -DCMAKE_MAKE_PROGRAM=$NINJA -G Ninja -S . -B ./cmake-build-release 9 | 10 | $CMAKE --build ./cmake-build-release --target all -j 4 11 | 12 | mkdir -p ./cmake-build-debug 13 | $CMAKE -DCMAKE_BUILD_TYPE=Debug -DCMAKE_MAKE_PROGRAM=$NINJA -G Ninja -S . -B ./cmake-build-debug 14 | 15 | $CMAKE --build ./cmake-build-debug --target all -j 4 16 | -------------------------------------------------------------------------------- /Chapter8/trading/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | set(CMAKE_CXX_COMPILER g++) 3 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 4 | set(CMAKE_VERBOSE_MAKEFILE on) 5 | 6 | file(GLOB SOURCES "*/*.cpp") 7 | 8 | include_directories(${PROJECT_SOURCE_DIR}) 9 | include_directories(${PROJECT_SOURCE_DIR}/trading) 10 | 11 | add_library(libtrading STATIC ${SOURCES}) 12 | -------------------------------------------------------------------------------- /Chapter8/trading/strategy/liquidity_taker.cpp: -------------------------------------------------------------------------------- 1 | #include "liquidity_taker.h" 2 | 3 | #include "trade_engine.h" 4 | 5 | namespace Trading { 6 | LiquidityTaker::LiquidityTaker(Common::Logger *logger, TradeEngine *trade_engine, const FeatureEngine *feature_engine, 7 | OrderManager *order_manager, 8 | const TradeEngineCfgHashMap &ticker_cfg) 9 | : feature_engine_(feature_engine), order_manager_(order_manager), logger_(logger), 10 | ticker_cfg_(ticker_cfg) { 11 | trade_engine->algoOnOrderBookUpdate_ = [this](auto ticker_id, auto price, auto side, auto book) { 12 | onOrderBookUpdate(ticker_id, price, side, book); 13 | }; 14 | trade_engine->algoOnTradeUpdate_ = [this](auto market_update, auto book) { onTradeUpdate(market_update, book); }; 15 | trade_engine->algoOnOrderUpdate_ = [this](auto client_response) { onOrderUpdate(client_response); }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter8/trading/strategy/market_maker.cpp: -------------------------------------------------------------------------------- 1 | #include "market_maker.h" 2 | 3 | #include "trade_engine.h" 4 | 5 | namespace Trading { 6 | MarketMaker::MarketMaker(Common::Logger *logger, TradeEngine *trade_engine, const FeatureEngine *feature_engine, 7 | OrderManager *order_manager, const TradeEngineCfgHashMap &ticker_cfg) 8 | : feature_engine_(feature_engine), order_manager_(order_manager), logger_(logger), 9 | ticker_cfg_(ticker_cfg) { 10 | trade_engine->algoOnOrderBookUpdate_ = [this](auto ticker_id, auto price, auto side, auto book) { 11 | onOrderBookUpdate(ticker_id, price, side, book); 12 | }; 13 | trade_engine->algoOnTradeUpdate_ = [this](auto market_update, auto book) { onTradeUpdate(market_update, book); }; 14 | trade_engine->algoOnOrderUpdate_ = [this](auto client_response) { onOrderUpdate(client_response); }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Chapter8/trading/strategy/market_order.cpp: -------------------------------------------------------------------------------- 1 | #include "market_order.h" 2 | 3 | namespace Trading { 4 | auto MarketOrder::toString() const -> std::string { 5 | std::stringstream ss; 6 | ss << "MarketOrder" << "[" 7 | << "oid:" << orderIdToString(order_id_) << " " 8 | << "side:" << sideToString(side_) << " " 9 | << "price:" << priceToString(price_) << " " 10 | << "qty:" << qtyToString(qty_) << " " 11 | << "prio:" << priorityToString(priority_) << " " 12 | << "prev:" << orderIdToString(prev_order_ ? prev_order_->order_id_ : OrderId_INVALID) << " " 13 | << "next:" << orderIdToString(next_order_ ? next_order_->order_id_ : OrderId_INVALID) << "]"; 14 | 15 | return ss.str(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter8/trading/strategy/om_order.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "common/types.h" 6 | 7 | using namespace Common; 8 | 9 | namespace Trading { 10 | enum class OMOrderState : int8_t { 11 | INVALID = 0, 12 | PENDING_NEW = 1, 13 | LIVE = 2, 14 | PENDING_CANCEL = 3, 15 | DEAD = 4 16 | }; 17 | 18 | inline auto OMOrderStateToString(OMOrderState side) -> std::string { 19 | switch (side) { 20 | case OMOrderState::PENDING_NEW: 21 | return "PENDING_NEW"; 22 | case OMOrderState::LIVE: 23 | return "LIVE"; 24 | case OMOrderState::PENDING_CANCEL: 25 | return "PENDING_CANCEL"; 26 | case OMOrderState::DEAD: 27 | return "DEAD"; 28 | case OMOrderState::INVALID: 29 | return "INVALID"; 30 | } 31 | 32 | return "UNKNOWN"; 33 | } 34 | 35 | struct OMOrder { 36 | TickerId ticker_id_ = TickerId_INVALID; 37 | OrderId order_id_ = OrderId_INVALID; 38 | Side side_ = Side::INVALID; 39 | Price price_ = Price_INVALID; 40 | Qty qty_ = Qty_INVALID; 41 | OMOrderState order_state_ = OMOrderState::INVALID; 42 | 43 | auto toString() const { 44 | std::stringstream ss; 45 | ss << "OMOrder" << "[" 46 | << "tid:" << tickerIdToString(ticker_id_) << " " 47 | << "oid:" << orderIdToString(order_id_) << " " 48 | << "side:" << sideToString(side_) << " " 49 | << "price:" << priceToString(price_) << " " 50 | << "qty:" << qtyToString(qty_) << " " 51 | << "state:" << OMOrderStateToString(order_state_) << "]"; 52 | 53 | return ss.str(); 54 | } 55 | }; 56 | 57 | typedef std::array OMOrderSideHashMap; 58 | typedef std::array OMOrderTickerSideHashMap; 59 | } 60 | -------------------------------------------------------------------------------- /Chapter8/trading/strategy/order_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "order_manager.h" 2 | #include "trade_engine.h" 3 | 4 | namespace Trading { 5 | auto OrderManager::newOrder(OMOrder *order, TickerId ticker_id, Price price, Side side, Qty qty) noexcept -> void { 6 | const Exchange::MEClientRequest new_request{Exchange::ClientRequestType::NEW, trade_engine_->clientId(), ticker_id, 7 | next_order_id_, side, price, qty}; 8 | trade_engine_->sendClientRequest(&new_request); 9 | 10 | *order = {ticker_id, next_order_id_, side, price, qty, OMOrderState::PENDING_NEW}; 11 | ++next_order_id_; 12 | 13 | logger_->log("%:% %() % Sent new order % for %\n", __FILE__, __LINE__, __FUNCTION__, 14 | Common::getCurrentTimeStr(&time_str_), 15 | new_request.toString().c_str(), order->toString().c_str()); 16 | } 17 | 18 | auto OrderManager::cancelOrder(OMOrder *order) noexcept -> void { 19 | const Exchange::MEClientRequest cancel_request{Exchange::ClientRequestType::CANCEL, trade_engine_->clientId(), 20 | order->ticker_id_, order->order_id_, order->side_, order->price_, 21 | order->qty_}; 22 | trade_engine_->sendClientRequest(&cancel_request); 23 | 24 | order->order_state_ = OMOrderState::PENDING_CANCEL; 25 | 26 | logger_->log("%:% %() % Sent cancel % for %\n", __FILE__, __LINE__, __FUNCTION__, 27 | Common::getCurrentTimeStr(&time_str_), 28 | cancel_request.toString().c_str(), order->toString().c_str()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Chapter8/trading/strategy/risk_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "risk_manager.h" 2 | 3 | #include "order_manager.h" 4 | 5 | namespace Trading { 6 | RiskManager::RiskManager(Common::Logger *logger, const PositionKeeper *position_keeper, const TradeEngineCfgHashMap &ticker_cfg) 7 | : logger_(logger) { 8 | for (TickerId i = 0; i < ME_MAX_TICKERS; ++i) { 9 | ticker_risk_.at(i).position_info_ = position_keeper->getPositionInfo(i); 10 | ticker_risk_.at(i).risk_cfg_ = ticker_cfg[i].risk_cfg_; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Chapter9/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(LowLatencyApp) 4 | 5 | set(CMAKE_CXX_STANDARD 20) 6 | set(CMAKE_CXX_COMPILER g++) 7 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 8 | set(CMAKE_VERBOSE_MAKEFILE on) 9 | 10 | add_subdirectory(common) 11 | add_subdirectory(exchange) 12 | add_subdirectory(trading) 13 | 14 | list(APPEND LIBS libexchange) 15 | list(APPEND LIBS libcommon) 16 | list(APPEND LIBS libtrading) 17 | list(APPEND LIBS pthread) 18 | 19 | include_directories(${PROJECT_SOURCE_DIR}) 20 | include_directories(${PROJECT_SOURCE_DIR}/exchange) 21 | include_directories(${PROJECT_SOURCE_DIR}/trading) 22 | 23 | add_executable(exchange_main exchange/exchange_main.cpp) 24 | target_link_libraries(exchange_main PUBLIC ${LIBS}) 25 | 26 | -------------------------------------------------------------------------------- /Chapter9/common/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | set(CMAKE_CXX_COMPILER g++) 3 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 4 | set(CMAKE_VERBOSE_MAKEFILE on) 5 | 6 | file(GLOB SOURCES "*.cpp") 7 | 8 | include_directories(${PROJECT_SOURCE_DIR}) 9 | 10 | add_library(libcommon STATIC ${SOURCES}) 11 | 12 | list(APPEND LIBS libcommon) 13 | list(APPEND LIBS pthread) 14 | 15 | add_executable(thread_example thread_example.cpp) 16 | target_link_libraries(thread_example PUBLIC ${LIBS}) 17 | 18 | add_executable(mem_pool_example mem_pool_example.cpp) 19 | target_link_libraries(mem_pool_example PUBLIC ${LIBS}) 20 | 21 | add_executable(lf_queue_example lf_queue_example.cpp) 22 | target_link_libraries(lf_queue_example PUBLIC ${LIBS}) 23 | 24 | add_executable(logging_example logging_example.cpp) 25 | target_link_libraries(logging_example PUBLIC ${LIBS}) 26 | 27 | add_executable(socket_example socket_example.cpp) 28 | target_link_libraries(socket_example PUBLIC ${LIBS}) 29 | -------------------------------------------------------------------------------- /Chapter9/common/lf_queue.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "macros.h" 8 | 9 | namespace Common { 10 | template 11 | class LFQueue final { 12 | public: 13 | LFQueue(std::size_t num_elems) : 14 | store_(num_elems, T()) /* pre-allocation of vector storage. */ { 15 | } 16 | 17 | auto getNextToWriteTo() noexcept { 18 | return &store_[next_write_index_]; 19 | } 20 | 21 | auto updateWriteIndex() noexcept { 22 | next_write_index_ = (next_write_index_ + 1) % store_.size(); 23 | num_elements_++; 24 | } 25 | 26 | auto getNextToRead() const noexcept -> const T * { 27 | return (size() ? &store_[next_read_index_] : nullptr); 28 | } 29 | 30 | auto updateReadIndex() noexcept { 31 | next_read_index_ = (next_read_index_ + 1) % store_.size(); 32 | ASSERT(num_elements_ != 0, "Read an invalid element in:" + std::to_string(pthread_self())); 33 | num_elements_--; 34 | } 35 | 36 | auto size() const noexcept { 37 | return num_elements_.load(); 38 | } 39 | 40 | // Deleted default, copy & move constructors and assignment-operators. 41 | LFQueue() = delete; 42 | 43 | LFQueue(const LFQueue &) = delete; 44 | 45 | LFQueue(const LFQueue &&) = delete; 46 | 47 | LFQueue &operator=(const LFQueue &) = delete; 48 | 49 | LFQueue &operator=(const LFQueue &&) = delete; 50 | 51 | private: 52 | std::vector store_; 53 | 54 | std::atomic next_write_index_ = {0}; 55 | std::atomic next_read_index_ = {0}; 56 | 57 | std::atomic num_elements_ = {0}; 58 | }; 59 | } 60 | -------------------------------------------------------------------------------- /Chapter9/common/lf_queue_example.cpp: -------------------------------------------------------------------------------- 1 | #include "thread_utils.h" 2 | #include "lf_queue.h" 3 | 4 | struct MyStruct { 5 | int d_[3]; 6 | }; 7 | 8 | using namespace Common; 9 | 10 | auto consumeFunction(LFQueue* lfq) { 11 | using namespace std::literals::chrono_literals; 12 | std::this_thread::sleep_for(5s); 13 | 14 | while(lfq->size()) { 15 | const auto d = lfq->getNextToRead(); 16 | lfq->updateReadIndex(); 17 | 18 | std::cout << "consumeFunction read elem:" << d->d_[0] << "," << d->d_[1] << "," << d->d_[2] << " lfq-size:" << lfq->size() << std::endl; 19 | 20 | std::this_thread::sleep_for(1s); 21 | } 22 | 23 | std::cout << "consumeFunction exiting." << std::endl; 24 | } 25 | 26 | int main(int, char **) { 27 | LFQueue lfq(20); 28 | 29 | auto ct = createAndStartThread(-1, "", consumeFunction, &lfq); 30 | 31 | for(auto i = 0; i < 50; ++i) { 32 | const MyStruct d{i, i * 10, i * 100}; 33 | *(lfq.getNextToWriteTo()) = d; 34 | lfq.updateWriteIndex(); 35 | 36 | std::cout << "main constructed elem:" << d.d_[0] << "," << d.d_[1] << "," << d.d_[2] << " lfq-size:" << lfq.size() << std::endl; 37 | 38 | using namespace std::literals::chrono_literals; 39 | std::this_thread::sleep_for(1s); 40 | } 41 | 42 | ct->join(); 43 | 44 | std::cout << "main exiting." << std::endl; 45 | 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /Chapter9/common/logging_example.cpp: -------------------------------------------------------------------------------- 1 | #include "logging.h" 2 | 3 | int main(int, char **) { 4 | using namespace Common; 5 | 6 | char c = 'd'; 7 | int i = 3; 8 | unsigned long ul = 65; 9 | float f = 3.4; 10 | double d = 34.56; 11 | const char* s = "test C-string"; 12 | std::string ss = "test string"; 13 | 14 | Logger logger("logging_example.log"); 15 | 16 | logger.log("Logging a char:% an int:% and an unsigned:%\n", c, i, ul); 17 | logger.log("Logging a float:% and a double:%\n", f, d); 18 | logger.log("Logging a C-string:'%'\n", s); 19 | logger.log("Logging a string:'%'\n", ss); 20 | 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /Chapter9/common/macros.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #define LIKELY(x) __builtin_expect(!!(x), 1) 7 | #define UNLIKELY(x) __builtin_expect(!!(x), 0) 8 | 9 | inline auto ASSERT(bool cond, const std::string &msg) noexcept { 10 | if (UNLIKELY(!cond)) { 11 | std::cerr << "ASSERT : " << msg << std::endl; 12 | 13 | exit(EXIT_FAILURE); 14 | } 15 | } 16 | 17 | inline auto FATAL(const std::string &msg) noexcept { 18 | std::cerr << "FATAL : " << msg << std::endl; 19 | 20 | exit(EXIT_FAILURE); 21 | } 22 | -------------------------------------------------------------------------------- /Chapter9/common/mcast_socket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "socket_utils.h" 6 | 7 | #include "logging.h" 8 | 9 | namespace Common { 10 | /// Size of send and receive buffers in bytes. 11 | constexpr size_t McastBufferSize = 64 * 1024 * 1024; 12 | 13 | struct McastSocket { 14 | McastSocket(Logger &logger) 15 | : logger_(logger) { 16 | outbound_data_.resize(McastBufferSize); 17 | inbound_data_.resize(McastBufferSize); 18 | } 19 | 20 | /// Initialize multicast socket to read from or publish to a stream. 21 | /// Does not join the multicast stream yet. 22 | auto init(const std::string &ip, const std::string &iface, int port, bool is_listening) -> int; 23 | 24 | /// Add / Join membership / subscription to a multicast stream. 25 | auto join(const std::string &ip) -> bool; 26 | 27 | /// Remove / Leave membership / subscription to a multicast stream. 28 | auto leave(const std::string &ip, int port) -> void; 29 | 30 | /// Publish outgoing data and read incoming data. 31 | auto sendAndRecv() noexcept -> bool; 32 | 33 | /// Copy data to send buffers - does not send them out yet. 34 | auto send(const void *data, size_t len) noexcept -> void; 35 | 36 | int socket_fd_ = -1; 37 | 38 | /// Send and receive buffers, typically only one or the other is needed, not both. 39 | std::vector outbound_data_; 40 | size_t next_send_valid_index_ = 0; 41 | std::vector inbound_data_; 42 | size_t next_rcv_valid_index_ = 0; 43 | 44 | /// Function wrapper for the method to call when data is read. 45 | std::function recv_callback_ = nullptr; 46 | 47 | std::string time_str_; 48 | Logger &logger_; 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /Chapter9/common/mem_pool_example.cpp: -------------------------------------------------------------------------------- 1 | #include "mem_pool.h" 2 | 3 | struct MyStruct { 4 | int d_[3]; 5 | }; 6 | 7 | int main(int, char **) { 8 | using namespace Common; 9 | 10 | MemPool prim_pool(50); 11 | MemPool struct_pool(50); 12 | 13 | for(auto i = 0; i < 50; ++i) { 14 | auto p_ret = prim_pool.allocate(i); 15 | auto s_ret = struct_pool.allocate(MyStruct{i, i+1, i+2}); 16 | 17 | std::cout << "prim elem:" << *p_ret << " allocated at:" << p_ret << std::endl; 18 | std::cout << "struct elem:" << s_ret->d_[0] << "," << s_ret->d_[1] << "," << s_ret->d_[2] << " allocated at:" << s_ret << std::endl; 19 | 20 | if(i % 5 == 0) { 21 | std::cout << "deallocating prim elem:" << *p_ret << " from:" << p_ret << std::endl; 22 | std::cout << "deallocating struct elem:" << s_ret->d_[0] << "," << s_ret->d_[1] << "," << s_ret->d_[2] << " from:" << s_ret << std::endl; 23 | 24 | prim_pool.deallocate(p_ret); 25 | struct_pool.deallocate(s_ret); 26 | } 27 | } 28 | 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /Chapter9/common/tcp_server.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "tcp_socket.h" 4 | 5 | namespace Common { 6 | struct TCPServer { 7 | explicit TCPServer(Logger &logger) 8 | : listener_socket_(logger), logger_(logger) { 9 | } 10 | 11 | /// Start listening for connections on the provided interface and port. 12 | auto listen(const std::string &iface, int port) -> void; 13 | 14 | /// Check for new connections or dead connections and update containers that track the sockets. 15 | auto poll() noexcept -> void; 16 | 17 | /// Publish outgoing data from the send buffer and read incoming data from the receive buffer. 18 | auto sendAndRecv() noexcept -> void; 19 | 20 | private: 21 | /// Add and remove socket file descriptors to and from the EPOLL list. 22 | auto addToEpollList(TCPSocket *socket); 23 | 24 | public: 25 | /// Socket on which this server is listening for new connections on. 26 | int epoll_fd_ = -1; 27 | TCPSocket listener_socket_; 28 | 29 | epoll_event events_[1024]; 30 | 31 | /// Collection of all sockets, sockets for incoming data, sockets for outgoing data and dead connections. 32 | std::vector receive_sockets_, send_sockets_; 33 | 34 | /// Function wrapper to call back when data is available. 35 | std::function recv_callback_ = nullptr; 36 | /// Function wrapper to call back when all data across all TCPSockets has been read and dispatched this round. 37 | std::function recv_finished_callback_ = nullptr; 38 | 39 | std::string time_str_; 40 | Logger &logger_; 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /Chapter9/common/thread_example.cpp: -------------------------------------------------------------------------------- 1 | #include "thread_utils.h" 2 | 3 | auto dummyFunction(int a, int b, bool sleep) { 4 | std::cout << "dummyFunction(" << a << "," << b << ")" << std::endl; 5 | std::cout << "dummyFunction output=" << a + b << std::endl; 6 | 7 | if(sleep) { 8 | std::cout << "dummyFunction sleeping..." << std::endl; 9 | 10 | using namespace std::literals::chrono_literals; 11 | std::this_thread::sleep_for(5s); 12 | } 13 | 14 | std::cout << "dummyFunction done." << std::endl; 15 | } 16 | 17 | int main(int, char **) { 18 | using namespace Common; 19 | 20 | auto t1 = createAndStartThread(-1, "dummyFunction1", dummyFunction, 12, 21, false); 21 | auto t2 = createAndStartThread(1, "dummyFunction2", dummyFunction, 15, 51, true); 22 | 23 | std::cout << "main waiting for threads to be done." << std::endl; 24 | t1->join(); 25 | t2->join(); 26 | std::cout << "main exiting." << std::endl; 27 | 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /Chapter9/common/thread_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace Common { 11 | /// Set affinity for current thread to be pinned to the provided core_id. 12 | inline auto setThreadCore(int core_id) noexcept { 13 | cpu_set_t cpuset; 14 | 15 | CPU_ZERO(&cpuset); 16 | CPU_SET(core_id, &cpuset); 17 | 18 | return (pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset) == 0); 19 | } 20 | 21 | /// Creates a thread instance, sets affinity on it, assigns it a name and 22 | /// passes the function to be run on that thread as well as the arguments to the function. 23 | template 24 | inline auto createAndStartThread(int core_id, const std::string &name, T &&func, A &&... args) noexcept { 25 | auto t = new std::thread([&]() { 26 | if (core_id >= 0 && !setThreadCore(core_id)) { 27 | std::cerr << "Failed to set core affinity for " << name << " " << pthread_self() << " to " << core_id << std::endl; 28 | exit(EXIT_FAILURE); 29 | } 30 | std::cerr << "Set core affinity for " << name << " " << pthread_self() << " to " << core_id << std::endl; 31 | 32 | std::forward(func)((std::forward(args))...); 33 | }); 34 | 35 | using namespace std::literals::chrono_literals; 36 | std::this_thread::sleep_for(1s); 37 | 38 | return t; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Chapter9/common/time_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Common { 8 | typedef int64_t Nanos; 9 | 10 | constexpr Nanos NANOS_TO_MICROS = 1000; 11 | constexpr Nanos MICROS_TO_MILLIS = 1000; 12 | constexpr Nanos MILLIS_TO_SECS = 1000; 13 | constexpr Nanos NANOS_TO_MILLIS = NANOS_TO_MICROS * MICROS_TO_MILLIS; 14 | constexpr Nanos NANOS_TO_SECS = NANOS_TO_MILLIS * MILLIS_TO_SECS; 15 | 16 | inline auto getCurrentNanos() noexcept { 17 | return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); 18 | } 19 | 20 | inline auto& getCurrentTimeStr(std::string* time_str) { 21 | const auto time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); 22 | time_str->assign(ctime(&time)); 23 | if(!time_str->empty()) 24 | time_str->at(time_str->length()-1) = '\0'; 25 | return *time_str; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Chapter9/exchange/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | set(CMAKE_CXX_COMPILER g++) 3 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 4 | set(CMAKE_VERBOSE_MAKEFILE on) 5 | 6 | file(GLOB SOURCES "*/*.cpp") 7 | 8 | include_directories(${PROJECT_SOURCE_DIR}) 9 | include_directories(${PROJECT_SOURCE_DIR}/exchange) 10 | 11 | add_library(libexchange STATIC ${SOURCES}) 12 | -------------------------------------------------------------------------------- /Chapter9/exchange/market_data/snapshot_synthesizer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "common/types.h" 4 | #include "common/thread_utils.h" 5 | #include "common/lf_queue.h" 6 | #include "common/macros.h" 7 | #include "common/mcast_socket.h" 8 | #include "common/mem_pool.h" 9 | #include "common/logging.h" 10 | 11 | #include "market_data/market_update.h" 12 | #include "matcher/me_order.h" 13 | 14 | using namespace Common; 15 | 16 | namespace Exchange { 17 | class SnapshotSynthesizer { 18 | public: 19 | SnapshotSynthesizer(MDPMarketUpdateLFQueue *market_updates, const std::string &iface, 20 | const std::string &snapshot_ip, int snapshot_port); 21 | 22 | ~SnapshotSynthesizer(); 23 | 24 | auto start() -> void; 25 | 26 | auto stop() -> void; 27 | 28 | auto addToSnapshot(const MDPMarketUpdate *market_update); 29 | 30 | auto publishSnapshot(); 31 | 32 | auto run() -> void; 33 | 34 | // Deleted default, copy & move constructors and assignment-operators. 35 | SnapshotSynthesizer() = delete; 36 | 37 | SnapshotSynthesizer(const SnapshotSynthesizer &) = delete; 38 | 39 | SnapshotSynthesizer(const SnapshotSynthesizer &&) = delete; 40 | 41 | SnapshotSynthesizer &operator=(const SnapshotSynthesizer &) = delete; 42 | 43 | SnapshotSynthesizer &operator=(const SnapshotSynthesizer &&) = delete; 44 | 45 | private: 46 | MDPMarketUpdateLFQueue *snapshot_md_updates_ = nullptr; 47 | 48 | Logger logger_; 49 | 50 | volatile bool run_ = false; 51 | 52 | std::string time_str_; 53 | 54 | McastSocket snapshot_socket_; 55 | 56 | std::array, ME_MAX_TICKERS> ticker_orders_; 57 | size_t last_inc_seq_num_ = 0; 58 | Nanos last_snapshot_time_ = 0; 59 | 60 | MemPool order_pool_; 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /Chapter9/exchange/matcher/matching_engine.cpp: -------------------------------------------------------------------------------- 1 | #include "matching_engine.h" 2 | 3 | namespace Exchange { 4 | MatchingEngine::MatchingEngine(ClientRequestLFQueue *client_requests, ClientResponseLFQueue *client_responses, 5 | MEMarketUpdateLFQueue *market_updates) 6 | : incoming_requests_(client_requests), outgoing_ogw_responses_(client_responses), outgoing_md_updates_(market_updates), 7 | logger_("exchange_matching_engine.log") { 8 | for(size_t i = 0; i < ticker_order_book_.size(); ++i) { 9 | ticker_order_book_[i] = new MEOrderBook(i, &logger_, this); 10 | } 11 | } 12 | 13 | MatchingEngine::~MatchingEngine() { 14 | stop(); 15 | 16 | using namespace std::literals::chrono_literals; 17 | std::this_thread::sleep_for(1s); 18 | 19 | incoming_requests_ = nullptr; 20 | outgoing_ogw_responses_ = nullptr; 21 | outgoing_md_updates_ = nullptr; 22 | 23 | for(auto& order_book : ticker_order_book_) { 24 | delete order_book; 25 | order_book = nullptr; 26 | } 27 | } 28 | 29 | auto MatchingEngine::start() -> void { 30 | run_ = true; 31 | ASSERT(Common::createAndStartThread(-1, "Exchange/MatchingEngine", [this]() { run(); }) != nullptr, "Failed to start MatchingEngine thread."); 32 | } 33 | 34 | auto MatchingEngine::stop() -> void { 35 | run_ = false; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Chapter9/exchange/matcher/me_order.cpp: -------------------------------------------------------------------------------- 1 | #include "me_order.h" 2 | 3 | namespace Exchange { 4 | auto MEOrder::toString() const -> std::string { 5 | std::stringstream ss; 6 | ss << "MEOrder" << "[" 7 | << "ticker:" << tickerIdToString(ticker_id_) << " " 8 | << "cid:" << clientIdToString(client_id_) << " " 9 | << "oid:" << orderIdToString(client_order_id_) << " " 10 | << "moid:" << orderIdToString(market_order_id_) << " " 11 | << "side:" << sideToString(side_) << " " 12 | << "price:" << priceToString(price_) << " " 13 | << "qty:" << qtyToString(qty_) << " " 14 | << "prio:" << priorityToString(priority_) << " " 15 | << "prev:" << orderIdToString(prev_order_ ? prev_order_->market_order_id_ : OrderId_INVALID) << " " 16 | << "next:" << orderIdToString(next_order_ ? next_order_->market_order_id_ : OrderId_INVALID) << "]"; 17 | 18 | return ss.str(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter9/exchange/order_server/order_server.cpp: -------------------------------------------------------------------------------- 1 | #include "order_server.h" 2 | 3 | namespace Exchange { 4 | OrderServer::OrderServer(ClientRequestLFQueue *client_requests, ClientResponseLFQueue *client_responses, const std::string &iface, int port) 5 | : iface_(iface), port_(port), outgoing_responses_(client_responses), logger_("exchange_order_server.log"), 6 | tcp_server_(logger_), fifo_sequencer_(client_requests, &logger_) { 7 | cid_next_outgoing_seq_num_.fill(1); 8 | cid_next_exp_seq_num_.fill(1); 9 | cid_tcp_socket_.fill(nullptr); 10 | 11 | tcp_server_.recv_callback_ = [this](auto socket, auto rx_time) { recvCallback(socket, rx_time); }; 12 | tcp_server_.recv_finished_callback_ = [this]() { recvFinishedCallback(); }; 13 | } 14 | 15 | OrderServer::~OrderServer() { 16 | stop(); 17 | 18 | using namespace std::literals::chrono_literals; 19 | std::this_thread::sleep_for(1s); 20 | } 21 | 22 | auto OrderServer::start() -> void { 23 | run_ = true; 24 | tcp_server_.listen(iface_, port_); 25 | 26 | ASSERT(Common::createAndStartThread(-1, "Exchange/OrderServer", [this]() { run(); }) != nullptr, "Failed to start OrderServer thread."); 27 | } 28 | 29 | auto OrderServer::stop() -> void { 30 | run_ = false; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Chapter9/scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # TODO - point these to the correct binary locations on your system. 4 | CMAKE=$(which cmake) 5 | NINJA=$(which ninja) 6 | 7 | mkdir -p ./cmake-build-release 8 | $CMAKE -DCMAKE_BUILD_TYPE=Release -DCMAKE_MAKE_PROGRAM=$NINJA -G Ninja -S . -B ./cmake-build-release 9 | 10 | $CMAKE --build ./cmake-build-release --target clean -j 4 11 | $CMAKE --build ./cmake-build-release --target all -j 4 12 | 13 | mkdir -p ./cmake-build-debug 14 | $CMAKE -DCMAKE_BUILD_TYPE=Debug -DCMAKE_MAKE_PROGRAM=$NINJA -G Ninja -S . -B ./cmake-build-debug 15 | 16 | $CMAKE --build ./cmake-build-debug --target clean -j 4 17 | $CMAKE --build ./cmake-build-debug --target all -j 4 18 | -------------------------------------------------------------------------------- /Chapter9/scripts/no_clean_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # TODO - point these to the correct binary locations on your system. 4 | CMAKE=$(which cmake) 5 | NINJA=$(which ninja) 6 | 7 | mkdir -p ./cmake-build-release 8 | $CMAKE -DCMAKE_BUILD_TYPE=Release -DCMAKE_MAKE_PROGRAM=$NINJA -G Ninja -S . -B ./cmake-build-release 9 | 10 | $CMAKE --build ./cmake-build-release --target all -j 4 11 | 12 | mkdir -p ./cmake-build-debug 13 | $CMAKE -DCMAKE_BUILD_TYPE=Debug -DCMAKE_MAKE_PROGRAM=$NINJA -G Ninja -S . -B ./cmake-build-debug 14 | 15 | $CMAKE --build ./cmake-build-debug --target all -j 4 16 | -------------------------------------------------------------------------------- /Chapter9/trading/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_STANDARD 20) 2 | set(CMAKE_CXX_COMPILER g++) 3 | set(CMAKE_CXX_FLAGS "-std=c++2a -Wall -Wextra -Werror -Wpedantic") 4 | set(CMAKE_VERBOSE_MAKEFILE on) 5 | 6 | file(GLOB SOURCES "*/*.cpp") 7 | 8 | include_directories(${PROJECT_SOURCE_DIR}) 9 | include_directories(${PROJECT_SOURCE_DIR}/trading) 10 | 11 | add_library(libtrading STATIC ${SOURCES} strategy/risk_manager.cpp) 12 | -------------------------------------------------------------------------------- /Chapter9/trading/strategy/liquidity_taker.cpp: -------------------------------------------------------------------------------- 1 | #include "liquidity_taker.h" 2 | 3 | #include "trade_engine.h" 4 | 5 | namespace Trading { 6 | LiquidityTaker::LiquidityTaker(Common::Logger *logger, TradeEngine *trade_engine, const FeatureEngine *feature_engine, 7 | OrderManager *order_manager, 8 | const TradeEngineCfgHashMap &ticker_cfg) 9 | : feature_engine_(feature_engine), order_manager_(order_manager), logger_(logger), 10 | ticker_cfg_(ticker_cfg) { 11 | trade_engine->algoOnOrderBookUpdate_ = [this](auto ticker_id, auto price, auto side, auto book) { 12 | onOrderBookUpdate(ticker_id, price, side, book); 13 | }; 14 | trade_engine->algoOnTradeUpdate_ = [this](auto market_update, auto book) { onTradeUpdate(market_update, book); }; 15 | trade_engine->algoOnOrderUpdate_ = [this](auto client_response) { onOrderUpdate(client_response); }; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter9/trading/strategy/market_maker.cpp: -------------------------------------------------------------------------------- 1 | #include "market_maker.h" 2 | 3 | #include "trade_engine.h" 4 | 5 | namespace Trading { 6 | MarketMaker::MarketMaker(Common::Logger *logger, TradeEngine *trade_engine, const FeatureEngine *feature_engine, 7 | OrderManager *order_manager, const TradeEngineCfgHashMap &ticker_cfg) 8 | : feature_engine_(feature_engine), order_manager_(order_manager), logger_(logger), 9 | ticker_cfg_(ticker_cfg) { 10 | trade_engine->algoOnOrderBookUpdate_ = [this](auto ticker_id, auto price, auto side, auto book) { 11 | onOrderBookUpdate(ticker_id, price, side, book); 12 | }; 13 | trade_engine->algoOnTradeUpdate_ = [this](auto market_update, auto book) { onTradeUpdate(market_update, book); }; 14 | trade_engine->algoOnOrderUpdate_ = [this](auto client_response) { onOrderUpdate(client_response); }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Chapter9/trading/strategy/market_order.cpp: -------------------------------------------------------------------------------- 1 | #include "market_order.h" 2 | 3 | namespace Trading { 4 | auto MarketOrder::toString() const -> std::string { 5 | std::stringstream ss; 6 | ss << "MarketOrder" << "[" 7 | << "oid:" << orderIdToString(order_id_) << " " 8 | << "side:" << sideToString(side_) << " " 9 | << "price:" << priceToString(price_) << " " 10 | << "qty:" << qtyToString(qty_) << " " 11 | << "prio:" << priorityToString(priority_) << " " 12 | << "prev:" << orderIdToString(prev_order_ ? prev_order_->order_id_ : OrderId_INVALID) << " " 13 | << "next:" << orderIdToString(next_order_ ? next_order_->order_id_ : OrderId_INVALID) << "]"; 14 | 15 | return ss.str(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter9/trading/strategy/om_order.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "common/types.h" 6 | 7 | using namespace Common; 8 | 9 | namespace Trading { 10 | enum class OMOrderState : int8_t { 11 | INVALID = 0, 12 | PENDING_NEW = 1, 13 | LIVE = 2, 14 | PENDING_CANCEL = 3, 15 | DEAD = 4 16 | }; 17 | 18 | inline auto OMOrderStateToString(OMOrderState side) -> std::string { 19 | switch (side) { 20 | case OMOrderState::PENDING_NEW: 21 | return "PENDING_NEW"; 22 | case OMOrderState::LIVE: 23 | return "LIVE"; 24 | case OMOrderState::PENDING_CANCEL: 25 | return "PENDING_CANCEL"; 26 | case OMOrderState::DEAD: 27 | return "DEAD"; 28 | case OMOrderState::INVALID: 29 | return "INVALID"; 30 | } 31 | 32 | return "UNKNOWN"; 33 | } 34 | 35 | struct OMOrder { 36 | TickerId ticker_id_ = TickerId_INVALID; 37 | OrderId order_id_ = OrderId_INVALID; 38 | Side side_ = Side::INVALID; 39 | Price price_ = Price_INVALID; 40 | Qty qty_ = Qty_INVALID; 41 | OMOrderState order_state_ = OMOrderState::INVALID; 42 | 43 | auto toString() const { 44 | std::stringstream ss; 45 | ss << "OMOrder" << "[" 46 | << "tid:" << tickerIdToString(ticker_id_) << " " 47 | << "oid:" << orderIdToString(order_id_) << " " 48 | << "side:" << sideToString(side_) << " " 49 | << "price:" << priceToString(price_) << " " 50 | << "qty:" << qtyToString(qty_) << " " 51 | << "state:" << OMOrderStateToString(order_state_) << "]"; 52 | 53 | return ss.str(); 54 | } 55 | }; 56 | 57 | typedef std::array OMOrderSideHashMap; 58 | typedef std::array OMOrderTickerSideHashMap; 59 | } 60 | -------------------------------------------------------------------------------- /Chapter9/trading/strategy/order_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "order_manager.h" 2 | #include "trade_engine.h" 3 | 4 | namespace Trading { 5 | auto OrderManager::newOrder(OMOrder *order, TickerId ticker_id, Price price, Side side, Qty qty) noexcept -> void { 6 | const Exchange::MEClientRequest new_request{Exchange::ClientRequestType::NEW, trade_engine_->clientId(), ticker_id, 7 | next_order_id_, side, price, qty}; 8 | trade_engine_->sendClientRequest(&new_request); 9 | 10 | *order = {ticker_id, next_order_id_, side, price, qty, OMOrderState::PENDING_NEW}; 11 | ++next_order_id_; 12 | 13 | logger_->log("%:% %() % Sent new order % for %\n", __FILE__, __LINE__, __FUNCTION__, 14 | Common::getCurrentTimeStr(&time_str_), 15 | new_request.toString().c_str(), order->toString().c_str()); 16 | } 17 | 18 | auto OrderManager::cancelOrder(OMOrder *order) noexcept -> void { 19 | const Exchange::MEClientRequest cancel_request{Exchange::ClientRequestType::CANCEL, trade_engine_->clientId(), 20 | order->ticker_id_, order->order_id_, order->side_, order->price_, 21 | order->qty_}; 22 | trade_engine_->sendClientRequest(&cancel_request); 23 | 24 | order->order_state_ = OMOrderState::PENDING_CANCEL; 25 | 26 | logger_->log("%:% %() % Sent cancel % for %\n", __FILE__, __LINE__, __FUNCTION__, 27 | Common::getCurrentTimeStr(&time_str_), 28 | cancel_request.toString().c_str(), order->toString().c_str()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Chapter9/trading/strategy/risk_manager.cpp: -------------------------------------------------------------------------------- 1 | #include "risk_manager.h" 2 | 3 | #include "order_manager.h" 4 | 5 | namespace Trading { 6 | RiskManager::RiskManager(Common::Logger *logger, const PositionKeeper *position_keeper, const TradeEngineCfgHashMap &ticker_cfg) 7 | : logger_(logger) { 8 | for (TickerId i = 0; i < ME_MAX_TICKERS; ++i) { 9 | ticker_risk_.at(i).position_info_ = position_keeper->getPositionInfo(i); 10 | ticker_risk_.at(i).risk_cfg_ = ticker_cfg[i].risk_cfg_; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | --------------------------------------------------------------------------------