/concurrent-order-matching-engine

A concurrent order matching engine in C++11 using FIX protocol for Linux/Windows

Primary LanguageC++Creative Commons Zero v1.0 UniversalCC0-1.0

Licence badge

Build status

1. Introduction:

A multithreaded order matching engine written in C++11 using FIX protocol. For limit orders and matching engines , see https://github.com/akhin/cpp_multithreaded_order_matching_engine/blob/master/documentation/README_Orders_MatchingEngines.md

For FIX protocol , see https://en.wikipedia.org/wiki/Financial_Information_eXchange

Features can be seen in the table below :

Feature Details
Order entry FIX 4.2, offline mode with files with FIX messages
Order types Limit
Order message types NewOrder, Cancel
Exec report types Accepted, Filled, PartiallyFilled, Rejected, Canceled
TIF Not supported
Symbology No validation, examples use RIC codes

Technical implementation details are as below :

Feature Details
OS Windows ( tested on 8.1), Linux ( tested on Ubuntu and CentOS)
C++ C++11
C++ Compiler Support GCC4.8 and MSVC120 (VS2013)
C++ Libraries STD, STL, QuickFix
C++ Platform APIs GNU LibC, POSIX, some POSIX NP ,WinAPI, MS CRT
IDE projects provided Netbeans for Linux, VS2013 for Windows

Watch server when working :

Client test automation when working :

The engine currently is using :

  • A thread class which you can set stack size, set OS priority and set names for debugging
  • A thread pool with ability to pin threads to CPU cores and avoid hyperthreading
  • 1 lock free SPSC ring buffer
  • Other fine grained lock based ring buffer and queues
  • Actor pattern
  • Also the engine currently makes use of a set of CPU cache aligned allocators for memory allocations in order to avoid false sharing : https://github.com/akhin/cpp_multithreaded_order_matching_engine/tree/master/source/core/memory

For detailed low level implementation details please see https://github.com/akhin/cpp_multithreaded_order_matching_engine/blob/master/documentation/README_MultithreadingImplementationDetails.md

The core of order matching layer is called a central order book, which keeps order books per security symbol. Each order book has a table for bids and another table for asks.

  1. The FIX engine will listen for session requests from the clients, and if a session is established then it listens for incoming ask/bid orders. If the order type is not supported, it sends “rejected” message. Otherwise the message will be submitted to an incoming message dispatcher which is a fine grained MPSC unbounded queue. ( The main reason being for MPSC here is the FIX engine being used is multithreaded and the worker queues in the thread pool are SPSC by design ) The incoming message dispatcher will submit messages to the central order book.

  2. Central Order book has a thread pool :

    • The thread pool will have N lockfree SPSC queues for N threads ( N = num of symbol ). The thread pool also has the ability to pin threads to CPU cores. And based on configuration it can also avoid hyperthreading/logical processors by pinning threads only to CPU cores with even indexes.

    • Central Order book also has N lockfree SPSC queues for outgoing messages.

    • When a new message arrives ( new order, or cancel ) from the incoming message dispatcher, it will be submitted to corresponding thread`s queue in the thread pool of the central order book.

  3. Each thread in the thread pool will get message from their SPSC queue in the thread pool , and add them to corresponding order queue which is used by only itself and eventually trigger the order matching process for that queue. At the end of the order matching , worker threads will submit messages ( FILLED or PARTIALLY FILLED ) to the corresponding outgoing messages queues.

  4. Outgoing message processor will process messages from lockfree SPSC queues of central order book and send responses back to the clients.

For Linux , the project is built and tested with GCC4.8 only on CentOS7.

As for Windows it is using MSVC1200(VS2013). An important note about VS2013 , its version shouldn`t be later then Update2 as the project is using C++11 curly brace initialisation in MILs and MSVC rollbacked that feature starting from Update3 :

https://connect.microsoft.com/VisualStudio/feedbackdetail/view/938122/list-initialization-inside-member-initializer-list-or-non-static-data-member-initializer-is-not-implemented

In the libraries side :

For Windows, you need MSVC120 runtime. You can find them in the same directory with executables : bin

For Linux, you need GNU Libstd C++ 6 runtime and QuickFIX runtime.

How to install Quickfix runtime on Linux ( tested on Ubuntu ) :

    1. Navigate to dependencies/quickfix/linux_runtime
    2. sudo chmod +x ./install_quickfix_runtime.sh
    3. sudo ./install_quickfix_runtime.sh

Note : This script will copy shared object to library path, create soft links, will add library path to /etc/ld.so.conf and finally execute ldconfig.

How to build the project on Linux :

cd build/linux_gcc
make clean
make debug  OR make release
or if you want to make a parallel build :
./build_in_parallel.sh release

How to build the project on Linux using Netbeans 8.0.2 C++ IDE:

Open Netbeans.
Open the project from the project directory. ( Choose "nbproject" directory )
Build the project inside Netbeans IDE.

Why Netbeans : In Netbeans, it is too straightforward to setup remote debugging, therefore it is quite convenient to build and debug on Linux from Windows via SSH and Samba. You can see an article about this setup here in my blog. It is for Debian but it should be trivial to apply it to any other distribution : https://nativecoding.wordpress.com/2014/10/24/configuring-a-debian-virtual-machine-for-linux-c-development-via-windows-step-by-step/

How to build the project on Windows with Visual Studio :

You can build with Visual Studio 2013
Go to "build/windows_msvc_visual_studio" directory
Use SLN file to launch VS with the project

How to build the project on Windows with Visual Studio in command line :

You can build with Visual Studio 2013
Go to "build/windows_msvc_command_line" directory
Execute one of batch files : build_debug.bat or build_release.bat

The engine executable looks for "ome.ini" file. Here is the list of things you can set :

Ini setting Functionality
LOGGER_BUFFER_SIZE Maximum buffer size for the logging ring buffer
LOGGER_WRITE_PERIOD_MILLISECONDS Logging period in milliseconds
LOGGER_MEMORY_MAPPED_FILE Sets log file
LOGGER_ROTATION_SIZE_IN_BYTES Log rotation size in bytes
LOGGER_LOG_LEVEL Log level, supported values : ERROR WARNING INFO
LOGGER_COPY_TO_STDOUT If enabled all logs will be printed in console
SINGLE_INSTANCE_TCP_PORT Port used in single instance check , applies to Linux only
PROCESS_PRIORITY Sets OS level priority of the main process
CENTRAL_ORDER_BOOK_MULTITHREADED_ORDER_MATCHING Toggles multithreading for order matching
CENTRAL_ORDER_BOOK_PIN_THREADS_TO_CORES Whether to pin threads of the threadpool to CPU cores
CENTRAL_ORDER_BOOK_HYPER_THREADING If hyperthreading is off it will use cores with an even index
CENTRAL_ORDER_BOOK_WORK_QUEUE_SIZE_PER_THREAD Queue size per worker thread in the thread pool
CENTRAL_ORDER_BOOK_THREAD_PRIORITY OS-level priority of thread pool threads
CENTRAL_ORDER_BOOK_THREAD_STACK_SIZE Stack size for thread pool threads
OUTGOING_MESSAGE_QUEUE_SIZE_PER_THREAD Queue size per worker thread for outgoing messages
OFFLINE_ORDER_ENTRY_FILE Offline order entry mode see below for details

You will also need to specify security symbols. The order matching engine`s thread pool will create a worker thread for each symbol. For specifying symbols in ini file, you need to use brackets as below :

    SYMBOL[]=MSFT
    SYMBOL[]=AAPL
    SYMBOL[]=INTC
    SYMBOL[]=GOOGL

You will also need to have "quickfix_FIX42.xml" and "quickfix_server.cfg" files to be in the same directory with OME executable. You can find them in "bin" directory.

Once you start the ome executable , initially you will see a screen like this :

    06-02-2016 16:22:00 : INFO , Main thread , starting
    06-02-2016 16:22:00 : INFO , Thread pool , Thread(0) MSFT starting
    06-02-2016 16:22:00 : INFO , Thread pool , Thread(1) AAPL starting
    06-02-2016 16:22:00 : INFO , Thread pool , Thread(2) INTC starting
    06-02-2016 16:22:00 : INFO , Thread pool , Thread(3) GOOGL starting
    06-02-2016 16:22:00 : INFO , Thread pool , Thread(4) QCOM starting
    06-02-2016 16:22:00 : INFO , Thread pool , Thread(5) QQQ starting
    06-02-2016 16:22:00 : INFO , Thread pool , Thread(6) BBRY starting
    06-02-2016 16:22:00 : INFO , Thread pool , Thread(7) SIRI starting
    06-02-2016 16:22:00 : INFO , Thread pool , Thread(8) ZNGA starting
    06-02-2016 16:22:00 : INFO , Thread pool , Thread(9) ARCP starting
    06-02-2016 16:22:00 : INFO , Thread pool , Thread(10) XIV starting
    06-02-2016 16:22:00 : INFO , Thread pool , Thread(11) FOXA starting
    06-02-2016 16:22:00 : INFO , Thread pool , Thread(12) TVIX starting
    06-02-2016 16:22:00 : INFO , Thread pool , Thread(13) YHOO starting
    06-02-2016 16:22:00 : INFO , Thread pool , Thread(14) HBAN starting
    06-02-2016 16:22:00 : INFO , Thread pool , Thread(15) BARC starting
    06-02-2016 16:22:00 : INFO , Incoming message dispatcher , Thread starting
    06-02-2016 16:22:00 : INFO , Outgoing message processor , Thread starting
    06-02-2016 16:22:00 : INFO , FIX Engine , Acceptor started

    Available commands :

            display : Shows all order books in the central order book
            quit : Shutdowns the server

The default mode is FIX server mode. However, if you specify an order file in ome.ini as below :

				OFFLINE_ORDER_ENTRY_FILE=orders.txt

Then the order matcher will process all the orders in that file bypassing FIX protocol and produce offline_order_entry_results.txt as result file. For an example offline order file see :

https://github.com/akhin/multithreaded_order_matching_engine/blob/master/bin/sample_offline_order_file.txt

The output of offline order matching will have timestamps with microsecond precision and FIX execution report messages as below :

	16-09-2017 03:12:27.739729
	8=FIX.4.2�9=68�35=8�6=0�11=�14=0�17=1�20=0�37=�38=2�39=0�54=2�55=GOOGL�150=0�151=2�10=001�
	16-09-2017 03:12:27.755355
	8=FIX.4.2�9=68�35=8�6=0�11=�14=0�17=2�20=0�37=�38=1�39=0�54=1�55=GOOGL�150=0�151=1�10=255�
	16-09-2017 03:12:27.773486
	8=FIX.4.2�9=78�35=8�6=1�11=�14=1�17=3�20=0�31=1�32=1�37=�38=1�39=2�54=1�55=GOOGL�150=2�151=0�10=173�
	16-09-2017 03:12:27.777489

The engine produces log messages below when it receives 1 buy order with quantity 1 and 1 sell order with quantity 1 for the same symbol :

06-02-2016 20:16:09 : INFO , FIX Engine , New logon , session ID : FIX.4.2:OME->TEST_CLIENT1
06-02-2016 20:16:09 : INFO , FIX Engine , Sending fix message : 8=FIX.4.2�9=154�35=8�34=5�43=Y�49=OME�52=20160206-20:16:09.295�56=TEST_CLIENT1�122=20160206-20:15:03.955�6=0�11=4�14=0�17=18�20=0�37=4�38=1�39=0�54=1�55=MSFT�150=0�151=1�10=000�
06-02-2016 20:16:09 : INFO , FIX Engine , Sending fix message : 8=FIX.4.2�9=155�35=8�34=6�43=Y�49=OME�52=20160206-20:16:09.295�56=TEST_CLIENT1�122=20160206-20:15:03.955�6=0�11=5�14=0�17=19�20=0�37=5�38=2�39=0�54=2�55=GOOGL�150=0�151=2�10=070�
06-02-2016 20:16:09 : INFO , FIX Engine , Sending fix message : 8=FIX.4.2�9=155�35=8�34=7�43=Y�49=OME�52=20160206-20:16:09.295�56=TEST_CLIENT1�122=20160206-20:15:03.971�6=0�11=6�14=0�17=20�20=0�37=6�38=1�39=0�54=1�55=GOOGL�150=0�151=1�10=060�
06-02-2016 20:16:09 : INFO , FIX Engine , Sending fix message : 8=FIX.4.2�9=164�35=8�34=8�43=Y�49=OME�52=20160206-20:16:09.295�56=TEST_CLIENT1�122=20160206-20:15:03.971�6=1�11=4�14=1�17=21�20=0�31=1�32=1�37=4�38=1�39=2�54=1�55=MSFT�150=2�151=0�10=168�
06-02-2016 20:16:09 : INFO , FIX Engine , Sending fix message : 8=FIX.4.2�9=164�35=8�34=9�43=Y�49=OME�52=20160206-20:16:09.295�56=TEST_CLIENT1�122=20160206-20:15:03.971�6=1�11=3�14=1�17=22�20=0�31=1�32=1�37=3�38=1�39=2�54=2�55=MSFT�150=2�151=0�10=169�
06-02-2016 20:16:09 : INFO , FIX Engine , Sending fix message : 8=FIX.4.2�9=156�35=8�34=10�43=Y�49=OME�52=20160206-20:16:09.295�56=TEST_CLIENT1�122=20160206-20:15:03.971�6=0�11=7�14=0�17=23�20=0�37=7�38=1�39=0�54=1�55=GOOGL�150=0�151=1�10=108�
06-02-2016 20:16:09 : INFO , FIX Engine , Sending fix message : 8=FIX.4.2�9=154�35=8�34=11�43=Y�49=OME�52=20160206-20:16:09.295�56=TEST_CLIENT1�122=20160206-20:15:03.971�6=0�11=8�14=0�17=24�20=0�37=8�38=1�39=8�54=1�55=xxx�150=8�151=1�10=110�
06-02-2016 20:16:09 : INFO , FIX Engine , Sending fix message : 8=FIX.4.2�9=166�35=8�34=12�43=Y�49=OME�52=20160206-20:16:09.295�56=TEST_CLIENT1�122=20160206-20:15:03.971�6=1�11=6�14=1�17=25�20=0�31=1�32=1�37=6�38=1�39=2�54=1�55=GOOGL�150=2�151=0�10=027�
06-02-2016 20:16:09 : INFO , FIX Engine , Sending fix message : 8=FIX.4.2�9=166�35=8�34=13�43=Y�49=OME�52=20160206-20:16:09.295�56=TEST_CLIENT1�122=20160206-20:15:03.971�6=1�11=5�14=1�17=26�20=0�31=1�32=1�37=5�38=2�39=1�54=2�55=GOOGL�150=1�151=1�10=028�
06-02-2016 20:16:09 : INFO , FIX Engine , Sending fix message : 8=FIX.4.2�9=166�35=8�34=14�43=Y�49=OME�52=20160206-20:16:09.295�56=TEST_CLIENT1�122=20160206-20:15:03.971�6=1�11=7�14=1�17=27�20=0�31=1�32=1�37=7�38=1�39=2�54=1�55=GOOGL�150=2�151=0�10=033�
06-02-2016 20:16:09 : INFO , FIX Engine , Sending fix message : 8=FIX.4.2�9=166�35=8�34=15�43=Y�49=OME�52=20160206-20:16:09.295�56=TEST_CLIENT1�122=20160206-20:15:03.971�6=1�11=5�14=2�17=28�20=0�31=1�32=1�37=5�38=2�39=2�54=2�55=GOOGL�150=2�151=0�10=034�
06-02-2016 20:16:09 : INFO , FIX Engine , Receiving fix message : 8=FIX.4.2�9=123�35=D�34=15�49=TEST_CLIENT1�52=20160206-20:16:09.342�56=OME�11=1�21=1�38=1�40=2�44=1�54=2�55=MSFT�59=0�60=20160206-20:16:09�10=124�
06-02-2016 20:16:09 : INFO , FIX Engine , New order message received :8=FIX.4.2�9=123�35=D�34=15�49=TEST_CLIENT1�52=20160206-20:16:09.342�56=OME�11=1�21=1�38=1�40=2�44=1�54=2�55=MSFT�59=0�60=20160206-20:16:09�10=124�
06-02-2016 20:16:09 : INFO , Central Order Book , New order accepted, client TEST_CLIENT1, client order ID 1 
06-02-2016 20:16:09 : INFO , Outgoing message processor , Processing ACCEPTED for order : Client TEST_CLIENT1 Client ID 1 Symbol MSFT Side SELL 
06-02-2016 20:16:09 : INFO , Thread pool , MSFT thread got a new task to execute
06-02-2016 20:16:09 : INFO , FIX Engine , Sending fix message : 8=FIX.4.2�9=125�35=8�34=23�49=OME�52=20160206-20:16:09.342�56=TEST_CLIENT1�6=0�11=1�14=0�17=113�20=0�37=1�38=1�39=0�54=2�55=MSFT�150=0�151=1�10=079�
06-02-2016 20:16:09 : INFO , Central Order Book , Order processing for symbol MSFT took 0000000 milliseconds , num of processed orders : 0
06-02-2016 20:16:09 : INFO , FIX Engine , Receiving fix message : 8=FIX.4.2�9=123�35=D�34=16�49=TEST_CLIENT1�52=20160206-20:16:09.342�56=OME�11=2�21=1�38=1�40=2�44=1�54=1�55=MSFT�59=0�60=20160206-20:16:09�10=125�
06-02-2016 20:16:09 : INFO , FIX Engine , New order message received :8=FIX.4.2�9=123�35=D�34=16�49=TEST_CLIENT1�52=20160206-20:16:09.342�56=OME�11=2�21=1�38=1�40=2�44=1�54=1�55=MSFT�59=0�60=20160206-20:16:09�10=125�
06-02-2016 20:16:09 : INFO , Central Order Book , New order accepted, client TEST_CLIENT1, client order ID 2 
06-02-2016 20:16:09 : INFO , Outgoing message processor , Processing ACCEPTED for order : Client TEST_CLIENT1 Client ID 2 Symbol MSFT Side BUY 
06-02-2016 20:16:09 : INFO , Thread pool , MSFT thread got a new task to execute
06-02-2016 20:16:09 : INFO , FIX Engine , Sending fix message : 8=FIX.4.2�9=125�35=8�34=24�49=OME�52=20160206-20:16:09.342�56=TEST_CLIENT1�6=0�11=2�14=0�17=114�20=0�37=2�38=1�39=0�54=1�55=MSFT�150=0�151=1�10=082�
06-02-2016 20:16:09 : INFO , Central Order Book , Order processing for symbol MSFT took 0000000 milliseconds , num of processed orders : 2
06-02-2016 20:16:09 : INFO , Outgoing message processor , Processing FILLED for order : Client TEST_CLIENT1 Client ID 2 Symbol MSFT Side BUY 
06-02-2016 20:16:09 : INFO , FIX Engine , Sending fix message : 8=FIX.4.2�9=135�35=8�34=25�49=OME�52=20160206-20:16:09.342�56=TEST_CLIENT1�6=1�11=2�14=1�17=115�20=0�31=1�32=1�37=2�38=1�39=2�54=1�55=MSFT�150=2�151=0�10=001�
06-02-2016 20:16:09 : INFO , Outgoing message processor , Processing FILLED for order : Client TEST_CLIENT1 Client ID 1 Symbol MSFT Side SELL 
06-02-2016 20:16:09 : INFO , FIX Engine , Sending fix message : 8=FIX.4.2�9=135�35=8�34=26�49=OME�52=20160206-20:16:09.342�56=TEST_CLIENT1�6=1�11=1�14=1�17=116�20=0�31=1�32=1�37=1�38=1�39=2�54=2�55=MSFT�150=2�151=0�10=002�
06-02-2016 20:16:11 : INFO , FIX Engine , Logout , session ID : FIX.4.2:OME->TEST_CLIENT1

Under "test_functional" directory :

For Linux, the project has a Python script which can send orders from FIX files using Fix protocol.

  1. Modify test_cases.txt which has the orders to send to the engine as FIX messages.

  2. To run it : python ./fix_client_automation.py

  3. You can set automation parameters in the commandline :

For Windows there is a Powershell script for Windows which can send orders from FIX files using Fix protocol. It internally uses C# to implement FIX protocol.

  1. Modify test_cases.txt which has the orders to send to the engine as FIX messages.

  2. For firing Windows test client(s), you can use fix_client_automation.bat.

  3. A GUI will appear. Simply specify the arguments.

  4. Press the start button.

The project uses GoogleTest 1.7. You can find a makefile and vcproj under "test_unit" directory.

Building and running unit test on Linux : You can either use Makefile or Netbeans project files under "test_unit" directory.

Building and running unit test on Windows : You can use VisualStudio solution in "test_unit" directory.

You can find them under "utility_scripts" directory :

Script Functionality
source_code_formatter.sh converts EOL to Unix, converts tabs to 4 spaces, removes trailing white space
leak_detector_valgrind.sh memory leak detection with Valgrind
build_with_thread_sanitizer.sh build with GCC thread sanitizer
build_with_address_sanitizer.sh build with GCC address sanitizer
build_for_gprof.sh build for gprof profiling
profile_with_callgrind.sh profile with Valgrind callgrind
profiler_windows_visual_studio profile with Visual Studio`s vsperf
leak_detector_drmemory.bat memory leak detection with Dr.Memory
tcpdump_capture.sh gets a tcpdump capture that you can view with Wireshark
valgrind_hellgrind.sh runs Valgrind concurrency analysis
valgrind_cachegrind.sh runs Valgrind CPU cache usage analysis

Viewing tcpdump captures with Wireshark :

Source code and file/directory naming conventions :

Category Naming convention
Directory names lower_case_word
File names lower_case_word
Include guards HEADER_H
Macros UPPER_CASE_WORD
Enums UPPER_CASE_WORD
Namespace names lower_case_word
Class names CamelCase
Method names pascalCase
Variable names pascalCase
Member variables starts with m_
Directories/Namespaces As in Boost, there is a namespace per directory

Source code indentations and new line usage :

- 4 spaces, no tabs ( This needs to be setup in VS )
- By default Netbeans editor uses spaces for tabs
- Needs to be set in VS2013 : https://msdn.microsoft.com/en-gb/library/ms165330(v=vs.90).aspx
- New lines : Unix CR only ( \n ) , VisualStudio can handle it even though Windows is \r\n

utility/source_code_formatter.sh : It is a Bash script that scans all cpp,h,hpp files in project directory and converts Windows end of lines to Linux, converts tabs to 4 spaces and removes trailing whitespace. It requires dos2unix.

Inclusions : Using forward slash as it works for both Linux and Windows :

		#include <core/concurrency/thread.h>

Warning level used for GCC : -Wall

Warning level used for MSVC : /W3

Precompiled header file usage : On Windows , the project is using /FI ( Force include parameter, therefore no need to include the pch header everywhere ) and specified the pch header to be precompiled_header.h. Note that this breaks edit-and-continue in Visual Studio. Also precompiled header setting of all projects is "use" whereas precompiled_header.cpp is "create". For Linux , there is pch rule to enable it in the makefile ( build/linux/Makefile) , but currently that rule is not being used since it doesn`t appear as it is doing much improvement as on Windows.

For GCC see https://gcc.gnu.org/onlinedocs/gcc/Precompiled-Headers.html

For MSVC 120 see https://msdn.microsoft.com/en-us/library/8c5ztk84(v=vs.120).aspx

MSVC120 C++11 Limitations : Curly brace initialisation at MILs and noexcept is not supported. For noexcept usage please see compiler_portability/noexcept.h .

For the time being , online CI has been setup for only MSVC using Appveyor. Planning to add GCC based online CI with TravisCI.