Repository: objectcomputing/liquibook Branch: master Commit: 2427613b32f1 Files: 88 Total size: 605.2 KB Directory structure: gitextract_uq787oa9/ ├── .gitignore ├── PERFORMANCE.md ├── README.md ├── README_ORDER_ENTRY.md ├── doc/ │ └── settAug2013/ │ ├── liquibook_sett.html │ ├── settAug2013_files/ │ │ └── paper.css │ └── styles/ │ └── SETT.css ├── env.sh ├── examples/ │ ├── .gitignore │ └── mt_order_entry/ │ ├── Market.cpp │ ├── Market.h │ ├── Order.cpp │ ├── Order.h │ ├── OrderFwd.h │ ├── TestOneAonBidTwoAsk.script │ ├── Util.cpp │ ├── Util.h │ ├── mt_order_entry.mpc │ ├── mt_order_entry_main.cpp │ ├── order_entry.script │ └── teststoporders.script ├── license.txt ├── liquibook.features ├── liquibook.mwc ├── mpc/ │ ├── liquibook.mpb │ ├── liquibook_book.mpb │ ├── liquibook_exe.mpb │ ├── liquibook_lib.mpb │ ├── liquibook_simple.mpb │ └── liquibook_test.mpb ├── mpc.bat ├── noQuickFAST/ │ └── QuickFASTApplication.mpb ├── pt_run.bat ├── src/ │ ├── book/ │ │ ├── bbo_listener.h │ │ ├── callback.h │ │ ├── comparable_price.h │ │ ├── depth.h │ │ ├── depth_constants.h │ │ ├── depth_level.h │ │ ├── depth_listener.h │ │ ├── depth_order_book.h │ │ ├── liquibook.mpc │ │ ├── logger.h │ │ ├── main.cpp │ │ ├── order.h │ │ ├── order_book.h │ │ ├── order_book_listener.h │ │ ├── order_listener.h │ │ ├── order_tracker.h │ │ ├── trade_listener.h │ │ ├── types.h │ │ └── version.h │ ├── liquibook_export.h │ └── simple/ │ ├── liquibook_simple.mpc │ ├── simple_order.cpp │ ├── simple_order.h │ └── simple_order_book.h ├── test/ │ ├── .gitignore │ ├── latency/ │ │ ├── clock_gettime.h │ │ ├── liquibook_latency.mpc │ │ └── lt_order_book.cpp │ ├── perf/ │ │ ├── liquibook_perf.mpc │ │ └── pt_order_book.cpp │ └── unit/ │ ├── changed_checker.h │ ├── depth_check.h │ ├── liquibook_unit.mpc │ ├── ut_all_or_none.cpp │ ├── ut_bbo_order_book.cpp │ ├── ut_depth.cpp │ ├── ut_immediate_or_cancel.cpp │ ├── ut_listeners.cpp │ ├── ut_main.cpp │ ├── ut_market_price.cpp │ ├── ut_order_book.cpp │ ├── ut_order_book_shared_ptr.cpp │ ├── ut_stop_orders.cpp │ └── ut_utils.h ├── ut_run.bat ├── web/ │ ├── css/ │ │ └── liquibook.css │ ├── easy.html │ ├── fast.html │ ├── flexible.html │ ├── fluid.html │ ├── get-started.html │ ├── index.html │ └── sub-template.html ├── winenv.bat └── winenv_clear.bat ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Compiled Object files *.slo *.lo *.o *.obj *.iobj # Compiled Dynamic libraries *.so *.dylib *.dll # Compiled Static libraries *.lai *.la *.a *.lib lib/ # Compiled executables *.exe /bin /Output Makefile* .depend* *.sln make # Artifacts *.swp #visual studio work files *vcproj* *vcxproj* Debug Release *.pdb *.ipdb *.ilk .vs/ *.VC.* # test output files *.log #helper/customized commands # (use single letter names and don't add to repo) ?.cmd ?.bat #a place to hide documents that should not go into the repo: doc/NotGitWorthy/ ================================================ FILE: PERFORMANCE.md ================================================ Performance Test Results, Inserts Per Second ============================================ (newest results on top)
5 Level Depth BBO Only Order Book Only Note
2,062,158 2,139,950 2,494,532 Now testing on a modern laptop (2.4 GHZ i7).
1,231,959 1,273,510 1,506,066 Handling all or none order condition.
1,249,544 1,305,482 1,531,998 Remove callbacks_added method. Caller can invoke equivalent if necessary.
1,222,000 1,279,711 1,495,714 Use vector for callback container.
1,250,616 1,264,227 1,463,738 Union in callback. For clarity of purpose, not for performance.
1,267,135 1,270,188 1,469,246 Combine 2 fill callbacks into one.
1,233,894 1,237,154 1,434,354 Store excess depth levels in depth to speed repopulation.
58,936 153,839 1,500,874 Removed spuroious insert on accept of completely filled order.
38,878 124,756 1,495,744 Initial run with all 3 tests.
================================================ FILE: README.md ================================================ # Liquibook Open source order matching engine Liquibook provides the low-level components that make up an order matching engine. Order matching is the process of accepting buy and sell orders for a security (or other fungible asset) and matching them to allow trading between parties who are otherwise unknown to each other. An order matching engine is the heart of every financial exchange, and may be used in many other circumstances including trading non-financial assets, serving as a test-bed for trading algorithms, etc. A typical Liquibook-based application might look something like this: ![Market Application](doc/Images/MarketApplication.png) In addition to the order matching process itself, Liquibook can be configured to maintain an "depth book" that records the number of open orders and total quantity represented by those orders at individual price levels. #### Example of an depth book * Symbol XYZ: * Buy Side: * $53.20 per share: 1203 orders; 150,398 shares. * $53.19 per share: 87 orders; 63,28 shares * $52.00 per share 3 orders; 2,150 shares * Sell Side * $54.00 per share 507 orders; 120,700 shares * etc... ## Order properties supported by Liquibook. Liquibook is aware of the following order properties. * Side: Buy or Sell * Quantity * Symbol to represent the asset to be traded * Liquibook imposes no restrictions on the symbol. It is treated as a simple character string. * Desired price or "Market" to accept the current price defined by the market. * Trades will be generated at the specified price or any better price (higher price for sell orders, lower price for buy orders) * Stop loss price to hold the order until the market price reaches the specified value. * This is often referred to as simply a stop price. * All or None flag to specify that the entire order should be filled or no trades should happen. * Immediate or Cancel flag to specify that after all trades that can be made against existing orders on the market have been made, the remainder of the order should be canceled. * Note combining All or None and Immediate or Cancel produces an order commonly described as Fill or Kill. The only required properties are side, quantity and price. Default values are available for the other properties. The application can define addtional properties on the order object as necessary. These properties will have no impact on the behavior of Liquibook. ## Operations on Orders In addition to submitting orders, traders may also submit requests to cancel or modify existing orders. (Modify is also know as cancel/replace) The requests may succeed or fail depending on previous trades executed against the order. ## Notifications returned to the application. Liquibook will notify the application when significant events occur to allow the application to actually execute the trades identified by Liquibook, and to allow the application to publish market data for use by traders. The notifications generated include: * Notifications intended for trader submitting an order: * Order accepted * Order rejected * Order filled (full or partial) * Order replaced * Replace request rejected * Order canceled * Cancel request rejected. * Notifications intended to be published as Market Data * Trade * Note this should also trigger the application to do what it needs to do to make the trade happen. * Security changed * Triggered by any event that effects a security * Does not include rejected requests. * Notification of changes in the depth book (if enabled) * Depth book changed * Best Bid or Best Offer (BBO) changed. ## Performance * Liquibook is written in C++ using modern, high-performance techniques. This repository includes the source of a test program that can be used to measure Liquibook performance. * Benchmark testing with this program shows sustained rates of __2.0 million__ to __2.5 million__ inserts per second. As always, the results of this type of performance test can vary depending on the hardware and operating system on which you run the test, so use these numbers as a rough order-of-magnitude estimate of the type of performance your application can expect from Liquibook. ## Works with Your Design * Allows an application to use smart or regular pointers to orders. * Compatible with existing order model, * Requires a trivial interface which can be added to or wrapped around an existing Order object. * Compatible with existing identifiers for securities, accounts, exchanges, orders, fills ## Example This repository contains two complete example programs. These programs can be used to evaluate Liquibook to see if it meets your needs. They can also be used as models for your application or even incorporated directly into your application thanks to the liberal license under which Liquibook is distributed. The examples are: * Depth feed publisher and subscriber * Generates orders that are submitted to Liquibook and publishes the resulting market data. * Uses [QuickFAST](https://github.com/objectcomputing/quickfast) to publish the market data * Manual Order Entry * Allows orders and other requests to be read from the console or submitted by a script (text file) * Submits these to Liquibook. * Displays the notifications received from Liquibook to the console or to a log file. * [Detailed instructions are in the README_ORDER_ENTRY.md file.]( README_ORDER_ENTRY.md) # Building Liquibook The good news is you don't need to build Liquibook. The core of Liquibook is a header-only library, so you can simply add Liquibook/src to your include path then `#include ` to your source, and Liquibook will be available to be used in your application. However, this repository includes tests and example programs for Liquibook. These programs need to be compiled and built in order to run them. The remainder of this section describes how to do this. ## Dependencies Liquibook has no runtime dependencies. It will run in any environment that can run C++ programs. To build the Liquibook test and example programs from source you need to create makefiles (for linux, et al.) or Project and Solution files for Windows Visual Studio. Liquibook uses MPC to create these platform-dependent files from a common build definition: * [MPC](https://github.com/objectcomputing/MPC) for cross-platform builds. MPC itself is written in perl, so your environment needs a working Perl compiler. Most linux systems already have this. If you need a Perl compiler on Windows, OCI recommends [Active State Perl V5.x or later](http://www.activestate.com/) If you wish to build the unit tests for Liquibook, you will also need the boost test library: * [BOOST](http://www.boost.org/) (optional) for unit testing. One of the example programs (publish and subscribe to market data) uses QuickFAST to encode and decode market data messages. If you wish to run this example you need QuickFAST: * [QuickFAST](https://github.com/objectcomputing/quickfast) (optional) for building the example depth feed publisher/subscriber. QuickFAST his its own dependencies which are described on its web page. ## Submodule Note The Assertive test framework was used in previous versions, but it is no longer needed. If you have imported this submodule to support previous versions, you may delete the liquibook/test/unit/assertiv directory. ## Getting ready to build the tests and example programs. ### Boost Test If you want to run the Liquibook unit tests (highly recommended!) you should install and/or build boost test before trying to build Liquibook. Boost test is used in the multifile-test mode rather than simple header-only mode so the compiled boost test library must be available. Please follow the instructions on the [boost web site](http://www.boost.org/) for building/installing the library in your environment. When you are done you should export the $BOOST_ROOT environment varialble. Because of the many boost build options, please check to be sure that the include files and library files are in the expected locations. MPC expects to find: * Include files in $BOOST_ROOT/include/boost * Library files in $BOOST_ROOT/lib If you prefer not to install boost you can edit the liquibook.features file to change the appropriate line to say `boost=0` This will disable building the unit tests. ### QuickFAST The publish and subscribe example program uses QuickFAST. If you want to run this example program, please see the [QuickFAST web site](https://github.com/objectcomputing/quickfast) to download and build this library. Set the environment variable $QUICKFAST_ROOT to point to the location where you installed and build QuickFAST. Before running MPC you should also edit the file liquibook.features to set the value QuickFAST=1 If you do not plan to run this example program, set the environment variable QUICKFAST_ROOT to liquibook/noQuickFAST. ## Building Liquibook on Linux The env.sh script uses the readlink program which is present on most Linux/Unix systems. If you don't have readlink, set the $LIQUIBOOK_ROOT environment variable the directory containing liquibook before running env.sh Open a shell and type:
$ cd liquibook
$ . ./env.sh
$ $MPC_ROOT/mwc.pl -type make liquibook.mwc
$ make depend
$ make all
### Output from build * The Liquibook test and example libraries will be in $LIQUIBOOK_ROOT/lib * The Liquibook example programs will be in $LIQUIBOOK_ROOT/bin * The Liquibook test programs will be in $LIQUIBOOK_ROOT/bin/test ## Building Liquibook examples and test programs with Visual Studio Use the following commands to set up the build environment and create Visual Studio project and solution files. Note if you are using MinGW or other linux-on-Windows techniques, follow the Linux instructions; however, OCI does not normally test this.
> cd liquibook
> copy winenv.bat w.bat #optional if you want to keep the original
                        # note that single character batch file names are ignored in 
                        # .getignore so the customized
                        # file will not be checked into the git repository (a good thing.)
> edit w.bat            # edit is your choice of text editor
                        # follow the instructions in the file itself.
> w.bat                 # sets and verifies environment variables
> mpc.bat               # generate the visual studio solution and project files.
Then: * Start Visual Studio from the command line by typing liquibook.sln or * Start Visual Studio from the Windows menu and use the menu File|Open|Project or Solution to load liquibook.sln. ## For any platform Liquibook should work on any platform with a modern C++ compiler (supporting at least C++11.) The MPC program used to create the build files and the Boost library used in the tests and some of the examples support a wide variety of platforms. See the [MPC documentation](https://github.com/objectcomputing/MPC) for details about using MPC in your enviornment. See the [Boost website](http://www.boost.org/) for details about using Boost in your environment. ================================================ FILE: README_ORDER_ENTRY.md ================================================ # Manual Order Entry -- Liquibook example program This program accepts requests typed from the console or read from a script file and applies them to one or more order books. It displays the results of the callbacks generated by Liquibook in response to those requests. ## The Command Line The mt_order_entry command accepts two command line options. Both are optional. Parameter: * script_file_name The name of a script file. * The script file contains a series of requests -- one per line. See below for the syntax of these requests. * If you don't want a script file, but you want to specify a log file, use a single hyphen (-) for the script file name. The commands will be read from the console. * log_file_name The name of a file to which output should be written. * Prompts (if any) will still be written to the console. ## Request syntax Requests are read from the console or from a script file. An empty line or a line beginning with a hash (#) is considered a comment and will be ignored. Other lines read are in the format: REQUEST [parameters]* [;] That is: a one word request followed by parameters as necessary and terminated in a semicolon. The semicolon may be optional for some commands, but it can always be included if desired. The stand-alone word "END" may be substituted for a semicolon. If a request accepts optional parameters, or if not all required parameters are provided, the console user will be prompted for additional parameters until a semicolon is entered. This prompting will happen even if the input is being read from a file, so the requests coming in from a script file should be complete and should be terminated with a semicolon. ####For example:
   > BUY 100 GOOG 850 AON;
       ADDING order:  [#2 BUY 100 GOOG $850 AON Last Event:{Submitted BUY 100 GOOG @850}]
   > BUY              (incomplete request)
   Quantity: 100      (prompt for parameters)
   Symbol: GOOG
   Limit Price or MKT: 800
   AON, or IOC, or STOP, or END: ;
       ADDING order:  [#3 BUY 100 GOOG $800 Last Event:{Submitted BUY 100 GOOG @800}]  
## Requests Most requests can be spelled out or abbreviated to their first character. Parameters accepted by each request will be explained later in this document. Requests are not case sensitive. Even though they are shown here in ALL CAPS a BUY request may be entered as BUY, buy, b, or even Buy.
* BUY or B      : Enter a new order to buy a security.
* SELL or S     : Enter a new order to sell a security.
* CANCEL or C   : Request that an existing order be canceled.
* MODIFY or M   : Request that an existing order be modified.
* DISPLAY or D  : Display information about existing orders
* FILE or F     : Open or close a script file.
* ?             : Display help for requests
* QUIT          : Exit the program.
## Symbols Liquibook itself imposes no limitations on symbol. It accepts an arbitrary string of characters (actually of 8 bit octets so UTF-8 or other encodings are supported with no special effort.) The manual entry program has a few restrictions on the symbols to ease parsing: * The symbol may not include spaces. * The symbol may not begin with a plus sign (+) or exclamation mark(!) * The symbol should not be ALL. * The symbol should not be a single asterisk (*) It is important to stress that these restrictions apply only to the example program, not to Liquibook itself. Disallowing spaces obviously makes parsing the incoming request easier. Using "ALL" and "*" would confict with the DISPLAY command parsing. The restriction against leading plus signs and exclamation marks requires more explanation. Liquibook uses a separate order book for each security. When a new symbol is encountered (representing a new security) the manual entry program will create a new order book for the symbol. It needs to know whether to create a simple order book, or a depth book. * If the new symbol is prefixed by a plus sign(+), then a simple order book is created. * If it is prefixed by an exclamation mark (!) then a depth book is created. * If neither of these prefixes is present then manual entry will prompt the console user to determine what type of book to create. For example:
    > buy 100 ibm 50;
    New Symbol IBM.
    Add [S]imple book, or [D]epth book, or 'N' to cancel request.
    [SDN}: d
    Create new depth order book for IBM
    ADDING order:  [#1 BUY 100 IBM $50 Last Event:{Submitted BUY 100 IBM @50}]
            Accepted: [#1 BUY 100 IBM $50 Open: 100 Last Event:{Accepted }]
            Book Change:  IBM
            Depth Change:  IBM Changed Change Id: 1 Published: 0
            BIDS:
            Price 50 Count: 1 Quantity: 100 Change id#: 1
            BBO Change:  IBM Changed Change Id: 1 Published: 0
    > buy 200 t 78;
    New Symbol T.
    Add [S]imple book, or [D]epth book, or 'N' to cancel request.
    [SDN}: s
    Create new order book for T
    ADDING order:  [#2 BUY 200 T $78 Last Event:{Submitted BUY 200 T @78}]
            Accepted: [#2 BUY 200 T $78 Open: 200 Last Event:{Accepted }]
            Book Change:  T
    > buy 50 r 99;
    New Symbol R.
    Add [S]imple book, or [D]epth book, or 'N' to cancel request.
    [SDN}: n
    Request ignored
    Cannot process command BUY 50 R 99 ;
Since console prompting does not play well with scripting, it is important that symbols used in scripts have the appropriate prefix the first time they appear. It is acceptable to have a + or ! prefix on a symbol for which a book already exists. The prefix will be ignored and the existing book will be used. ## Prices Liquibook uses integers for prices. It does care what currency unit is actually being used as long as all orders for a particular security use the same units. Because Liquibook uses integers the prices must be expressed in terms of the "atomic currency unit" For example if prices are in US Dollars, then the atomic currency unit is often a penny, so fifteen dollars would be expressed to Liquibook as 1500. In most cases where a price is needed, the word MARKET (or the abbreviation MKT) is accepted indicating that no limit price is specified for this order, and a trade can be generated at the price specified for the counter order, or at the current market price for the security if both sides of the trade specify MARKET price. ## Order Identity Liquibook uses the memory address of the applications order object as an identifier for the order. [This may be changed in the future because it imposes some odd restrictions on object lifetime.] The manual entry program needs a way for the console user (or script) to identify orders, and the memory address is obviously not available for this purpose. Thus the order entry program assigns an order id to each order. Whenever an order is displayed the order ID appears after a hash sign(#). So for example if a new order is added, and the response is: ADDING order: [#1 BUY 100 IBM $50 Last Event:{Submitted BUY 100 IBM @50}] This order may be referred to in following commands by using the order id 1. When an order is read from the console or from a script, an optional # can prefix the order id. Thus: "Cancel #1" and "CANCEL 1" refer to the same order. Order ID's may also be entered with a leading minus sign. These order IDs are relative to the NEXT order id that will be used. Thus "Cancel -1" would cancel the most recent order entered. "Cancel -2" would cancel the order before that, and so on. ## Details of specific Requests ### BUY or B Enter a new order to buy a security. Syntax: BUY quantity symbol price [AON | IOC | STOP price]* ; Parameters: * *quantity* is number of shares (or other tradable units) to buy. * *symbol* is an arbitrary character string. * See the **Symbol** section above for information about this string. * *price* is the highest price the trader is willing to pay for each tradable unit of the security, or MARKET to accept any trade. * See the **Price** section above for details about expressing prices. * AON This optional parameter sets the All-Or-None condition for this order. * IOC This optional parameter sets the Immediate-Or-Cancel condition for this order. * STOP price This optional parameter sets a STOP LOSS price for this order using the same currency units as the price parameter. The traling semicolon (or the word END) is required for a BUY request. Zero or more trades may be triggered by entering a BUY order. ### SELL or S Enter a new order to sell a security. Syntax: SELL quantity symbol price [AON | IOC | STOP price]* ; Parameters: * *quantity* is number of shares (or other tradable units) to be sold. * *symbol* is an arbitrary character string. * See the **Symbol** section above for information about this string. * *price* is the lowest price the trader is willing to accept for each tradable unit of the security, or MARKET to accept any trade. * See the **Price** section above for details about expressing prices. * AON This optional parameter sets the All-Or-None condition for this order. * IOC This optional parameter sets the Immediate-Or-Cancel condition for this order. * STOP price This optional parameter sets a STOP LOSS price for this order using the same currency units as the price parameter. The traling semicolon (or the word END) is required for a SELL request. Zero or more trades may be triggered by entering a SELL order. ### CANCEL or C Request that an existing order be canceled. Parameter: * orderid, or #orderid, or -relative_order_id. * See the **Order Identity** section above for details. No trades will be triggered by entering a CANCEL request. The traling semicolon (or the word END) is optional for a CANCEL request. The request is considered complete after one required parameter. The request may be rejected if the order has already been filled. ### MODIFY or M Request that an existing order be modified. Parameters: * orderid, or #orderid, or -relative_order_id. * See the **Order Identity** section above for details. * PRICE new_price * See the **Price** section above for details about expressing prices. * Prices may be increased or decreased. * PRICE is optional. If it is not specified the existing price will be unchanged. * QUANTITY new_initial_quantity * Note this is NOT the new quantity to be open (that's a moving target). Nor is it a delta to quantity (although that's how Liquibook expresses it internally.) * QUANTITY is optional. If it is not specified the existing quantity will be unchanged. The traling semicolon (or the word END) is required for a MODIFY request. Zero or more trades may be triggered by entering a MODIFY request. The request may be rejected if the order has already been filled, or if no changes are requested. ### DISPLAY or D Display information about existing orders Parameters: * * this optional first parameter (a single asterisk(*)) requests a more verbose display. * order_id or symbol or ALL. * One of these must appear. * See the **Order Identity** section above for a description of the ways in which order ID can be expressed. * A symbol will display all orders related to a specific symbol. * The literal word ALL will display information about all known symbols. ### FILE or F Open or close a script file. Parameter: * If this command appears in a script file, no parameter is allowed. This is considered to be a request to close the script file. * filename If this command is entered from the console, the filename is the name of a script file from which future commands should com. Commands will be read from the script file until: * The "FILE" command appears in the script file, * End-of-file on the script file. * The "QUIT" command appears in the script file. If the script file was originally opened by a FILE command entered from the console, and the script exits because of- a FILE command or end-of-file, then control returs to the console. If the script file was opened from the command line (see the **The Command Line** section), or the QUIT command appears in the script file, then the program exits. ### HELP or ? Display help for requests Displays a brief form of the information in this document. ### QUIT Exit the program. ================================================ FILE: doc/settAug2013/liquibook_sett.html ================================================ SETT August 2013 - Building a Market Data Feed With Liquibook

Building a Market Data Feed with Liquibook

by
Jeff Schmitz, Principal Software Engineer
Object Computing, Inc. (OCI)

Introduction

The financial industry relies heavily on systems to disseminate and interpret market data. Both trading decisions and analytics are based on the current state of the market for a particular security. A typical trading exchange involves multiple parties, and in a conceptual view looks like this:

Figure 1: Conceptual View of Exchange

Building such an exchange is a complex undertaking, involving order taking, matching, serialization and compression to a feed, dissemination, deserialization, decompression, and interpretation. This paper will demonstrate how to build such a system using only open-source components, including Liquibook for limit order book matching, QuickFAST for compression and decompression, and Boost ASIO for distribution.

Liquibook Background

Liquibook is a C++ open source limit order book matching engine. It is a component library that you can integrate into your project for implementing matching algorithms. Liquibook includes components for an order book, aggregate depth tracking, BBO tracking, and a trade feed.

QuickFAST Background

The FAST protocol is a compression and encoding mechanism for streaming data. QuickFAST is an implementation of the FAST protocol in C++ for encoding and decoding message streams using the FAST protocol.

Boost ASIO Background

Boost ASIO is a cross-platform C++ library for asynchronous network programming.

Example System

The example project for this paper will take on a more advanced use case: simulating an exchange producing a 5-level depth feed - aggregated by price, and a trade feed. This example involves a number of steps:

Accept Orders

The example exchange generates orders, simulating the receipt of orders from traders. A true exchange takes orders from traders by means of an API - often via the FIX protocol. For the purposes of this paper, internally generated orders will suffice.

Maintain a Limit Order Book for Each Security

The example exchange must be ready to match unfilled orders against inbound orders. This is done by means of a limit order book. The limit order book keeps these orders sorted so that when multiple existing orders can match a new inbound order, the matching is performed in a sequence determined by established exchange rules. The example exchange uses Liquibook's DepthOrderBook class to maintain the limit order book.

Match Buy and Sell Orders

The example exchange must match new inbound orders against those in the limit order book. The exchange must detect a match, and fill the appropriate orders. Liquibook's order book classes naturally includes matching capability.

Update Trading Client Order Status

An exchange must always notify traders of changes in their order status. This includes accepting, filling, and rejecting orders, but also accepting or rejecting order cancellation and replacement requests. In the example exchange, there are no trading clients (the orders are randomly generated), so this step will be skipped. Liquibook does, however, provide an interface and make callbacks for changes in order status, which can easily be turned into notifications sent to trading clients.

Aggregate the Top 5 Price Levels into Depth

While a full order book feed is useful, some exchanges choose to send aggregated forms of data instead, known as depth. Rather than representing individual orders, depth models price levels as a whole. An exchange will also limit the number of levels distributed - top 5 and top 10 are common limits.

The example exchange goes through the extra step and complexity of building 5-level depth. Liquibook provides the DepthOrderBook class, which manages the order book and depth, and the DepthListener class for being notified of changes to the top few depth levels.

Determine Changes to Depth in Response to an Event

It is not enough to maintain the depth. In order to produce an incremental feed, the example exchange must also be able to determine which levels of the depth have changed. A change can happen to one or more levels in the depth, on both the buy and sell sides, in response to a single order event. Liquibook has an interface for interrogating the various levels of depth and finding out if it has changed since the last published change. This makes it trivial to build an incremental feed - sending only changed levels to feed clients.

Compress and Distributing Market Data in a Feed

In order to effectively trade, feed clients have a need to understand order flow in the exchange. Feeds in which clients are notified of all possible order detail are known as "quote" feeds or "full order book" feeds, while those which aggregate orders by price are called "depth" feeds. A "BBO" feed gives only the top-level quote for a security, and a "trade" feed reflects all trades, or matched orders. Liquibook has support for building all of these feeds.

The example exchange builds both a trade and depth feed, then compresses the trade and depth messages using QuickFAST and sends them to clients using Boost ASIO.

Handle the Feed in a Client Program

Finally, the example includes feed clients that connect to the feed, decode the updates to recreate the trade events and reproduce the market depth. The clients use QuickFAST for decoding, Boost ASIO to handle network I/O, and Liquibook to reproduce the depth.

Getting Started

If you intend to build this project, you will need to download and build:

  1. Boost version 1.53 or later
  2. Xerces version 3.1.1 or later
  3. QuickFAST version 1.5.0 or later
  4. Liquibook version 1.1.0 or later

The project is found in total within the directory examples/depth_feed_publisher of Liquibook.

The Example Exchange

The example exchange consists of two applications, a publisher and a subscriber. The figure below illustrates the flow of data in the example exchange.

Figure 2: Example Exchange Data Flow

Publisher Application

The publisher implements the exchange in the example project. The publisher generates orders and adds them to the proper order book, handles the trade and depth events through listeners, encodes these using QuickFAST, and sends them using Boost ASIO.

The Order Book

As an exchange, the publisher must implement an order book. Liquibook comes with an order book class - two actually: book::OrderBook for managing an order book only, and a derived class book::DepthOrderBook for adding depth aggregation to that order book. Since the publisher builds a depth feed, it uses book::DepthOrderBook. The relevant parts of the book::OrderBook class are:

book/order_book.h: Selected parts of OrderBook class declaration
namespace liquibook { namespace book {

template <class OrderPtr = Order*>
class OrderBook {
public:
  typedef OrderBook<OrderPtr > MyClass;
  typedef TradeListener<MyClass > TypedTradeListener;

  /// @brief set the trade listener
  void set_trade_listener(TypedTradeListener* listener);

  /// @brief add an order to book
  /// @param order the order to add
  /// @param conditions special conditions on the order
  /// @return true if the add resulted in a fill
  virtual bool add(const OrderPtr& order, OrderConditions conditions = 0);

  /// @brief perform all callbacks in the queue
  virtual void perform_callbacks();
};

} }

Note that the book::OrderBook class is a template class, allowing the user to define not only the order class used in the order book, but the style of pointer used. This can be a regular pointer or a smart pointer.

The first method of interest allows a setting of a listener for trades. This listener, shown later, gets notified when a trade occurs. The example exchange needs this notification to build a trade feed, and thus sets the trade listener. The book::OrderBook class also includes listeners for all order status updates, for providing updates back to the trade clients, and a listener for all changes to the order book, to build a full order book feed.

Next is a method to add an order to the order book. add() accepts an order pointer, and condition flags for special conditions, like an immediate or cancel order. Note that there are also methods (not used in the example project) to cancel an order and to replace an order.

Finally, there is a method to perform the callbacks on the order book. Liquibook gives the client code the responsibility to execute this method, so that it can be done in the calling thread, or a background thread. By default, this method calls perform_callback() for each callback in the queue. Naturally, perform_callback() can be overridden, but that is not necessary in the publisher. The default implementation of this method issues callbacks for to the trade listener, order listener, and order book listener, if present.

The derived book::DepthOrderBook class is simpler:

book/depth_order_book.h: DepthOrderBook class declaration
namespace liquibook { namespace book {

/// @brief Implementation of order book child class, that incorporates
///        aggregate depth tracking.  Overrides perform_callback() method to 
//         track depth aggregated by price.
template 
class DepthOrderBook : public OrderBook {
public:
  typedef Depth DepthTracker;
  typedef BboListenerTypedBboListener;
  typedef DepthListenerTypedDepthListener;
  typedef Callback DobCallback;

  /// @brief construct
  DepthOrderBook();

  /// @brief set the BBO listener
  void set_bbo_listener(TypedBboListener* bbo_listener);

  /// @brief set the depth listener
  void set_depth_listener(TypedDepthListener* depth_listener);

  /// @brief handle a single callback
  virtual void perform_callback(DobCallback& cb);

  // @brief access the depth tracker
  DepthTracker& depth();

  // @brief access the depth tracker
  const DepthTracker& depth() const;

private:
  DepthTracker depth_;
  TypedBboListener* bbo_listener_;
  TypedDepthListener* depth_listener_;
};

book::DepthOrderBook overrides perform_callback() to update its depth, which is accessible through the depth() accessor methods. In addition, book::DepthOrderBook adds two new listeners - a BBO listener, for tracking only changes to the best bid and best offer, and a depth listener, for tracking all depth changes.

To build the exchange, the publisher must create a book::DepthOrderBook, and it set the trade listener and depth listener. While handling these callbacks, the publisher will need to update the feed clients with trade and depth update messages.

Note that in some of the callbacks, a pointer to the order book is provided, while a pointer to the order is provided in others. This gives the publisher two opportunities to provide additional fields and logic through inheritance. The publisher takes advantage of this, providing access to the symbol of the order book's security:

example_order_book.h: ExampleOrderBook class declaration
namespace liquibook { namespace examples {

typedef boost::shared_ptr OrderPtr;

class ExampleOrderBook : public book::DepthOrderBook<OrderPtr> {
public:
  ExampleOrderBook(const std::string& symbol);
  const std::string& symbol() const;

private:
  std::string symbol_;
};

} } // End namespace

Here the reader will note that the ExampleOrderBook class specializes book::DepthOrderBook and binds a shared pointer of Order to its parent's template argument. The Order class is shown below. The trivial implementation of the ExampleOrderBook is omitted from this paper. The class diagram for order books looks like this:

Figure 3: Order Book Class Diagram

The Exchange

The ExampleOrderBook class manages the order book and depth for a single security. Since exchanges handle multiple securities, somewhere the publisher needs to create an order book for each security, and maintain a mapping from a symbol to its order book. The publisher does this in the Exchange class:

exchange.h: Exchange class declaration
namespace liquibook { namespace examples {

class Exchange {
public:
  Exchange(ExampleOrderBook::TypedDepthListener* depth_listener,
           ExampleOrderBook::TypedTradeListener* trade_listener);

  // Permanently add an order book to the exchange
  void add_order_book(const std::string& symbol);

  // Handle an incoming order
  void add_order(const std::string& symbol, OrderPtr& order);
private:
  typedef std::map<std::string, ExampleOrderBook> OrderBookMap;
  OrderBookMap order_books_;
  ExampleOrderBook::TypedDepthListener* depth_listener_;
  ExampleOrderBook::TypedTradeListener* trade_listener_;
};

} }

The Exchange class has only three methods: a constructor, a method to add an order book, and a method to handle an order. As expected, it maintains a map of order books. It also maintains pointers to the depth listener and trade listener. The constructor does exactly that, in order to add the listeners to future order books:

exchange.cpp: Exchange class implementation
namespace liquibook { namespace examples {

Exchange::Exchange(ExampleOrderBook::TypedDepthListener* depth_listener,
                   ExampleOrderBook::TypedTradeListener* trade_listener)
: depth_listener_(depth_listener),
  trade_listener_(trade_listener)
{
}

The add_order_book() method creates a new order book, sets the listeners on the order book, and adds a mapping from the given symbol to the order book:

exchange.cpp: Exchange class implementation, continued
void
Exchange::add_order_book(const std::string& sym)
{
  std::pair result;
  result = order_books_.insert(std::make_pair(sym, ExampleOrderBook(sym)));
  result.first->second.set_depth_listener(depth_listener_);
  result.first->second.set_trade_listener(trade_listener_);
}

Finally, the add_order() method finds the correct order book, adds the order to the order book, and then triggers callbacks by calling the perform_callbacks() method:

exchange.cpp: Exchange class implementation, continued
void
Exchange::add_order(const std::string& symbol, OrderPtr& order)
{
  OrderBookMap::iterator order_book = order_books_.find(symbol);
  if (order_book != order_books_.end()) {
    order_book->second.add(order);
    order_book->second.perform_callbacks();
  }
}

} } // End namespace

The call to perform_callbacks() will trigger any callbacks to the provided listeners. This way, a single thread is manipulating the order books. It is also possible to issue perform_callbacks() calls in a background thread, but the publisher does it this way for simplicity.

Order Class

Liquibook requires that the application define an order class to represent an order to match. Liquibook requires that it meet the interface defined in the class src/book/order.h:

book/order.h: Order class (Liquibook) declaration
namespace liquibook { namespace book {

class Order {
public:
  /// @brief is this a limit order?
  bool is_limit() const;

  /// @brief is this order a buy?
  virtual bool is_buy() const = 0;

  /// @brief get the price of this order, or 0 if a market order
  virtual Price price() const = 0;

  /// @brief get the quantity of this order
  virtual Quantity order_qty() const = 0;
};

} }

The publisher defines Order to implement the book::Order interface:

order.h: Order class (publisher) declaration
namespace liquibook { namespace examples {

class Order : public book::Order {
public:
  Order(bool buy,
        const double& price,
        book::Quantity qty);

  virtual bool is_buy() const;
  virtual book::Quantity order_qty() const;
  virtual book::Price price() const;

  static const uint8_t precision_;
private:
  bool is_buy_;
  double price_;
  book::Quantity qty_;
};

} }

Note that as a template class, Liquibook's book::OrderBook does not strictly require inheriting from the book::Order class, just that the interface be implemented.

The example's Order class implements book::Order, and adds a static field called precision_ for price conversion.

The Order constructor simply copies the arguments passed to it:

order.h: Order class implementation
namespace liquibook { namespace examples {

const uint8_t Order::precision_(100);

Order::Order(bool buy, const double& price, book::Quantity qty)
: is_buy_(buy),
  price_(price),
  qty_(qty)
{
}

Note the presence of the static initializer of precision_.

The is_buy() and order_qty() are simple accessors:

order.h: Order class implementation, continued
bool
Order::is_buy() const
{
  return is_buy_;
}

book::Quantity
Order::order_qty() const
{
  return qty_;
}

The final method is used to provide a price to the order book. For performance reasons, the Liquibook requires this to be in integer format, so Order converts the price by multiplying by the precision:

order.h: Order class implementation, continued
book::Price
Order::price() const
{
  return price_ * precision_;
}

There are two implications - first, that the publisher is limited to penny precision. This is suitable for purposes of the example, but may need to be increased for other use cases. The second implication is that all securities in the publisher have the same precision - since a static variable is used to set precision. Again, in other use cases, this may not be sufficient.

QuickFAST Templates

To transmit messages using the FAST protocol, one must first decide on the messages to transmit on the feed. This exchange will produce a trade feed and an incremental depth feed. A simplified view of a trade consists of a symbol, a quantity, and a trade price. The publisher alters this slightly by providing the trade cost (price times quantity), from which price per share can be derived.

QuickFAST uses XML files, called templates to describe the messages in the feed protocol. The example's templates can be found in the file examples/depth_feed_publisher/depth.xml. The trade message template looks like this:

<template name="Trade" id="1">
  <uInt16 name="MessageType" id="100">
    <constant value="22"/>
  </uInt16>
  <uInt32 name="SequenceNumber" id="200">
    <increment/>
  </uInt32>
  <uInt32 name="Timestamp" id="300">
    <copy/>
  </uInt32>
  <string name="Symbol" id="400">
    <copy/>
  </string>
  <uInt32 name="Quantity" id="604">
    <copy/>
  </uInt32>
  <uInt32 name="Cost" id="603">
    <copy/>
  </uInt32>
</template>

Note that the tag names within the <template> tag indicate the type of the field. The reader may be surprised to see a currency field (Cost) represented as an integer (uint32). This, however is consistent with Liquibook's internal storage of prices as as integer. Liquibook's design decision is thus carried forward to the feed protocol, requiring the clients to convert these prices back to their decimal format.

In addition to the expected trade fields, the publisher provides a message type, to indicate to the subscriber that the message is for a trade, a sequence number, so the subscriber can be confident it has received all messages, and processed them in the correct order, and a timestamp, so the subscriber can be confident the message has been received in a timely manner.

The depth update template is more complicated:

<template name="Depth" id="2">
  <uInt16 name="MessageType" id="100">
    <constant value="11"/>
  </uInt16>
  <uInt32 name="SequenceNumber" id="200">
    <increment/>
  </uInt32>
  <uInt32 name="Timestamp" id="300">
    <copy/>
  </uInt32>
  <string name="Symbol" id="400">
    <copy/>
  </string>
  <sequence name="Bids" id="500">
    <uInt8 name="LevelNum" id="501">
      <copy/>
    </uInt8>
    <uInt32 name="OrderCount" id="502">
      <copy/>
    </uInt32>
    <uInt32 name="Price" id="503">
      <copy/>
    </uInt32>
    <uInt32 name="AggregateQty" id="504">
      <copy/>
    </uInt32>
  </sequence>
  <sequence name="Asks" id="600">
    <uInt8 name="LevelNum" id="601">
      <copy/>
    </uInt8>
    <uInt32 name="OrderCount" id="602">
      <copy/>
    </uInt32>
    <uInt32 name="Price" id="603">
      <copy/>
    </uInt32>
    <uInt32 name="AggregateQty" id="604">
      <copy/>
    </uInt32>
  </sequence>
</template>

The depth message begins with the same 4 fields as the trade message: message type, sequence number, timestamp, and symbol. The depth message also includes two sequences, or variable-length lists. These sequences represent the changed bid and ask levels of the depth for the message security. The sequences themselves contain a set of fields, although in this case both sequences are of the same type.

Each changed depth level begins with a level number, indicating which level has changed. If the exchange were to produce all depth levels on every change, this would not be necessary. Because in an incremental feed the sequence element could represent any of the 5 levels, it is required.

Order count indicates the number of orders which were aggregated at this level. If the order count is 0, it indicates a deleted level. Price is the price common to all the aggregated orders at this level. As in the trade message, it is represented by an integer. Finally, aggregate quantity shows the sum of all the order quantities at this level.

Using QuickFAST Template

With the template in place, the publisher (and subscriber) must use QuickFAST to put the templates to use for encoding (and decoding). The publisher (and subscriber) do this by means of the TemplateConsumer class:

tempate_consumer.h: TemplateConsumer class declaration
namespace liquibook { namespace examples {

class TemplateConsumer {
public:
  static QuickFAST::Codecs::TemplateRegistryPtr 
             parse_templates(const std::string& template_filename);

  // Trade field identities
  static const QuickFAST::Messages::FieldIdentity id_qty_;
  static const QuickFAST::Messages::FieldIdentity id_cost_;

  // Common field identities
  static const QuickFAST::Messages::FieldIdentity id_seq_num_;
  static const QuickFAST::Messages::FieldIdentity id_msg_type_;
  static const QuickFAST::Messages::FieldIdentity id_timestamp_;
  static const QuickFAST::Messages::FieldIdentity id_symbol_;

  // Depth field identities
  static const QuickFAST::Messages::FieldIdentity id_bids_length_;
  static const QuickFAST::Messages::FieldIdentity id_bids_;
  static const QuickFAST::Messages::FieldIdentity id_asks_length_;
  static const QuickFAST::Messages::FieldIdentity id_asks_;

  static const QuickFAST::Messages::FieldIdentity id_level_num_;
  static const QuickFAST::Messages::FieldIdentity id_order_count_;
  static const QuickFAST::Messages::FieldIdentity id_price_;
  static const QuickFAST::Messages::FieldIdentity id_size_;
};

} }

The TemplateConsumer class also has a static parse_templates() method for parsing of templates. It also includes several static members of type const QuickFAST::Messages::FieldIdentityCPtr. These members are used by QuickFAST to establish identities of fields within the various messages. The reader will notice that the field identifiers correspond to fields in the template definition, with two exceptions: id_bids_length_, and id_asks_length_. This is because the FAST protocol specifies that a sequence length is required for each sequence, but can be implicitly declared.

These members are initialized in a manner consistent with the template definition:

tempate_consumer.cpp: TemplateConsumer class implementation
namespace liquibook { namespace examples {

using namespace QuickFAST::Messages;

const FieldIdentity TemplateConsumer::id_seq_num_("SequenceNumber");

const FieldIdentity TemplateConsumer::id_msg_type_("MessageType");

const FieldIdentity TemplateConsumer::id_timestamp_("Timestamp");

// more like this...

The remaining members are initialized in a similar manner, and thus are not included in this paper. Finally, there is the parse_templates() method which parses the QuickFAST templates:

tempate_consumer.cpp: TemplateConsumer class implementation, continued
QuickFAST::Codecs::TemplateRegistryPtr 
TemplateConsumer::parse_templates(const std::string& template_filename)
{
  std::ifstream template_stream(template_filename.c_str());
  QuickFAST::Codecs::XMLTemplateParser parser;
  return parser.parse(template_stream);
}

} } 

The parse_templates() method opens an input file stream using the filename passed to it. Then it creates a QuickFAST::Codecs::XMLTemplateParser and calls the parse() method on it, passing it the input file stream. This parses the template file, and produces the QuickFAST::Codecs::TemplateRegistryPtr which preserves the parsed form of the template file.

Connecting the Publisher and Subscriber

The publisher and subscriber communicate over TCP/IP using boost::ASIO. The communication between publisher and subscriber is one way - the publisher only sends messages, and the subscriber only receives them. The communication is also one-to-many, as multiple subscribers connect to a single publisher. Since QuickFAST encodes a stream of data, uniquely compressed for each recipient, the publisher will need to maintain a QuickFAST state for each subscriber's encoder. This is done through a class called DepthFeedSession.

The publisher also uses the DepthFeedConnection to listen for connections from subscribers. The DepthFeedConnection will establish many DepthFeedSession instances.

depth_feed_connection.h: DepthFeedSession class declaration
namespace liquibook { namespace examples {
  typedef boost::shared_ptr WorkingBufferPtr;
  typedef std::deque WorkingBuffers;
  typedef boost::array Buffer;
  typedef boost::shared_ptr BufferPtr;
  typedef boost::function MessageHandler;
  typedef boost::function ResetHandler;
  typedef boost::function SendHandler;
  typedef boost::function RecvHandler;

  class DepthFeedConnection;

  // Session between a publisher and one subscriber
  class DepthFeedSession : boost::noncopyable {
  public:
    DepthFeedSession(boost::asio::io_service& ios,
                     DepthFeedConnection* connection,
                     QuickFAST::Codecs::TemplateRegistryPtr& templates);

In addition to declaring some typedefs and a forward declaration, this snippet of code declares DepthFeedSession's constructor. This constructor accepts (1) an IO Service object through which it can create a TCP socket, (2) the DepthFeedConnection and (3) the parsed QuickFAST templates, so it can encode outgoing messages.

Next are an accessor and setter for the session's connection status, and an accessor for the session's TCP socket:

depth_feed_connection.h: DepthFeedSession class declaration, continued
    // Is this session connected?
    bool connected() const { return connected_; }

    // Mark this session as connected
    void set_connected() { connected_ = true; }

    // Get the socket for this session
    boost::asio::ip::tcp::socket& socket() { return socket_; }

The real meat of the class is next, where messages are sent to the subscriber, in send_trade() for trades, and both send_incr_update()and send_full_update() for depth updates.

depth_feed_connection.h: DepthFeedSession class declaration, continued
    // Send a trade messsage to all clients
    void send_trade(QuickFAST::Messages::FieldSet& message);

    // Send an incremental update - if this client has handled this symbol
    //   return true if handled
    bool send_incr_update(const std::string& symbol,
                          QuickFAST::Messages::FieldSet& message);

    // Send a full update - if the client has not yet received for this symbol
    void send_full_update(const std::string& symbol,
                          QuickFAST::Messages::FieldSet& message);

The reader may wonder why there are two methods for sending depth updates. This is because the publisher produces an incremental feed for depth updates, allowing only the changed depth levels to be published to the subscriber, rather than all 5 levels of depth. This is particularly effective because of the nature of this data - most changes to depth are at the top one or two levels of the limit order book.

Note that the trade feed is not an incremental feed - each trade stands on its own. This could be different if the trade carried accumulating values, such as volume for the day, or average traded price. In this case, an incremental feed may be suitable for trades as well.

For the depth feed, two update methods are provided, for sending an incremental update, and for sending a full update. Even though the depth feed is an incremental feed, it is necessary to send full updates on occasion. A full update is required, for example, when the subscriber does not yet have the depth state for a security, such as when it first connects to the publisher.

The DepthFeedSession class includes a number of private members:

depth_feed_connection.h: DepthFeedSession class declaration, continued
  private:       
    bool connected_;
    uint64_t seq_num_;

    boost::asio::io_service& ios_;
    boost::asio::ip::tcp::socket socket_;

The first member indicates the connection status of the session. Next, is a sequence number counter for the session. This is followed by two low-level ASIO members: a reference to the IO service, and a socket for the session.

This is followed by a pointer to the one DepthFeedConnection of the publisher, and the session's QuickFAST encoder:

depth_feed_connection.h: DepthFeedSession class declaration, continued
    DepthFeedConnection* connection_;
    QuickFAST::Codecs::Encoder encoder_;

Next, is a set to track which securities have had depth information published, in order to track which securities still need a full update:

depth_feed_connection.h: DepthFeedSession class declaration, continued
    typedef std::set<std::string> StringSet;
    StringSet sent_symbols_;

Next are two static members that store the template IDS for the trade and depth messages:

depth_feed_connection.h: DepthFeedSession class declaration, continued
    static QuickFAST::template_id_t TID_TRADE_MESSAGE;
    static QuickFAST::template_id_t TID_DEPTH_MESSAGE;

Finally, there are two private methods, to set the sequence number on a message, and to handle a sent message:

depth_feed_connection.h: DepthFeedSession class declaration, continued
    void set_sequence_num(QuickFAST::Messages::FieldSet& message);

    void on_send(WorkingBufferPtr wb,
                 const boost::system::error_code& error,
                 std::size_t bytes_transferred);
  };

The DepthFeedSession implementation begins with the declarations of its two static members, TID_TRADE_MESSAGE and TID_DEPTH_MESSAGE.

depth_feed_connection.cpp: DepthFeedSession class implementation
using namespace boost::asio::ip;

namespace liquibook { namespace examples {

QuickFAST::template_id_t DepthFeedSession::TID_TRADE_MESSAGE(1);
QuickFAST::template_id_t DepthFeedSession::TID_DEPTH_MESSAGE(2);

Next is the class constructor, which sets the connected status to false, sets the session sequence number to 0, initializes the socket, saves the IO service and connection, and provides the templates to the QuickFAST encoder:

depth_feed_connection.cpp: DepthFeedSession class implementation, continued
DepthFeedSession::DepthFeedSession(
    boost::asio::io_service& ios,
    DepthFeedConnection* connection,
    QuickFAST::Codecs::TemplateRegistryPtr& templates)
: connected_(false),
  seq_num_(0),
  ios_(ios),
  socket_(ios),
  connection_(connection),
  encoder_(templates)
{
}

The next method sends a trade to the subscriber:

depth_feed_connection.cpp: DepthFeedSession class implementation, continued
void
DepthFeedSession::send_trade(QuickFAST::Messages::FieldSet& message)
{
  // Add or update sequence number in message
  set_sequence_num(message);

  std::cout && "sending trade message with " && message.size() && " fields" && std::endl;

  QuickFAST::Codecs::DataDestination dest;
  encoder_.encodeMessage(dest, TID_TRADE_MESSAGE, message);
  WorkingBufferPtr wb = connection_->reserve_send_buffer();
  dest.toWorkingBuffer(*wb);

  // Perform the send
  SendHandler send_handler = boost::bind(&DepthFeedSession::on_send,
                                         this, wb, _1, _2);
  boost::asio::const_buffers_1 buffer(
      boost::asio::buffer(wb->begin(), wb->size()));
  socket_.async_send(buffer, 0, send_handler);
}

The send_trade() accepts an argument, reference to type QuickFAST::Messages::FieldSet, which contains the method in a protocol-neutral format. The method starts by setting the sequence number of the message by passing the field set to set_sequence_num(). The caller is responsible for filling out the other fields of the message, while the session sets the sequence number.

Next, the method declares a data destination variable, which holds an encoded message. This message is then encoded into the destination by the QuickFAST encoder, using the template ID for trade messages.

Next a QuickFAST working buffer is reserved for sending the message, and the encoded message is serialized into the buffer. To send the message, the method first creates a send handler for the buffer, so that DepthFeedSession::on_send is called after the send. Next, it sets up an ASIO buffer to send the working buffer's contents, and calls the socket's async_send() method.

The send_incr_update method is similar, with some key differences:

depth_feed_connection.cpp: DepthFeedSession class implementation, continued
bool
DepthFeedSession::send_incr_update(const std::string& symbol,
                                   QuickFAST::Messages::FieldSet& message)
{
  bool sent = false;
  // If the session has been started for this symbol
  if (sent_symbols_.find(symbol) != sent_symbols_.end()) {
    QuickFAST::Codecs::DataDestination dest;
    // Add or update sequence number in message
    set_sequence_num(message);
    encoder_.encodeMessage(dest, TID_DEPTH_MESSAGE, message);
    WorkingBufferPtr wb = connection_->reserve_send_buffer();
    dest.toWorkingBuffer(*wb);
    SendHandler send_handler = boost::bind(&DepthFeedSession::on_send,
                                           this, wb, _1, _2);
    boost::asio::const_buffers_1 buffer(
        boost::asio::buffer(wb->begin(), wb->size()));
    socket_.async_send(buffer, 0, send_handler);
    sent = true;
  }
  return sent;
}

First, the reader will notice that send_incr_update() returns a bool value. This value indicates whether the message was sent or not. If not, the caller is responsible for calling send_full_update() with the message.

The method also checks the sent_symbols_ for the symbol of the message (provided as an argument) to ensure the security has been sent to the subscriber previously. Should this be true, execution is similar to that of send_trade(), where the sequence number is set, the message is encoded to a data destination, a working buffer is reserved, the destination serialized to the working buffer, and the working buffer contents is sent.

The send_full_update() method is similar to send_incr_update, without the return value. In this case, the method updates the sent_symbols_ map to be true. As a form of protection, it does not send the full update if the map already had a value for that security.

depth_feed_connection.cpp: DepthFeedSession class implementation, continued
void
DepthFeedSession::send_full_update(const std::string& symbol,
                                   QuickFAST::Messages::FieldSet& message)
{
  // Mark this symbols as sent
  std::pair result = sent_symbols_.insert(symbol);

  // If this symbol is new for the session
  if (result.second) {
    QuickFAST::Codecs::DataDestination dest;
    // Add or update sequence number in message
    set_sequence_num(message);
    encoder_.encodeMessage(dest, TID_DEPTH_MESSAGE, message);
    WorkingBufferPtr wb = connection_->reserve_send_buffer();
    dest.toWorkingBuffer(*wb);

    // Perform the send
    SendHandler send_handler = boost::bind(&DepthFeedSession::on_send,
                                           this, wb, _1, _2);
    boost::asio::const_buffers_1 buffer(
        boost::asio::buffer(wb->begin(), wb->size()));
    socket_.async_send(buffer, 0, send_handler);
  }
}

The first private method, set_sequence_num() is used to set the sequence number field in the outgoing message. The sequence number is unique to each session, so it must be encoded for each copy of the outbound message:

depth_feed_connection.cpp: DepthFeedSession class implementation, continued
void
DepthFeedSession::set_sequence_num(QuickFAST::Messages::FieldSet& message)
{
  // Create the field
  QuickFAST::Messages::FieldCPtr value =
      QuickFAST::Messages::FieldUInt32::create(++seq_num_);
  // Update the sequence number
  if (!message.replaceField(TemplateConsumer::id_seq_num_, value)) {
    // Not found, add the sequence number
    message.addField(TemplateConsumer::id_seq_num_, value);
  }
}

The method begins by creating a QUICKFAST field for the incremented sequence number. Since this code resides within a session, there is a copy of the member variable seq_num_ for each subscriber. The first message will have a sequence number of 1.

The method attempts to update the sequence number field in the message, and if the sequence number is not yet present in the message, the field is added. Using this technique, the message can be built once, and then have the sequence number customized for each subscriber, without having to build the message from scratch for each subscriber.

The other private method, on_send() handles a sent message, logging any error code, and calling the DepthFeedConnection version of the method:

depth_feed_connection.cpp: DepthFeedSession class implementation, continued
void
DepthFeedSession::on_send(WorkingBufferPtr wb,
                          const boost::system::error_code& error,
                          std::size_t bytes_transferred)
{
  if (error) {
    std::cout << "Error " << error << " sending message" << std::endl;
    connected_ = false;
  }

  // Keep buffer for later
  connection_->on_send(wb, error, bytes_transferred);
}

The DepthFeedConnection class is used by both the publisher and the subscriber. It has a constructor that accepts the command line arguments:

depth_feed_connection.h: DepthFeedConnection class declaration
  typedef boost::shared_ptr SessionPtr;

  class DepthFeedConnection : boost::noncopyable {
  public:
    DepthFeedConnection(int argc, const char* argv[]);

Next are an accessor for the templates parsed by the connection, and two methods for connecting the publisher and subscribers. The publisher calls accept() and the subscriber calls connect():

depth_feed_connection.h: DepthFeedConnection class declaration, continued
    // Get the template registry
    const QuickFAST::Codecs::TemplateRegistryPtr&
          get_templates() { return templates_; }

    // Connect to publisher
    void connect();

    // Accept connection from subscriber
    void accept();

The next method, run() issues all the ASIO callbacks. Both the publisher and subscriber must cal run().

depth_feed_connection.h: DepthFeedConnection class declaration, continued
    // Let the IO service run
    void run();

The next two methods set up handlers for events:

depth_feed_connection.h: DepthFeedConnection class declaration, continued
    // Set a callback to handle a message
    void set_message_handler(MessageHandler msg_handler);

    // Set a callback to handle a reset connection
    void set_reset_handler(ResetHandler reset_handler);

The first, set_message_handler(), handles incoming messages in the subscriber. The second, set_reset_handler() within the publisher handles disconnects within the subscriber to the publisher.

The next two methods are for buffer management:

depth_feed_connection.h: DepthFeedConnection class declaration, continued
    // Reserve a buffer for receiving a message
    BufferPtr reserve_recv_buffer();

    // Reserve a buffer for sending a message
    WorkingBufferPtr reserve_send_buffer();

Rather than constantly allocating and deallocating buffers, the publisher and subscriber have a pool of buffers ready to be used again and again. The publisher and subscriber will reserve these buffers from DepthFeedConnection, bind them to an ASIO asynchronous callback, and then restore the buffers back to the DepthFeedConnection.

The first, reserve_recv_buffer() is called by the subscriber for a buffer to receive a message. Likewise, reserve_send_buffer() reserves a buffer to serialize an encoded QuickFAST message, for the publisher to send to the subscriber.

Next, are three methods for the publisher to send a message to each subscriber:

depth_feed_connection.h: DepthFeedConnection class declaration, continued
    // Send a trade messsage to all clients
    void send_trade(QuickFAST::Messages::FieldSet& message);

    // Send an incremental update
    //   return true if all sessions could handle an incremental update
    bool send_incr_update(const std::string& symbol, 
                          QuickFAST::Messages::FieldSet& message);

    // Send a full update to those which have not yet received for this symbol
    void send_full_update(const std::string& symbol, 
                          QuickFAST::Messages::FieldSet& message);

These three methods call the corresponding method on DepthFeedSession for each subscriber. The public interface ends with a number of event handlers:

depth_feed_connection.h: DepthFeedConnection class declaration, continued
    // Handle a connection
    void on_connect(const boost::system::error_code& error);

    // Handle an accepted connection
    void on_accept(SessionPtr session,
                   const boost::system::error_code& error);

    // Handle a received message
    void on_receive(BufferPtr bp, 
                    const boost::system::error_code& error,
                    std::size_t bytes_transferred);
    // Handle a sent message
    void on_send(WorkingBufferPtr wb,
                 const boost::system::error_code& error,
                 std::size_t bytes_transferred);

These methods handle, the result of a subscriber connecting to the publisher, a publisher's accepted connection from a subscriber, a message received from the publisher, and a message sent to the subscriber, respectively.

The private members of DepthFeedConnection start with some configuration date: the name of the file containing the FAST templates, the host for the subscriber to connect to, and the connection port. These are followed by the event handlers, the parsed QuickFAST templates, buffer pools, the DepthFeedSession instances, and some boost ASIO helpers.

The private section ends with some methods, to issue a read, and parse the command line arguments:

depth_feed_connection.h: DepthFeedConnection class declaration, continued
  private:
    typedef std::deque<BufferPtr> Buffers;
    typedef std::vector<SessionPtr> Sessions;
    const char* template_filename_;
    const char* host_;
    int port_;
    MessageHandler msg_handler_;
    ResetHandler reset_handler_;
    QuickFAST::Codecs::TemplateRegistryPtr templates_;

    Buffers        unused_recv_buffers_;
    WorkingBuffers unused_send_buffers_;
    Sessions sessions_;
    boost::shared_ptr<boost::asio::ip::tcp::acceptor> acceptor_;
    boost::asio::io_service ios_;
    boost::asio::ip::tcp::socket socket_;
    boost::shared_ptr<boost::asio::io_service::work> work_ptr_;

    void issue_read();
    static const char* template_file_from_args(int argc, const char* argv[]);
    static const char* host_from_args(int argc, const char* argv[]);
    static int port_from_args(int argc, const char* argv[]);
  };
} } // End namespace

The DepthFeedConnection constructor derives the configuration variables by passing the command-line arguments to the *_from_args() methods:

depth_feed_connection.cpp: DepthFeedConnection class implementation
DepthFeedConnection::DepthFeedConnection(int argc, const char* argv[])
: template_filename_(template_file_from_args(argc, argv)),
  host_(host_from_args(argc, argv)),
  port_(port_from_args(argc, argv)),
  templates_(TemplateConsumer::parse_templates(template_filename_)),
  socket_(ios_)
{
}

It also invokes parse_templates() to parse the QuickFAST templates, and initializes the subscriber socket. Next, connect() does a standard ASIO asynchronous connection, using on_connect() as its callback:

depth_feed_connection.cpp: DepthFeedConnection class implementation, continued
void
DepthFeedConnection::connect()
{
  std::cout << "Connecting to feed" << std::endl;
  tcp::endpoint endpoint(address::from_string(host_), port_);
  socket_.async_connect(endpoint, boost::bind(&DepthFeedConnection::on_connect,
                                              this, _1));
}

The accept() method implements the publisher's side of ASIO connectivity. The first time accept() id called a TCP acceptor is created and initialized. For each call, a DepthFeedSession is created, and an asynchronous accept is started, using to on_accept() as its callback:

depth_feed_connection.cpp: DepthFeedConnection class implementation, continued
void
DepthFeedConnection::accept()
{
  if (!acceptor_) {
    acceptor_.reset(new tcp::acceptor(ios_));
    tcp::endpoint endpoint(tcp::v4(), port_);
    acceptor_->open(endpoint.protocol());
    boost::system::error_code ec;
    acceptor_->set_option(boost::asio::socket_base::reuse_address(true), ec);
    acceptor_->bind(endpoint);
    acceptor_->listen();
  }
  SessionPtr session(new DepthFeedSession(ios_, this, templates_));
  acceptor_->async_accept(
      session->socket(), 
      boost::bind(&DepthFeedConnection::on_accept, this, session, _1));
}

The run() method invokes the run() method on the IO service object. Usually run() will exit when there are no more callbacks to make. Using the io_service::work object ensures run() does not exit:

depth_feed_connection.cpp: DepthFeedConnection class implementation, continued
void
DepthFeedConnection::run()
{
std::cout << "DepthFeedConnection::run()" << std::endl;
  // Keep on running
  work_ptr_.reset(new boost::asio::io_service::work(ios_));
  ios_.run();
}

The next two methods simply save off the two message handlers:

depth_feed_connection.cpp: DepthFeedConnection class implementation, continued

void
DepthFeedConnection::set_message_handler(MessageHandler handler)
{
  msg_handler_ = handler;
}

void
DepthFeedConnection::set_reset_handler(ResetHandler handler)
{
  reset_handler_ = handler;
}

As stated above, the DepthFeedConnection uses buffer pools to reduce heap allocations and deallocations. The following two methods provide unused buffers to the caller:

depth_feed_connection.cpp: DepthFeedConnection class implementation
BufferPtr
DepthFeedConnection::reserve_recv_buffer()
{
  if (unused_recv_buffers_.empty()) {
    return BufferPtr(new Buffer());
  } else {
    BufferPtr bp = unused_recv_buffers_.front();
    unused_recv_buffers_.pop_front();
    return bp;
  }
}

WorkingBufferPtr
DepthFeedConnection::reserve_send_buffer()
{
  if (unused_send_buffers_.empty()) {
    return WorkingBufferPtr(new QuickFAST::WorkingBuffer());
  } else {
    WorkingBufferPtr wb = unused_send_buffers_.front();
    unused_send_buffers_.pop_front();
    return wb;
  }
}

In both methods, an unused buffer is returned, and one is created if none are available.

The send_trade(), send_incr_update() and send_full_update() methods send a message to all subscribers by iterating through the sessions and invoking the corresponding method on each:

depth_feed_connection.cpp: DepthFeedConnection class implementation, continued
void
DepthFeedConnection::send_trade(QuickFAST::Messages::FieldSet& message)
{
  // For each session
  Sessions::iterator session;
  for (session = sessions_.begin(); session != sessions_.end(); ) {
    // If the session is connected
    if ((*session)->connected()) {
      // conditionally send on that session
      (*session)->send_trade(message);
      ++session;
    } else {
      // Remove the session
      session = sessions_.erase(session);
    }
  }
}

bool
DepthFeedConnection::send_incr_update(const std::string& symbol,
                                      QuickFAST::Messages::FieldSet& message)
{
  bool none_new = true;
  // For each session
  Sessions::iterator session;
  for (session = sessions_.begin(); session != sessions_.end(); ) {
    // If the session is connected
    if ((*session)->connected()) {
      // send on that session
      if (!(*session)->send_incr_update(symbol, message)) {
        none_new = false;
      }
      ++session;
    } else {
      // Remove the session
      session = sessions_.erase(session);
    }
  }
  return none_new;
}

void
DepthFeedConnection::send_full_update(const std::string& symbol,
                                      QuickFAST::Messages::FieldSet& message)
{
  // For each session
  Sessions::iterator session;
  for (session = sessions_.begin(); session != sessions_.end(); ) {
    // If the session is connected
    if ((*session)->connected()) {
      // conditionally send on that session
      (*session)->send_full_update(symbol, message);
      ++session;
    } else {
      // Remove the session
      session = sessions_.erase(session);
    }
  }
}

In any of the cases, if the session is no longer connected, the session is removed. There is one important difference in send_incr_update() - the method's return value indicates if any of the sessions gave back a return value of false.

The next method handles a connection within the subscriber:

depth_feed_connection.cpp: DepthFeedConnection class implementation, continued

void
DepthFeedConnection::on_connect(const boost::system::error_code& error)
{
  if (!error) {
    std::cout << "connected to feed" << std::endl;
    reset_handler_();
    issue_read();
  } else {
    std::cout << "on_connect, error=" << error << std::endl;
    socket_.close();
    sleep(3);
    // Try again
    connect();
  }
}

When on_connect() is called, the method tries again, in case of failure, or calls the reset handler and then begins to read on the socket, in case of success.

The next handler is the counterpart in the publisher:

depth_feed_connection.cpp: DepthFeedConnection class implementation, continued
void
DepthFeedConnection::on_accept(SessionPtr session,
                               const boost::system::error_code& error)
{
  if (!error) {
    std::cout << "accepted client connection" << std::endl;
    sessions_.push_back(session);
    session->set_connected();
  } else {
    std::cout << "on_accept, error=" << error << std::endl;
    session.reset();
    sleep(2);
  }
  // accept again
  accept();
}

In the case of on_accept(), the publisher always restarts the accept cycle - so that another subscriber may connect. The subscriber, on the other hand, only wants one connection.

In the case of a successful connection, the publisher saves off the session, and marks it as connected, so that messages may be sent to it. If there is failure, the session is deleted (by resetting the shared pointer) and the publisher waits momentarily before starting again.

The next event handler handles a message received by the subscriber:

depth_feed_connection.cpp: DepthFeedConnection class implementation, continued
void
DepthFeedConnection::on_receive(BufferPtr bp,
                                const boost::system::error_code& error,
                                std::size_t bytes_transferred)
{
  if (!error) {
    // Next read
    issue_read();

    // Handle the buffer
    if (!msg_handler_(bp, bytes_transferred)) {
      socket_.close();
    }
  } else {
    std::cout << "Error " << error << " receiving message" << std::endl;
    socket_.close();
    sleep(3);
    connect();
  }
  // Restore buffer
  unused_recv_buffers_.push_back(bp);
}

When the receive is handled, if the read was successful, the subscriber first issues a new read, and then calls the receive handler, which parses the message. Should this parsing fail, the socket will close (and the next read will then fail).

Should the read fail, the socket is closed, and the connection cycle starts anew. In all cases, the buffer is restored back to the pool.

The next handler is called after a send of a message to the subscriber:

depth_feed_connection.cpp: DepthFeedConnection class implementation, continued
void
DepthFeedConnection::on_send(WorkingBufferPtr wb,
                             const boost::system::error_code& error,
                             std::size_t bytes_transferred)
{
  // Keep buffer for later
  unused_send_buffers_.push_back(wb);
}

Recall that this method is called from within DepthFeedSession::on_send() which has already examined the error code. What remains is to restore the buffer back to the pool.

The next method starts an ASIO read on the subscriber's socket:

depth_feed_connection.cpp: DepthFeedConnection class implementation, continued

void
DepthFeedConnection::issue_read()
{
  BufferPtr bp = reserve_recv_buffer();
  RecvHandler recv_handler = boost::bind(&DepthFeedConnection::on_receive, 
                                         this, bp, _1, _2);
  boost::asio::mutable_buffers_1 buffer(
      boost::asio::buffer(*bp, bp->size()));
  socket_.async_receive(buffer, 0, recv_handler);
}

issue_read() reserves a buffer, binds the buffer to the on_receive() method, and issues the asynchronous read.

The next three methods look through the command line arguments for configuration variables:

depth_feed_connection.cpp: DepthFeedConnection class implementation, continued
const char*
DepthFeedConnection::template_file_from_args(int argc, const char* argv[])
{
  bool next_is_name = false;
  for (int i = 0; i < argc; ++i) {
    if (next_is_name) {
      return argv[i];
    } else if (strcmp(argv[i], "-t") == 0) {
      next_is_name = true;
    }
  }
  return "./templates/depth.xml";
}

const char*
DepthFeedConnection::host_from_args(int argc, const char* argv[])
{
  bool next_is_host = false;
  for (int i = 0; i < argc; ++i) {
    if (next_is_host) {
      return argv[i];
    } else if (strcmp(argv[i], "-h") == 0) {
      next_is_host = true;
    }
  }
  return "127.0.0.1";
}

int
DepthFeedConnection::port_from_args(int argc, const char* argv[])
{
  bool next_is_port = false;
  for (int i = 0; i < argc; ++i) {
    if (next_is_port) {
      return atoi(argv[i]);
    } else if (strcmp(argv[i], "-p") == 0) {
      next_is_port = true;
    }
  }
  return 10003;
}

} } // End namespace

The first method looks for a -t followed by a template file name. The second looks foe a -h followed by a host name. The last one looks for a -p followed by a port number. All three return default values.

Handling Liquibook Events

At this point, the publisher is able to accept subscriber connections and route QuickFAST messages to the subscriber. What is missing is handling Liquibook events, and creating the QuickFAST messages to send. As shown in figure 2, the publisher needs a properly-typed TradeListener for trade events, and a properly-typed DepthListener for depth events.

The TradeListener interface has a single method to implement:

trade_listener.h: TradeListener interface declaration
namespace liquibook { namespace book {

/// @brief listener of trade events.   Implement to build a trade feed.
template <class OrderBook >
class TradeListener {
public:
  /// @brief callback for a trade
  /// @param book the order book of the fill (not defined whether this is before
  ///      or after fill)
  /// @param qty the quantity of this fill
  /// @param cost the cost of this fill (qty * price)
  virtual void on_trade(const OrderBook* book,
                        Quantity qty,
                        Cost cost) = 0;
};

} }

Likewise, the DepthListener interface has a single method to implement:

trade_listener.h: TradeListener interface declaration
namespace liquibook { namespace book {

/// @brief listener of depth events.  Implement to build an aggregate depth 
/// feed.
template <class OrderBook >
class DepthListener {
public:
  /// @brief callback for change in tracked aggregated depth
  virtual void on_depth_change(
      const OrderBook* book,
      const typename OrderBook::DepthTracker* depth) = 0;
};

} }

The publisher implements these interfaces in the DepthFeedPublisher class:

depth_feed_publisher.h: DepthFeedPublisher class declaration
namespace liquibook { namespace examples {

class DepthFeedPublisher : public ExampleOrderBook::TypedDepthListener,
                           public ExampleOrderBook::TypedTradeListener,
                           public TemplateConsumer {

The DepthFeedPublisher class implements both the ExampleOrderBook::TypedDepthListener and the ExampleOrderBook::TypedTradeListener interfaces, which bind the book::DepthListener and the book::TradeListener templates to those used in ExampleOrderBook. DepthFeedPublisher also inherits from TemplateConsumer, but this is simply a convenience (to reduce the need for name qualifiers), as TemplateConsumer's data and methods are entirely static.

The DepthFeedPublisher has a simple constructor, for initializing its pointer.

depth_feed_publisher.h: DepthFeedPublisher class declaration, continued
public:
  DepthFeedPublisher();

The DepthFeedPublisher keeps a pointer to a DepthFeedConnection, and receives that pointer through a setter method:

depth_feed_publisher.h: DepthFeedPublisher class declaration, continued
  void set_connection(DepthFeedConnection* connection);

Next are the two event handlers to implement the two interfaces:

depth_feed_publisher.h: DepthFeedPublisher class declaration, continued
  virtual void on_trade(
      const book::OrderBook<OrderPtr>* order_book,
      book::Quantity qty,
      book::Cost cost);

  virtual void on_depth_change(
      const book::DepthOrderBook<OrderPtr>* order_book,
      const book::DepthOrderBook<OrderPtr>::DepthTracker* tracker);

The private section has a single member - the DepthFeedConnection pointer:

depth_feed_publisher.h: DepthFeedPublisher class declaration, continued
private:
  DepthFeedConnection* connection_;

Next are methods to build the feed messages:

depth_feed_publisher.h: DepthFeedPublisher class declaration, continued
  // Build an trade message
  void build_trade_message(
      QuickFAST::Messages::FieldSet& message,
      const std::string& symbol,
      book::Quantity qty,
      book::Cost cost);

  // Build an incremental depth message
  void build_depth_message(
      QuickFAST::Messages::FieldSet& message,
      const std::string& symbol,
      const book::DepthOrderBook<OrderPtr>::DepthTracker* tracker,
      bool full_message);
  void build_depth_level(
      QuickFAST::Messages::SequencePtr& level_seq,
      const book::DepthLevel* level,
      int level_index);

And finally a method to generate a time stamp for the messages:

depth_feed_publisher.h: DepthFeedPublisher class declaration, continued
  uint32_t time_stamp();
};

} } // End namespace

The DepthFeedPublisher constructor initializes its connection pointer, which is set by a separate setter method:

depth_feed_publisher.cpp: DepthFeedPublisher class implementation
namespace liquibook { namespace examples { 

using namespace QuickFAST::Messages;

DepthFeedPublisher::DepthFeedPublisher()
: connection_(NULL)
{
}

void
DepthFeedPublisher::set_connection(DepthFeedConnection* connection)
{
  connection_ = connection;
}

The DepthFeedPublisher's primary responsibility is to build feed messages, in response to events. The first event handler is on_trade():

depth_feed_publisher.cpp: DepthFeedPublisher class implementation, continued

void
DepthFeedPublisher::on_trade(
    const book::OrderBook<OrderPtr>* order_book,
    book::Quantity qty,
    book::Cost cost)
{
  // Publish trade
  QuickFAST::Messages::FieldSet message(20);
  const ExampleOrderBook* exob = 
          dynamic_cast<const ExampleOrderBook*>(order_book);
  std::cout << "Got trade for " << exob->symbol() 
            << " qty " << qty
            << " cost " << cost << std::endl;
  build_trade_message(message, exob->symbol(), qty, cost);
  connection_->send_trade(message);
}

on_trade() starts by casting the order book to the derived type. The reader will recall that the derived order book is used to store the symbol of the order book. The method then builds the trade message, through a call to build_trade_message(), and distributes the message through the DepthFeedConnection.

The next event handler handles depth change events:

depth_feed_publisher.cpp: DepthFeedPublisher class implementation, continued
void
DepthFeedPublisher::on_depth_change(
    const book::DepthOrderBook<OrderPtr>* order_book,
    const book::DepthOrderBook<OrderPtr>::DepthTracker* tracker)
{
  // Publish changed levels of order book
  QuickFAST::Messages::FieldSet message(20);
  const ExampleOrderBook* exob = 
          dynamic_cast<const ExampleOrderBook*>(order_book);
  build_depth_message(message, exob->symbol(), tracker, false);
  if (!connection_->send_incr_update(exob->symbol(), message)) {
    // Publish all levels of order book
    QuickFAST::Messages::FieldSet full_message(20);
    build_depth_message(full_message, exob->symbol(), tracker, true);
    connection_->send_full_update(exob->symbol(), full_message);
  }
}

Similar to the trade event handler, the depth change event handler casts the order book to the derived class, builds a feed message, and sends it to the clients through the DepthFeedConnection. What is different in on_depth_change() is that since the depth feed is incremental, as noted above, the clients must be known to be in a valid state for the security of the update to handle the next message.

The reader will recall that the call to DepthFeedConnection::send_incr_update() returns true if and only if all of the clients could handle the message. If not, false is returned, and on_depth_change() builds and sends a full update instead. In the steady state, only the incremental message need by built.

The build_trade_message() method is used by on_trade() to build a trade message:

depth_feed_publisher.cpp: DepthFeedPublisher class implementation, continued
void
DepthFeedPublisher::build_trade_message(
    QuickFAST::Messages::FieldSet& message,
    const std::string& symbol,
    book::Quantity qty,
    book::Cost cost)
{
  message.addField(id_timestamp_, FieldUInt32::create(time_stamp()));
  message.addField(id_symbol_, FieldString::create(symbol));
  message.addField(id_qty_, FieldUInt32::create(qty));
  message.addField(id_cost_, FieldUInt32::create(cost));
}

Building a QuickFAST message is relatively simple. Each field is added to the message through the addField() call. The sequence number, added later, is particular to a specific client.

The next message builder, build_depth_message(), builds repeating sequences of data, and is more complex:

depth_feed_publisher.cpp: DepthFeedPublisher class implementation, continued
void
DepthFeedPublisher::build_depth_message(
    QuickFAST::Messages::FieldSet& message,
    const std::string& symbol,
    const book::DepthOrderBook<OrderPtr>::DepthTracker* tracker,
    bool full_message)
{
  size_t bid_count(0), ask_count(0);

  message.addField(id_timestamp_, FieldUInt32::create(time_stamp()));
  message.addField(id_symbol_, FieldString::create(symbol));

  // Build the changed levels
  book::ChangeId last_published_change = tracker->last_published_change();
  
  // Build changed bids
  {
    SequencePtr bids(new Sequence(id_bids_length_, 1));
    int index = 0;
    const book::DepthLevel* bid = tracker->bids();
    // Create sequence of bids
    while (true) {
      if (full_message || bid->changed_since(last_published_change)) {
        build_depth_level(bids, bid, index);
        ++bid_count;
      }
      ++index;
      if (bid == tracker->last_bid_level()) {
        break;
      } else {
        ++bid;
      }
    }
    message.addField(id_bids_, FieldSequence::create(bids));
  }

  // Build changed asks
  {
    SequencePtr asks(new Sequence(id_asks_length_, 1));
    int index = 0;
    const book::DepthLevel* ask = tracker->asks();
    // Create sequence of asks
    while (true) {
      if (full_message || ask->changed_since(last_published_change)) {
        build_depth_level(asks, ask, index);
        ++ask_count;
      }
      ++index;
      if (ask == tracker->last_ask_level()) {
        break;
      } else {
        ++ask;
      }
    }
    message.addField(id_asks_, FieldSequence::create(asks));
  }
  std::cout << "Encoding " << (full_message ? "full" : "incr")
            << " depth message for symbol " << symbol 
            << " with " << bid_count << " bids, "
            << ask_count << " asks" << std::endl;
}

This method starts like add_trade(), adding the timestamp and symbol fields. Next, two sequences are built, for the changed bid and ask levels. To support an incremental feed, Liquibook tracks a change number (called ChangeId) on each depth level, and the ID of the last published change in the depth, which is set after the callback completes. To determine the levels to publish, the publisher needs to compare the last change of each level to the last published change of the depth as a whole.

In build_depth_message(), a bid sequence is created, and an associated length is attached. The changed bid levels are each added to the sequence through calls to build_depth_level(). The iteration logic here must be careful to not move beyond the last bid level. Finally, the sequence is added to the message. The same is done for changed ask levels.

By comparison, build_depth_level() is simple:

depth_feed_publisher.cpp: DepthFeedPublisher class implementation, continued
void
DepthFeedPublisher::build_depth_level(
    QuickFAST::Messages::SequencePtr& level_seq,
    const book::DepthLevel* level,
    int level_index)
{
  FieldSetPtr level_fields(new FieldSet(4));
  level_fields->addField(id_level_num_, FieldUInt8::create(level_index));
  level_fields->addField(id_order_count_, 
                         FieldUInt32::create(level->order_count()));
  level_fields->addField(id_price_,
                         FieldUInt32::create(level->price()));
  level_fields->addField(id_size_,
                         FieldUInt32::create(level->aggregate_qty()));
  level_seq->addEntry(level_fields);
}

This method shows how to add an entry to a QuickFAST sequence - by creating a FieldSet, adding fields to it, and adding the FieldSet to the sequence.

The final method is a helper, to create an integer timestamp for a message:

depth_feed_publisher.cpp: DepthFeedPublisher class implementation, continued
uint32_t
DepthFeedPublisher::time_stamp()
{
  time_t now;
  time(&now);
  return now;
}

} } // End namespace

Publisher Initialization

At this point the publisher has all the necessary parts to publish a trade feed and an incremental depth feed for the exchange. The only things that remains are to create, initialize, and associate the various parts, and to generate the random orders.

The exchange is initialized in the file publisher_main.cpp. The main() function starts by establishing the securities to "trade" in the exchange - in this case the NASDAQ 100 - taken from a snapshot in April 2013. main() records the symbol and a base price in a structure:

publisher_main.cpp: SecurityInfo structure
struct SecurityInfo {
  std::string symbol;
  double ref_price;

  SecurityInfo(const char* sym, double price)
  : symbol(sym),
    ref_price(price)
  {
  }
};

typedef std::vector SecurityVector;

The base price serves as a basis for generating random prices for the example exchange. main() keeps this security information in a SecurityVector and populates it in the create_securities() method, later in main().

The main() function begins by creating the DepthFeedConnection, and uses Boost's thread library to create a background thread to accept connections from subscribers:

publisher_main.cpp: main() function
int main(int argc, const char* argv[])
{
  // Feed connection
  examples::DepthFeedConnection connection(argc, argv);

  // Open connection in background thread
  connection.accept();
  boost::function acceptor(
      boost::bind(&examples::DepthFeedConnection::run, &connection));
  boost::thread acceptor_thread(acceptor);

Next, the DepthFeedPublisher and the Exchange are created, and theDepthFeedPublisher is made aware of the DepthFeedConnection:

publisher_main.cpp: main() function, continued
  // Create feed publisher
  examples::DepthFeedPublisher feed;
  feed.set_connection(&connection);

  // Create exchange
  examples::Exchange exchange(&feed, &feed);

Note that the exchange constructor requires a TradeListener and a DepthListener, both of which the DepthFeedPublisher implements.

Finally, the securities are created and used to populate the exchange, and orders are generated.

publisher_main.cpp: main() function, continued
  // Create securities
  SecurityVector securities;
  create_securities(securities);

  // Populate exchange with securities
  populate_exchange(exchange, securities);
  
  // Generate random orders
  generate_orders(exchange, securities);

  return 0;
}

The final function call, to generate_orders() is an infinite loop, so there is no need to worry about the main() method returning.

The helper function create_securities() adds 100 securities to the SecurityVector:

publisher_main.cpp: main() helper functions
void
create_securities(SecurityVector& securities) {
  securities.push_back(SecurityInfo("AAPL", 436.36));
  securities.push_back(SecurityInfo("ADBE", 45.06));
  securities.push_back(SecurityInfo("ADI", 43.93));

  // Repeated for 100 securities...
}

Due to the mercy of the author, the other 97 securities were omitted from this paper. The next helper function, populate_exchange() adds these securities to the exchange:

publisher_main.cpp: main() helper functions, continued
void
populate_exchange(examples::Exchange& exchange, const SecurityVector& securities) {
  SecurityVector::const_iterator sec;
  for (sec = securities.begin(); sec != securities.end(); ++sec) {
    exchange.add_order_book(sec->symbol);
  }
}

The final helper function, generate_orders() creates random orders and adds them to the exchange:

publisher_main.cpp: main() helper functions, continued
void
generate_orders(examples::Exchange& exchange, const SecurityVector& securities) {
  time_t now;
  time(&now);
  std::srand(now);

  size_t num_securities = securities.size();
  while (true) {
    // which security
    size_t index = std::rand() % num_securities;
    const SecurityInfo& sec = securities[index];
    // side
    bool is_buy = std::rand() % 2;
    // price
    uint32_t price_base = sec.ref_price * 100;
    uint32_t delta_range = price_base / 50;  // +/- 2% of base
    int32_t delta = std::rand() % delta_range;
    delta -= (delta_range / 2);
    double price = double (price_base + delta) / 100;

    // qty
    book::Quantity qty = (std::rand() % 10 + 1) * 100;

    // order
    examples::OrderPtr order(new examples::Order(is_buy, price, qty));

    // add order
    exchange.add_order(sec.symbol, order);

    // Wait for eyes to read
    sleep(1);
  }
}

The implementation of generate_orders() is somewhat complex, but the details are not relevant. There is a call to sleep() inside the loop, so that the data stream is produced at a somewhat readable pace. At this point, the publisher is complete.

Subscriber Application

With the publisher in place, focus turns to the subscriber. The subscriber must connect to the publisher, decode the FAST feed, recreate the depth state for each security and display the results. As noted earlier, the connection is handled by the DepthFeedConnection class. The decoding and display of messages is handled by a the DepthFeedSubscriber class:

depth_feed_subscriber.h: DepthFeedSubscriber class declaration
namespace liquibook { namespace examples {

  class DepthFeedSubscriber : public TemplateConsumer {
  public:
    DepthFeedSubscriber(
        const QuickFAST::Codecs::TemplateRegistryPtr& templates);

    // Handle a reset of the connection
    void handle_reset();

    // Handle a message
    // return false if failure
    bool handle_message(BufferPtr& bp, size_t bytes_transferred);

The DepthFeedSubscriber class has a number of private members:

depth_feed_subscriber.h: DepthFeedSubscriber class declaration, continued
  private:
    QuickFAST::Codecs::Decoder decoder_;
    typedef std::map<std::string, book::Depth<5> > DepthMap;
    DepthMap depth_map_;
    uint64_t expected_seq_;

    static const uint64_t MSG_TYPE_DEPTH;
    static const uint64_t MSG_TYPE_TRADE;

These members include a QuickFAST decoder, an order book for each security (kept in a std::map), and the expected sequence number. The sequence number is tracked to validate the feed, so that the subscriber is sure that every message from the publisher has been handled.

Finally, there are two constants for determining message type. The reader will note that these message type fields appear in the message, but the publisher nowhere encoded them. This is because of their field instructions, found in the FAST template, of constant.

In addition, there are a few private methods, to process incoming methods:

depth_feed_subscriber.h: DepthFeedSubscriber class declaration, continued
    void log_depth(book::Depth<5>& depth);
    bool handle_trade_message(const std::string& symbol,
                              uint64_t& seq_num,
                              uint64_t& timestamp,
                              QuickFAST::Messages::Message& msg);
    bool handle_depth_message(const std::string& symbol,
                              uint64_t& seq_num,
                              uint64_t& timestamp,
                              QuickFAST::Messages::Message& msg);
  };
} }

After initializing the static members comes the class constructor:

depth_feed_subscriber.cpp: DepthFeedSubscriber class implementation
namespace liquibook { namespace examples {

const uint64_t DepthFeedSubscriber::MSG_TYPE_DEPTH(11);
const uint64_t DepthFeedSubscriber::MSG_TYPE_TRADE(22);

using QuickFAST::ValueType;

DepthFeedSubscriber::DepthFeedSubscriber(
        const QuickFAST::Codecs::TemplateRegistryPtr& templates)
: decoder_(templates),
  expected_seq_(1)
{
}

The constructor passes the templates to the decoder, and initializes the expected sequence number. Next is the handle_reset() method, which is called when the connection to the publisher is reset:

depth_feed_subscriber.cpp: DepthFeedSubscriber class implementation, continued
void
DepthFeedSubscriber::handle_reset()
{
  expected_seq_ = 1;
}

This simple method just resets the expected sequence number to one. Next, is the meaty method handle_message():

depth_feed_subscriber.cpp: DepthFeedSubscriber class implementation, continued

bool
DepthFeedSubscriber::handle_message(BufferPtr& bp, size_t bytes_transferred)
{
  // Decode the message
  QuickFAST::Codecs::DataSourceBuffer source(bp->c_array(), bytes_transferred);
  QuickFAST::Codecs::SingleMessageConsumer consumer;
  QuickFAST::Codecs::GenericMessageBuilder builder(consumer);
  decoder_.decodeMessage(source, builder);
  QuickFAST::Messages::Message& msg(consumer.message());

  // Examine message contents
  uint64_t seq_num, msg_type, timestamp;
  const QuickFAST::StringBuffer* string_buffer;
  size_t bids_length, asks_length;
  std::string symbol;
  if (!msg.getUnsignedInteger(id_seq_num_, ValueType::UINT32, seq_num)) {
    std::cout << "Could not get seq num from msg" << std::endl;
    return false;
  }
  if (seq_num != expected_seq_) {
    std::cout << "ERROR: Got Seq num " << seq_num << ", expected " 
              << expected_seq_ << std::endl;
    return false;
  }
  if (!msg.getUnsignedInteger(id_msg_type_, ValueType::UINT32, msg_type)) {
    std::cout << "Could not get msg type from msg" << std::endl;
    return false;
  }
  if (!msg.getString(id_symbol_, ValueType::ASCII, string_buffer)) {
    std::cout << "Could not get symbol from msg" << std::endl;
    return false;
  }
  if (!msg.getUnsignedInteger(id_timestamp_, ValueType::UINT32, timestamp)) {
    std::cout << "Could not get timestamp from msg" << std::endl;
    return false;
  }
  bool result = false;
  symbol = (std::string)*string_buffer;
  switch (msg_type) {
  case MSG_TYPE_DEPTH:
    result = handle_depth_message(symbol, seq_num, timestamp, msg);
    break;
  case MSG_TYPE_TRADE:
    result = handle_trade_message(symbol, seq_num, timestamp, msg);
    break;
  default:
    std::cout << "ERROR: Unknown message type " << msg_type 
              << " seq num " << seq_num << std::endl;
    return false;
  }
  ++expected_seq_;
  return result;
}

This method first decodes the FAST message. This is done by stuffing the message contents into a QuickFAST buffer specific for decoding. Next, a message builder and consumer are created and associated, and the message is decoded into the builder. After this, the message is available from the consumer.

The common fields are then checked, by using the type-specific extractors on the QuickFAST::Messages::Message class, such as getUnsignedInteger(). This starts with the sequence number, which is validated against the expected sequence number. Next the message type, the symbol, and the timestamp are extracted. If any of these fail, the method exits with an error value.

Finally, since the message type is known, the proper message-type-specific handler is called.

The first helper method logs the contents of the depth for a security:

depth_feed_subscriber.cpp: DepthFeedSubscriber class implementation, continued
void
DepthFeedSubscriber::log_depth(book::Depth<5>& depth)
{
  book::DepthLevel* bid = depth.bids();
  book::DepthLevel* ask = depth.asks();
  printf("----------BID----------    ----------ASK----------\n");
  while (bid || ask) {
    if (bid && bid->order_count()) {
      printf("%8.2f %9d [%2d]", 
             (double)bid->price() / Order::precision_,
             bid->aggregate_qty(), bid->order_count());
      if (bid == depth.last_bid_level()) {
        bid = NULL;
      } else {
        ++bid;
      }
    } else {
      // Blank lines
      printf("                       ");
      bid = NULL;
    }

    if (ask && ask->order_count()) {
      printf("    %8.2f %9d [%2d]\n",
             (double)ask->price() / Order::precision_,
             ask->aggregate_qty(), ask->order_count());
      if (ask == depth.last_ask_level()) {
        ask = NULL;
      } else {
        ++ask;
      }
    } else {
      // Newline
      printf("\n");
      ask = NULL;
    }
  }
}

This method is complex, because it logs both bid and ask on the same line. In its loop, there could be a bid and an ask, only a bid, or only an ask.

The next helper handles a depth message:

depth_feed_subscriber.cpp: DepthFeedSubscriber class implementation, continued
bool
DepthFeedSubscriber::handle_depth_message(
  const std::string& symbol,
  uint64_t& seq_num,
  uint64_t& timestamp,
  QuickFAST::Messages::Message& msg)
{
  size_t bids_length, asks_length;
  std::cout << timestamp
            << " Got depth msg " << seq_num 
            << " for symbol " << symbol << std::endl;

  // Create or find depth
  std::pair<DepthMap::iterator, bool> results = depth_map_.insert(
      std::make_pair(symbol, book::Depth<5>()));
  book::Depth<5>& depth = results.first->second;

  if (msg.getSequenceLength(id_bids_, bids_length)) {
    for (size_t i = 0; i < bids_length; ++i) {
      const QuickFAST::Messages::MessageAccessor* accessor;
      if (msg.getSequenceEntry(id_bids_, i, accessor)) {
        uint64_t level_num, price, order_count, aggregate_qty;
        if (!accessor->getUnsignedInteger(id_level_num_, ValueType::UINT8,
                                         level_num)) {
          std::cout << "Could not get Bid level from depth msg" << std::endl;
          return false;
        }
        if (!accessor->getUnsignedInteger(id_price_, ValueType::UINT32,
                                         price)) {
          std::cout << "Could not get Bid price from depth msg" << std::endl;
          return false;
        }
        if (!accessor->getUnsignedInteger(id_order_count_, ValueType::UINT32,
                                         order_count)) {
          std::cout << "Could not get Bid count from depth msg" << std::endl;
          return false;
        }
        if (!accessor->getUnsignedInteger(id_size_, ValueType::UINT32,
                                         aggregate_qty)) {
          std::cout << "Could not get Bid agg qty  from depth msg" << std::endl;
          return false;
        }

        book::DepthLevel& level = depth.bids()[level_num];
        level.set(price, aggregate_qty, order_count);

      } else {
        std::cout << "Failed to get bid " << i << std::endl;
        return false;
      }
      msg.endSequenceEntry(id_bids_, i, accessor);
    }
  }
  if (msg.getSequenceLength(id_asks_, asks_length)) {
    for (size_t i = 0; i < asks_length; ++i) {
      const QuickFAST::Messages::MessageAccessor* accessor;
      if (msg.getSequenceEntry(id_asks_, i, accessor)) {
        uint64_t level_num, price, order_count, aggregate_qty;
        if (!accessor->getUnsignedInteger(id_level_num_, ValueType::UINT8,
                                         level_num)) {
          std::cout << "Could not get Ask level from depth msg " << std::endl;
          return false;
        }
        if (!accessor->getUnsignedInteger(id_price_, ValueType::UINT32,
                                         price)) {
          std::cout << "Could not get Ask price  from depth msg" << std::endl;
          return false;
        }
        if (!accessor->getUnsignedInteger(id_order_count_, ValueType::UINT32,
                                         order_count)) {
          std::cout << "Could not get Ask count from depth msg " << std::endl;
          return false;
        }
        if (!accessor->getUnsignedInteger(id_size_, ValueType::UINT32,
                                         aggregate_qty)) {
          std::cout << "Could not get Ask agg qty from depth msg " << std::endl;
          return false;
        }

        book::DepthLevel& level = depth.asks()[level_num];
        level.set(price, aggregate_qty, order_count);

      } else {
        std::cout << "Failed to get ask " << i << std::endl;
        return false;
      }
      msg.endSequenceEntry(id_asks_, i, accessor);
    }
  }
  log_depth(depth);
  return true;
}

This method has the common fields passed to it, and logs the symbol of the message. It then finds the proper depth object for the security, or creates one if this is the first depth message for the security.

Next the method must iterate through the sequence of changed bids. To do this, the sequence length is first extracted, and each entry accessed through an accessor.

Each bid field is then accessed using type-specific extractors, including the level number, the price, the number of orders, and the aggregate quantity. These updated values are then used to update the depth level for that security through a call to set(). Similar logic is performed for changed ask levels, and the resulting depth is logged using log_depth().

The final helper method handles a trade message:

depth_feed_subscriber.cpp: DepthFeedSubscriber class implementation, continued
bool
DepthFeedSubscriber::handle_trade_message(
  const std::string& symbol,
  uint64_t& seq_num,
  uint64_t& timestamp,
  QuickFAST::Messages::Message& msg)
{
  uint64_t qty, cost;
  // Get trade fields
  if (!msg.getUnsignedInteger(id_qty_, ValueType::UINT32, qty)) {
    std::cout << "Could not qty from trade msg" << std::endl;
    return false;
  }
  if (!msg.getUnsignedInteger(id_cost_, ValueType::UINT32, cost)) {
    std::cout << "Could not get cost from trade msg" << std::endl;
    return false;
  }

  double price = (double) cost / (qty * Order::precision_);
  std::cout << timestamp
            << " Got trade msg " << seq_num 
            << " for symbol " << symbol 
            << ": " << qty << "@" << price
            << std::endl;

  return true;
}

} }

Like handle_depth_message(), handle_trade_message() is passed the common fields. It extracts the trade quantity and cost, from which it calculates the price of the trade (after adjusting for the precision). The result is then logged.

Subscriber Initialization

Finally, the subscriber can create, initialize, and associate its components. The main() function of subscriber starts by creating the DepthFeedConnection and the DepthFeedSubscriber:

subscriber_main.cpp: main() function
int main(int argc, const char* argv[])
{
  // Create the connection
  liquibook::examples::DepthFeedConnection connection(argc, argv);

  // Create feed subscriber
  liquibook::examples::DepthFeedSubscriber feed(connection.get_templates());

  // Set up handlers
  liquibook::examples::MessageHandler msg_handler =
      boost::bind(&liquibook::examples::DepthFeedSubscriber::handle_message,
                  &feed, _1, _2);
  liquibook::examples::ResetHandler reset_handler =
      boost::bind(&liquibook::examples::DepthFeedSubscriber::handle_reset,
                  &feed);
  connection.set_message_handler(msg_handler);
  connection.set_reset_handler(reset_handler);

  // Connect to server
  connection.connect();
  connection.run();

  return 0;
}

Next, the handlers for messages and connection resets are set up, and finally, the subscriber connects to the publisher. This completes the subscriber application.

Running the Example

By default the publisher and subscriber connect on port 10003 on localhost. To run on a different port, add the -p <port> to each command line. The subscriber may also specify a remote host with the -h <host> option. To specify a different template file, or template file location, use the -t <filename> option.

For example:

  $ ./depth_feed_publisher -p 9999
  $ ./depth_feed_subscriber -p 9999

The subscriber produces output like this:

1369167639 Got depth msg 8182 for symbol GILD
----------BID----------    ----------ASK----------
   49.93       400 [ 1]       50.12       500 [ 1]
   49.66       300 [ 1]       50.13       500 [ 1]
   49.59      1000 [ 3]       50.14       500 [ 1]
   49.58       700 [ 1]       50.19       500 [ 1]
                              50.26       800 [ 1]
1369167640 Got depth msg 8183 for symbol XLNX
----------BID----------    ----------ASK----------
   37.40       400 [ 1]       37.60      1000 [ 2]
   37.36       400 [ 1]       37.61      1900 [ 3]
   37.35      1000 [ 1]       37.65       900 [ 3]
   37.28       500 [ 2]       37.70       600 [ 1]
   37.25      1700 [ 2]       37.71      1000 [ 2]
1369167641 Got depth msg 8184 for symbol GOLD
----------BID----------    ----------ASK----------
   78.30       300 [ 1]       78.43       300 [ 1]
   78.18       300 [ 1]       78.48       700 [ 1]
   78.17       900 [ 1]       78.64       600 [ 1]
   78.09       200 [ 1]       78.70       600 [ 1]
   78.06       900 [ 1]       79.12       500 [ 1]
1369167642 Got trade msg 8185 for symbol HSIC: 600@89.47
1369167642 Got trade msg 8186 for symbol HSIC: 200@89.49
1369167642 Got depth msg 8187 for symbol HSIC
----------BID----------    ----------ASK----------
   89.32       700 [ 1]       89.49       500 [ 1]
   89.09       500 [ 1]       89.52       200 [ 1]
   88.96       600 [ 1]       89.68       500 [ 1]
   88.82       200 [ 1]       89.76       900 [ 1]
   88.59       100 [ 1]       89.91       100 [ 1]

These updates show the full depth for each security updated, and not just the changed levels. The first three updates shows the depth for various securities, followed by some trades and a depth update for a fourth security. The trades go hand-in-hand with a depth update. Some depth updates cause trades, and some do not.

Note that a very good test of the incremental feed is to start two subscribers at different times, so that the first subscriber has handled a good number of messages before the second subscriber is started. The two should still be perfectly in sync, and produce identical output.

Summary

Although much code has been walked through in this example, very little custom code was related to the order book. Most of the code was either general networking code using ASIO, or QuickFAST code. In addition, the complexities of order book maintenance and depth-building are contained - most of the code shown was simple, or even trivial.

Liquibook makes it very simple to implement a limit order book matching engine, whether you are building an exchange, or are a trader simulating an exchange's matching capability.

References

Valid XHTML 1.0 Strict [Valid RSS]
RSS
Top
================================================ FILE: doc/settAug2013/settAug2013_files/paper.css ================================================ .listing, .figure { font-weight: bold; font-size: .9em; } .listing { margin-bottom: -16px; } ================================================ FILE: doc/settAug2013/styles/SETT.css ================================================ body { color: rgb(0, 0, 0); background-color: rgb(255, 255, 255); font-family: Verdana, sans-serif; margin-left: 0.25in; margin-right: 0.25in; min-width: 800px; } .header { float: left; width: 100%; } .left { float: left; } .right { float: right; } .lower_header { height: 34px; float: left; width: 100%; border-top: 1px solid lightgrey; border-bottom: 1px solid lightgrey; margin-bottom: 25px; } .social { margin-top: 6px; } span.rssRow { margin: 33px; background:transparent url(../images/rss_icon_12x12.gif) no-repeat scroll 0; padding-left:18px; } .headimage_container { position: relative; float: left; width: 100%; background: url('../images/Middle.png') repeat-x 35px; height: 123px; } .headerimage_leftlogo { position: absolute; left: 0; top: 0; background-color: white; width: 180px; height: 123px; } .headimage_left { position: absolute; background-color: white; top: 33px; left: 180px; width: 10px; } .headimage_right { position: absolute; background-color: white; width: 278px; right: 0; margin-top: 33px; } a.career { font-weight: bold; } code { font-family: "Courier New", monospace; } div.center { text-align: center; } h1 { text-align: center; } h2 { text-align: left; } h3 { text-align: left; } h4 { text-align: left; } h5 { text-align: left; } hr { height: 1px; color: rgb(17, 59, 86); background-color: transparent; } kbd { font-family: "Courier New", monospace; } p { text-align: justify; } p.author { text-align: center; } p.footer { text-align: justify; } pre { font-family: "Courier New", monospace; } .quicklinks { text-align: right; } .red { color: rgb(255, 0, 0); background-color: rgb(255, 255, 255); } .green { color: rgb(0, 128, 0); background-color: rgb(255, 255, 255); } .blue { color: rgb(0, 0, 192); background-color: rgb(255, 255, 255); } .code { background-color: #FFFFF0; border: dashed black 1px; padding-left: 10px; } .comment { color: rgb(128, 128, 128); font-weight: normal; font-style: italic; } ================================================ FILE: env.sh ================================================ SOURCE="${BASH_SOURCE[0]}" SOURCE_DIR=`dirname $SOURCE` if test "$LIQUIBOOK_ROOT" = ""; then READLINK='readlink' $READLINK --version >/dev/null 2>/dev/null if (( $? != 0 )); then echo "readlink does not exist or it does not support --version" echo "maybe it is not GNU readlink but BSD" echo "trying with greadlink..." READLINK='greadlink' fi $READLINK --version >/dev/null 2>/dev/null if (( $? != 0 )); then echo "greadlink does not exist or an error occurred" UNAME=`uname` if [[ $UNAME == "Darwin" ]]; then echo "You are running on a Mac OSX system." echo "Consider installing homebrew." echo "Then install coreutils." echo "# brew install coreutils" fi else echo "$READLINK found at `which $READLINK`." fi $READLINK -f $SOURCE_DIR if (( $? != 0 )); then echo "trying exporting LIQUIBOOK_ROOT by pwd." export LIQUIBOOK_ROOT=`pwd` echo "LIQUIBOOK_ROOT = $LIQUIBOOK_ROOT" else export LIQUIBOOK_ROOT=`$READLINK -f $SOURCE_DIR` fi fi if test "$QUICKFAST_ROOT" == ""; then export QUICKFAST_ROOT=`pwd`/noQuickFAST echo QuickFAST support disabled fi if test "$BOOST_VERSION" = ""; then echo Please export BOOST_VERSION, and BOOST_CFG echo you can also set BOOST_ROOT if it is not /usr/boost/BOOST_VERSION else if test "$BOOST_ROOT" = ""; then export BOOST_ROOT=/usr/boost/$BOOST_VERSION fi if test "$BOOST_ROOT_LIB" = ""; then export BOOST_ROOT_LIB=$BOOST_ROOT/lib fi if test "$BOOST_CFG" = ""; then export BOOST_CFG=-gcc62-mt-1_63 fi if test "$BOOST_STATIC_LIB_PREFIX" = ""; then export BOOST_STATIC_LIB_PREFIX= fi fi LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$LIQUIBOOK_ROOT/lib # CIAO is not used, set so MPC does not give warning export CIAO_ROOT=/dev/null ================================================ FILE: examples/.gitignore ================================================ ================================================ FILE: examples/mt_order_entry/Market.cpp ================================================ // Copyright (c) 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #include "Market.h" #include "Util.h" #include #include #include namespace { /////////////////////// // depth display helper void displayDepthLevel(std::ostream & out, const liquibook::book::DepthLevel & level) { out << "\tPrice " << level.price(); out << " Count: " << level.order_count(); out << " Quantity: " << level.aggregate_qty(); if(level.is_excess()) { out << " EXCESS"; } out << " Change id#: " << level.last_change(); out << std::endl; } void publishDepth(std::ostream & out, const orderentry::BookDepth & depth) { liquibook::book::ChangeId published = depth.last_published_change(); bool needTitle = true; // Iterate awkwardly auto pos = depth.bids(); auto back = depth.last_bid_level(); bool more = true; while(more) { if(pos->aggregate_qty() !=0 && pos->last_change() > published) { if(needTitle) { out << "\n\tBIDS:\n"; needTitle = false; } displayDepthLevel(out, *pos); } ++pos; more = pos != back; } needTitle = true; pos = depth.asks(); back = depth.last_ask_level(); more = true; while(more) { if(pos->aggregate_qty() !=0 && pos->last_change() > published) { if(needTitle) { out << "\n\tASKS:\n"; needTitle = false; } displayDepthLevel(out, *pos); } ++pos; more = pos != back; } } } namespace orderentry { uint32_t Market::orderIdSeed_ = 0; Market::Market(std::ostream * out) : logFile_(out) { } Market::~Market() { } const char * Market::prompt() { return "\t(B)uy\n\t(S)ell\n\t(M)odify\n\t(C)ancel\n\t(D)isplay\n"; } void Market::help(std::ostream & out) { out << "Buy: Create a new Buy order and add it to the book\n" << "Sell: Create a new Sell order and add it to the book\n" << " Arguments for BUY or SELL\n" << " \n" << " \n" << " or MARKET\n" << " AON (optional)\n" << " IOC (optional)\n" << " STOP (optional)\n" << " ; end of order\n" << std::endl; out << "Modify: Request Modify an existing order\n" << " Arguments:\n" << " \n" << " PRICE \n" << " QUANTITY \n" << " ; end of modify requet\n" << std::endl; out << "Cancel: Request cancel an existing order\n" << " Arguments:\n" << " \n" << " ; end of cancel request (optional)\n" << std::endl; out << "Display: Display status of an existing order\n" << " Arguments:\n" << " + (enable verbose display)\n" << " or or \"all\"\n" << std::endl; } bool Market::apply(const std::vector & tokens) { const std::string & command = tokens[0]; if(command == "BUY" || command == "B") { return doAdd("BUY", tokens, 1); } if(command == "SELL" || command == "S") { return doAdd("SELL", tokens, 1); } else if (command == "CANCEL" || command == "C") { return doCancel(tokens, 1); } else if(command == "MODIFY" || command == "M") { return doModify(tokens, 1); } else if(command == "DISPLAY" || command == "D") { return doDisplay(tokens, 1); } return false; } //////// // ADD bool Market::doAdd(const std::string & side, const std::vector & tokens, size_t pos) { ////////////// // Quantity liquibook::book::Quantity quantity; std::string qtyStr = nextToken(tokens, pos); if(!qtyStr.empty()) { quantity = toUint32(qtyStr); } else { quantity = promptForUint32("Quantity"); } // sanity check if(quantity == 0 || quantity > 1000000000) { out() << "--Expecting quantity" << std::endl; return false; } ////////////// // SYMBOL std::string symbol = nextToken(tokens, pos); if(symbol.empty()) { symbol = promptForString("Symbol"); } if(!symbolIsDefined(symbol)) { if(symbol[0] == '+' || symbol[0] == '!') { bool useDepth = symbol[0] == '!'; symbol = symbol.substr(1); if(!symbolIsDefined(symbol)) { addBook(symbol, useDepth); } } else { std::string bookType; while(bookType != "S" && bookType != "D" && bookType != "N") { bookType = promptForString( "New Symbol " + symbol + ". \nAdd [S]imple book, or [D]epth book, or 'N' to cancel request.\n[SDN}"); } if(bookType == "N") { out() << "Request ignored" << std::endl; return false; } bool useDepth = bookType == "D"; addBook(symbol, useDepth); } } /////////////// // PRICE uint32_t price = 0; std::string priceStr = nextToken(tokens, pos); if(!priceStr.empty()) { price = stringToPrice(priceStr); } else { price = promptForPrice("Limit Price or MKT"); } if(price > 10000000) { out() << "--Expecting price or MARKET" << std::endl; return false; } ////////////////////////// // OPTIONS: AON, IOC STOP bool aon = false; bool ioc = false; liquibook::book::Price stopPrice = 0; bool go = false; while(!go) { bool prompted = false; bool optionOk = false; std::string option = nextToken(tokens, pos); if(option.empty()) { prompted = true; option = promptForString("AON, or IOC, or STOP, or END"); } if(option == ";" || option == "E" || option == "END") { go = true; optionOk = true; } else if(option == "A" || option == "AON") { aon = true; optionOk = true; } else if(option == "I" || option == "IOC") { ioc = true; optionOk = true; } else if(option == "S" || option == "STOP") { std::string stopstr = nextToken(tokens, pos); if(!stopstr.empty()) { stopPrice = stringToPrice(stopstr); } else { stopPrice = promptForUint32("Stop Price"); prompted = true; } optionOk = stopPrice <= 10000000; } if(!optionOk) { out() << "Unknown option " << option << std::endl; if(!prompted) { out() << "--Expecting AON IOC STOP or END" << std::endl; return false; } } } std::string orderId = std::to_string(++orderIdSeed_); OrderPtr order = std::make_shared(orderId, side == "BUY", quantity, symbol, price, stopPrice, aon, ioc); const liquibook::book::OrderConditions AON(liquibook::book::oc_all_or_none); const liquibook::book::OrderConditions IOC(liquibook::book::oc_immediate_or_cancel); const liquibook::book::OrderConditions NOC(liquibook::book::oc_no_conditions); const liquibook::book::OrderConditions conditions = (aon ? AON : NOC) | (ioc ? IOC : NOC); auto book = findBook(symbol); if(!book) { out() << "--No order book for symbol" << symbol << std::endl; return false; } order->onSubmitted(); out() << "ADDING order: " << *order << std::endl; orders_[orderId] = order; book->add(order, conditions); return true; } /////////// // CANCEL bool Market::doCancel(const std::vector & tokens, size_t position) { OrderPtr order; OrderBookPtr book; if(!findExistingOrder(tokens, position, order, book)) { return false; } out() << "Requesting Cancel: " << *order << std::endl; book->cancel(order); return true; } /////////// // MODIFY bool Market::doModify(const std::vector & tokens, size_t position) { OrderPtr order; OrderBookPtr book; if(!findExistingOrder(tokens, position, order, book)) { return false; } ////////////// // options ////////////////////////// // OPTIONS: PRICE (price) ; QUANTITY (delta) int64_t quantityChange = liquibook::book::SIZE_UNCHANGED; liquibook::book::Price price = liquibook::book::PRICE_UNCHANGED; bool go = false; while(!go) { bool prompted = false; bool optionOk = false; std::string option = nextToken(tokens, position); if(option.empty()) { prompted = true; option = promptForString("PRICE, or QUANTITY, or END"); } if(option == ";" || option == "E" || option == "END") { go = true; optionOk = true; } else if(option == "P" || option == "PRICE") { uint32_t newPrice = INVALID_UINT32; std::string priceStr = nextToken(tokens, position); if(priceStr.empty()) { newPrice = promptForUint32("New Price"); } else { newPrice = toUint32(priceStr); } if(newPrice > 0 && newPrice != INVALID_UINT32) { price = newPrice; optionOk = true; } else { out() << "Invalid price" << std::endl; } } else if(option == "Q" || option == "QUANTITY") { int32_t qty = INVALID_INT32; std::string qtyStr = nextToken(tokens, position); if(qtyStr.empty()) { qty = promptForInt32("Change in quantity"); } else { qty = toInt32(qtyStr); } if(qty != INVALID_INT32) { quantityChange = qty; optionOk = true; } else { out() << "Invalid quantity change." << std::endl; } } if(!optionOk) { out() << "Unknown or invalid option " << option << std::endl; if(!prompted) { out() << "--Expecting PRICE , or QUANTITY , or END" << std::endl; return false; } } } book->replace(order, quantityChange, price); out() << "Requested Modify" ; if(quantityChange != liquibook::book::SIZE_UNCHANGED) { out() << " QUANTITY += " << quantityChange; } if(price != liquibook::book::PRICE_UNCHANGED) { out() << " PRICE " << price; } out() << std::endl; return true; } /////////// // DISPLAY bool Market::doDisplay(const std::vector & tokens, size_t pos) { bool verbose = false; // see if first token could be an order id. // todo: handle prompted imput! std::string parameter = nextToken(tokens, pos); if(parameter.empty()) { parameter = promptForString("+ or #OrderId or -orderOffset or symbol or \"ALL\""); } else { --pos; // Don't consume this parameter yet. } if(parameter[0] == '+') { verbose = true; if(parameter.length() > 1) { parameter = parameter.substr(1); } else { ++pos; // now we can consume the first parameter (whether or not it's there!) parameter = nextToken(tokens, pos); if(parameter.empty()) { parameter = promptForString("#OrderId or -orderOffset or symbol or \"ALL\""); } else { --pos; // Don't consume this parameter yet. } } } if(parameter[0] == '#' || parameter[0] == '-' || isdigit(parameter[0])) { OrderPtr order; OrderBookPtr book; if(findExistingOrder(parameter, order, book)) { out() << *order << std::endl; return true; } } // Not an order id. Try for a symbol: std::string symbol = parameter; if(symbolIsDefined(symbol)) { for(auto pOrder = orders_.begin(); pOrder != orders_.end(); ++pOrder) { const OrderPtr & order = pOrder->second; if(order->symbol() == symbol) { out() << order->verbose(verbose) << std::endl; order->verbose(false); } } auto book = findBook(symbol); if(!book) { out() << "--No order book for symbol" << symbol << std::endl; } else { book->log(out()); } return true; } else if( symbol == "ALL") { for(auto pOrder = orders_.begin(); pOrder != orders_.end(); ++pOrder) { const OrderPtr & order = pOrder->second; out() << order->verbose(verbose) << std::endl; order->verbose(false); } for(auto pBook = books_.begin(); pBook != books_.end(); ++pBook) { out() << "Order book for " << pBook->first << std::endl; pBook->second->log(out()); } return true; } else { out() << "--Unknown symbol: " << symbol << std::endl; } return false; } ///////////////////////////// // Order book interactions bool Market::symbolIsDefined(const std::string & symbol) { auto book = books_.find(symbol); return book != books_.end(); } OrderBookPtr Market::addBook(const std::string & symbol, bool useDepthBook) { OrderBookPtr result; if(useDepthBook) { out() << "Create new depth order book for " << symbol << std::endl; DepthOrderBookPtr depthBook = std::make_shared(symbol); depthBook->set_bbo_listener(this); depthBook->set_depth_listener(this); result = depthBook; } else { out() << "Create new order book for " << symbol << std::endl; result = std::make_shared(symbol); } result->set_order_listener(this); result->set_trade_listener(this); result->set_order_book_listener(this); books_[symbol] = result; return result; } OrderBookPtr Market::findBook(const std::string & symbol) { OrderBookPtr result; auto entry = books_.find(symbol); if(entry != books_.end()) { result = entry->second; } return result; } bool Market::findExistingOrder(const std::vector & tokens, size_t & position, OrderPtr & order, OrderBookPtr & book) { //////////////// // Order ID std::string orderId = nextToken(tokens, position); trim(orderId); if(orderId.empty()) { orderId = promptForString("Order Id#"); trim(orderId); } // discard leading # if any if(orderId[0] == '#') { orderId = orderId.substr(1); trim(orderId); if(orderId.empty()) { out() << "--Expecting #orderID" << std::endl; return false; } } if(orderId[0] == '-') // relative addressing { int32_t orderOffset = toInt32(orderId); if(orderOffset == INVALID_INT32) { out() << "--Expecting orderID or offset" << std::endl; return false; } uint32_t orderNumber = orderIdSeed_ + 1 + orderOffset; orderId = std::to_string(orderNumber); } return findExistingOrder(orderId, order, book); } bool Market::findExistingOrder(const std::string & orderId, OrderPtr & order, OrderBookPtr & book) { auto orderPosition = orders_.find(orderId); if(orderPosition == orders_.end()) { out() << "--Can't find OrderID #" << orderId << std::endl; return false; } order = orderPosition->second; std::string symbol = order->symbol(); book = findBook(symbol); if(!book) { out() << "--No order book for symbol" << symbol << std::endl; return false; } return true; } ///////////////////////////////////// // Implement OrderListener interface void Market::on_accept(const OrderPtr& order) { order->onAccepted(); out() << "\tAccepted: " <<*order<< std::endl; } void Market::on_reject(const OrderPtr& order, const char* reason) { order->onRejected(reason); out() << "\tRejected: " <<*order<< ' ' << reason << std::endl; } void Market::on_fill(const OrderPtr& order, const OrderPtr& matched_order, liquibook::book::Quantity fill_qty, liquibook::book::Cost fill_cost) { order->onFilled(fill_qty, fill_cost); matched_order->onFilled(fill_qty, fill_cost); out() << (order->is_buy() ? "\tBought: " : "\tSold: ") << fill_qty << " Shares for " << fill_cost << ' ' <<*order<< std::endl; out() << (matched_order->is_buy() ? "\tBought: " : "\tSold: ") << fill_qty << " Shares for " << fill_cost << ' ' << *matched_order << std::endl; } void Market::on_cancel(const OrderPtr& order) { order->onCancelled(); out() << "\tCanceled: " << *order<< std::endl; } void Market::on_cancel_reject(const OrderPtr& order, const char* reason) { order->onCancelRejected(reason); out() << "\tCancel Reject: " <<*order<< ' ' << reason << std::endl; } void Market::on_replace(const OrderPtr& order, const int64_t& size_delta, liquibook::book::Price new_price) { order->onReplaced(size_delta, new_price); out() << "\tModify " ; if(size_delta != liquibook::book::SIZE_UNCHANGED) { out() << " QUANTITY += " << size_delta; } if(new_price != liquibook::book::PRICE_UNCHANGED) { out() << " PRICE " << new_price; } out() <<*order<< std::endl; } void Market::on_replace_reject(const OrderPtr& order, const char* reason) { order->onReplaceRejected(reason); out() << "\tReplace Reject: " <<*order<< ' ' << reason << std::endl; } //////////////////////////////////// // Implement TradeListener interface void Market::on_trade(const OrderBook* book, liquibook::book::Quantity qty, liquibook::book::Cost cost) { out() << "\tTrade: " << qty << ' ' << book->symbol() << " Cost " << cost << std::endl; } ///////////////////////////////////////// // Implement OrderBookListener interface void Market::on_order_book_change(const OrderBook* book) { out() << "\tBook Change: " << ' ' << book->symbol() << std::endl; } ///////////////////////////////////////// // Implement BboListener interface void Market::on_bbo_change(const DepthOrderBook * book, const BookDepth * depth) { out() << "\tBBO Change: " << ' ' << book->symbol() << (depth->changed() ? " Changed" : " Unchanged") << " Change Id: " << depth->last_change() << " Published: " << depth->last_published_change() << std::endl; } ///////////////////////////////////////// // Implement DepthListener interface void Market::on_depth_change(const DepthOrderBook * book, const BookDepth * depth) { out() << "\tDepth Change: " << ' ' << book->symbol(); out() << (depth->changed() ? " Changed" : " Unchanged") << " Change Id: " << depth->last_change() << " Published: " << depth->last_published_change(); publishDepth(out(), *depth); out() << std::endl; } } // namespace orderentry ================================================ FILE: examples/mt_order_entry/Market.h ================================================ // Copyright (c) 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #pragma once #include #include "Order.h" #include #include #include #include #include #include namespace orderentry { typedef liquibook::book::OrderBook OrderBook; typedef std::shared_ptr OrderBookPtr; typedef liquibook::book::DepthOrderBook DepthOrderBook; typedef std::shared_ptr DepthOrderBookPtr; typedef liquibook::book::Depth<> BookDepth; class Market : public liquibook::book::OrderListener , public liquibook::book::TradeListener , public liquibook::book::OrderBookListener , public liquibook::book::BboListener , public liquibook::book::DepthListener { typedef std::map OrderMap; typedef std::map SymbolToBookMap; public: Market(std::ostream * logFile = &std::cout); ~Market(); /// @brief What to display to user when requesting input static const char * prompt(); /// @brief Help for user's input static void help(std::ostream & out = std::cout); /// @brief Apply a user command that has been parsed into tokens. bool apply(const std::vector & tokens); public: ///////////////////////////////////// // Implement OrderListener interface /// @brief callback for an order accept virtual void on_accept(const OrderPtr& order); /// @brief callback for an order reject virtual void on_reject(const OrderPtr& order, const char* reason); /// @brief callback for an order fill /// @param order the inbound order /// @param matched_order the matched order /// @param fill_qty the quantity of this fill /// @param fill_cost the cost of this fill (qty * price) virtual void on_fill(const OrderPtr& order, const OrderPtr& matched_order, liquibook::book::Quantity fill_qty, liquibook::book::Cost fill_cost); /// @brief callback for an order cancellation virtual void on_cancel(const OrderPtr& order); /// @brief callback for an order cancel rejection virtual void on_cancel_reject(const OrderPtr& order, const char* reason); /// @brief callback for an order replace /// @param order the replaced order /// @param size_delta the change to order quantity /// @param new_price the updated order price virtual void on_replace(const OrderPtr& order, const int64_t& size_delta, liquibook::book::Price new_price); /// @brief callback for an order replace rejection virtual void on_replace_reject(const OrderPtr& order, const char* reason); //////////////////////////////////// // Implement TradeListener interface /// @brief callback for a trade /// @param book the order book of the fill (not defined whether this is before /// or after fill) /// @param qty the quantity of this fill /// @param cost the cost of this fill (qty * price) virtual void on_trade(const OrderBook* book, liquibook::book::Quantity qty, liquibook::book::Cost cost); ///////////////////////////////////////// // Implement OrderBookListener interface /// @brief callback for change anywhere in order book virtual void on_order_book_change(const OrderBook* book); ///////////////////////////////////////// // Implement BboListener interface void on_bbo_change(const DepthOrderBook * book, const BookDepth * depth); ///////////////////////////////////////// // Implement DepthListener interface void on_depth_change(const DepthOrderBook * book, const BookDepth * depth); private: //////////////////////////////////// // Command implementatiokns bool doAdd(const std::string & side, const std::vector & tokens, size_t pos); bool doCancel(const std::vector & tokens, size_t position); bool doModify(const std::vector & tokens, size_t position); bool doDisplay(const std::vector & tokens, size_t position); //////////////////////// // Order book interactions bool symbolIsDefined(const std::string & symbol); OrderBookPtr findBook(const std::string & symbol); OrderBookPtr addBook(const std::string & symbol, bool useDepthBook); bool findExistingOrder(const std::vector & tokens, size_t & position, OrderPtr & order, OrderBookPtr & book); bool findExistingOrder(const std::string & orderId, OrderPtr & order, OrderBookPtr & book); std::ostream & out() { return *logFile_; } private: static uint32_t orderIdSeed_; std::ostream * logFile_; OrderMap orders_; SymbolToBookMap books_; }; } // namespace orderentry ================================================ FILE: examples/mt_order_entry/Order.cpp ================================================ // Copyright (c) 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #include "Order.h" #include namespace orderentry { Order::Order(const std::string & id, bool buy_side, liquibook::book::Quantity quantity, std::string symbol, liquibook::book::Price price, liquibook::book::Price stopPrice, bool aon, bool ioc) : id_(id) , buy_side_(buy_side) , symbol_(symbol) , quantity_(quantity) , price_(price) , stopPrice_(stopPrice) , ioc_(ioc) , aon_(aon) , quantityFilled_(0) , quantityOnMarket_(0) , fillCost_(0) , verbose_(false) { } std::string Order::order_id() const { return id_; } bool Order::is_limit() const { return price() != 0; } bool Order::is_buy() const { return buy_side_; } bool Order::all_or_none() const { return aon_; } bool Order::immediate_or_cancel() const { return ioc_; } std::string Order::symbol() const { return symbol_; } liquibook::book::Price Order::price() const { return price_; } liquibook::book::Quantity Order::order_qty() const { return quantity_; } liquibook::book::Price Order::stop_price() const { return stopPrice_; } uint32_t Order::quantityOnMarket() const { return quantityOnMarket_; } uint32_t Order::quantityFilled() const { return quantityFilled_; } uint32_t Order::fillCost() const { return fillCost_; } const Order::History & Order::history() const { return history_; } const Order::StateChange & Order::currentState() const { return history_.back(); } Order & Order::verbose(bool verbose) { verbose_ = verbose; return *this; } bool Order::isVerbose() const { return verbose_; } void Order::onSubmitted() { std::stringstream msg; msg << (is_buy() ? "BUY " : "SELL ") << quantity_ << ' ' << symbol_ << " @"; if( price_ == 0) { msg << "MKT"; } else { msg << price_; } history_.emplace_back(Submitted, msg.str()); } void Order::onAccepted() { quantityOnMarket_ = quantity_; history_.emplace_back(Accepted); } void Order::onRejected(const char * reason) { history_.emplace_back(Rejected, reason); } void Order::onFilled( liquibook::book::Quantity fill_qty, liquibook::book::Cost fill_cost) { quantityOnMarket_ -= fill_qty; fillCost_ += fill_cost; std::stringstream msg; msg << fill_qty << " for " << fill_cost; history_.emplace_back(Filled, msg.str()); } void Order::onCancelRequested() { history_.emplace_back(CancelRequested); } void Order::onCancelled() { quantityOnMarket_ = 0; history_.emplace_back(Cancelled); } void Order::onCancelRejected(const char * reason) { history_.emplace_back(CancelRejected, reason); } void Order::onReplaceRequested( const int32_t& size_delta, liquibook::book::Price new_price) { std::stringstream msg; if(size_delta != liquibook::book::SIZE_UNCHANGED) { msg << "Quantity change: " << size_delta << ' '; } if(new_price != liquibook::book::PRICE_UNCHANGED) { msg << "New Price " << new_price; } history_.emplace_back(ModifyRequested, msg.str()); } void Order::onReplaced(const int32_t& size_delta, liquibook::book::Price new_price) { std::stringstream msg; if(size_delta != liquibook::book::SIZE_UNCHANGED) { quantity_ += size_delta; quantityOnMarket_ += size_delta; msg << "Quantity change: " << size_delta << ' '; } if(new_price != liquibook::book::PRICE_UNCHANGED) { price_ = new_price; msg << "New Price " << new_price; } history_.emplace_back(Modified, msg.str()); } void Order::onReplaceRejected(const char * reason) { history_.emplace_back(ModifyRejected, reason); } std::ostream & operator << (std::ostream & out, const Order::StateChange & event) { out << "{"; switch(event.state_) { case Order::Submitted: out << "Submitted "; break; case Order::Rejected: out << "Rejected "; break; case Order::Accepted: out << "Accepted "; break; case Order::ModifyRequested: out << "ModifyRequested "; break; case Order::ModifyRejected: out << "ModifyRejected "; break; case Order::Modified: out << "Modified "; break; case Order::PartialFilled: out << "PartialFilled "; break; case Order::Filled: out << "Filled "; break; case Order::CancelRequested: out << "CancelRequested "; break; case Order::CancelRejected: out << "CancelRejected "; break; case Order::Cancelled: out << "Cancelled "; break; case Order::Unknown: out << "Unknown "; break; } out << event.description_; out << "}"; return out; } std::ostream & operator << (std::ostream & out, const Order & order) { out << "[#" << order.order_id(); out << ' ' << (order.is_buy() ? "BUY" : "SELL"); out << ' ' << order.order_qty(); out << ' ' << order.symbol(); if(order.price() == 0) { out << " MKT"; } else { out << " $" << order.price(); } if(order.stop_price() != 0) { out << " STOP " << order.stop_price(); } out << (order.all_or_none() ? " AON" : "") << (order.immediate_or_cancel() ? " IOC" : ""); auto onMarket = order.quantityOnMarket(); if(onMarket != 0) { out << " Open: " << onMarket; } auto filled = order.quantityFilled(); if(filled != 0) { out << " Filled: " << filled; } auto cost = order.fillCost(); if(cost != 0) { out << " Cost: " << cost; } if(order.isVerbose()) { const Order::History & history = order.history(); for(auto event = history.begin(); event != history.end(); ++event) { out << "\n\t" << *event; } } else { out << " Last Event:" << order.currentState(); } out << ']'; return out; } } ================================================ FILE: examples/mt_order_entry/Order.h ================================================ // Copyright (c) 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #pragma once #include "OrderFwd.h" #include #include #include namespace orderentry { class Order { public: enum State{ Submitted, Rejected, // Terminal state Accepted, ModifyRequested, ModifyRejected, Modified, PartialFilled, Filled, // Terminal State CancelRequested, CancelRejected, Cancelled, // Terminal state Unknown }; struct StateChange { State state_; std::string description_; StateChange() : state_(Unknown) {} StateChange(State state, const std::string & description = "") : state_(state) , description_(description) {} }; typedef std::vector History; public: Order(const std::string & id, bool buy_side, liquibook::book::Quantity quantity, std::string symbol, liquibook::book::Price price, liquibook::book::Price stopPrice, bool aon, bool ioc); ////////////////////////// // Implement the // liquibook::book::order // concept. /// @brief is this a limit order? bool is_limit() const; /// @brief is this order a buy? bool is_buy() const; /// @brief get the price of this order, or 0 if a market order liquibook::book::Price price() const; /// @brief get the stop price (if any) for this order. /// @returns the stop price or zero if not a stop order liquibook::book::Price stop_price() const; /// @brief get the quantity of this order liquibook::book::Quantity order_qty() const; /// @brief if no trades should happen until the order /// can be filled completely. /// Note: one or more trades may be used to fill the order. virtual bool all_or_none() const; /// @brief After generating as many trades as possible against /// orders already on the market, cancel any remaining quantity. virtual bool immediate_or_cancel() const; std::string symbol() const; std::string order_id() const; uint32_t quantityFilled() const; uint32_t quantityOnMarket() const; uint32_t fillCost() const; Order & verbose(bool verbose = true); bool isVerbose()const; const History & history() const; const StateChange & currentState() const; /////////////////////////// // Order life cycle events void onSubmitted(); void onAccepted(); void onRejected(const char * reason); void onFilled( liquibook::book::Quantity fill_qty, liquibook::book::Cost fill_cost); void onCancelRequested(); void onCancelled(); void onCancelRejected(const char * reason); void onReplaceRequested( const int32_t& size_delta, liquibook::book::Price new_price); void onReplaced(const int32_t& size_delta, liquibook::book::Price new_price); void onReplaceRejected(const char * reaseon); private: std::string id_; bool buy_side_; std::string symbol_; liquibook::book::Quantity quantity_; liquibook::book::Price price_; liquibook::book::Price stopPrice_; bool aon_; bool ioc_; liquibook::book::Quantity quantityFilled_; int32_t quantityOnMarket_; uint32_t fillCost_; std::vector history_; bool verbose_; }; std::ostream & operator << (std::ostream & out, const Order & order); std::ostream & operator << (std::ostream & out, const Order::StateChange & event); } ================================================ FILE: examples/mt_order_entry/OrderFwd.h ================================================ // Copyright (c) 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #pragma once #include namespace orderentry { class Order; typedef std::shared_ptr OrderPtr; } ================================================ FILE: examples/mt_order_entry/TestOneAonBidTwoAsk.script ================================================ ## Copyright (c) 2017 Object Computing, Inc. ## All rights reserved. ## See the file license.txt for licensing information. # Run the One AON bid, two AON ask test BUY 300 +IBM 1251 AON; SELL 100 IBM 1251; SELL 200 IBM 1251; #expect execution, but currently the test fails D + ALL ================================================ FILE: examples/mt_order_entry/Util.cpp ================================================ // Copyright (c) 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #include "Util.h" #include #include #include #include namespace orderentry { std::string nextToken(const std::vector & tokens, size_t & pos) { if(pos < tokens.size()) { return tokens[pos++]; } return ""; } uint32_t toUint32(const std::string & input) { char * end; uint32_t value = strtoul(input.c_str(), &end, 10); if(*end != '\0') { value = INVALID_UINT32; } return value; } uint32_t toInt32(const std::string & input) { char * end; uint32_t value = strtol(input.c_str(), &end, 10); if(*end != '\0') { value = INVALID_INT32; } return value; } liquibook::book::Price stringToPrice(const std::string & str) { if(str == "MARKET" || str == "MKT") { return 0; } else { return toUint32(str); } } std::string promptForString(const std::string & prompt, bool uppercase) { std::cout << "\n" << prompt << ": " << std::flush; std::string input; std::getline(std::cin, input); if(uppercase) { std::transform(input.begin(), input.end(), input.begin(), toupper); } return input; } liquibook::book::Price promptForPrice(const std::string & prompt) { std::string str = promptForString(prompt); return stringToPrice(str); } uint32_t promptForUint32(const std::string & prompt) { std::cout << "\n" << prompt << ": " << std::flush; std::string input; std::getline(std::cin, input); return toUint32(input); } int32_t promptForInt32(const std::string & prompt) { std::cout << "\n" << prompt << ": " << std::flush; std::string input; std::getline(std::cin, input); return toInt32(input); } bool promptForYesNo(const std::string & prompt) { while(true) { std::string input = promptForString(prompt); if(input == "Y" || input == "YES" || input == "T" || input == "TRUE") { return true; } if(input == "N" || input == "NO" || input == "F" || input == "FALSE") { return false; } } } // trim from start (in place) std::string & ltrim(std::string &s) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); return s; } // trim from end (in place) std::string & rtrim(std::string &s) { s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); return s; } // trim from both ends (in place) std::string & trim(std::string &s) { return ltrim(rtrim(s)); } // trim from start (copying) std::string ltrimmed(std::string s) { ltrim(s); return s; } // trim from end (copying) std::string rtrimmed(std::string s) { rtrim(s); return s; } // trim from both ends (copying) std::string trimmed(std::string s) { trim(s); return s; } } ================================================ FILE: examples/mt_order_entry/Util.h ================================================ // Copyright (c) 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. /// @brief Command parsing helpers #pragma once #include #include #include namespace orderentry { static const uint32_t INVALID_UINT32 = UINT32_MAX; static const int32_t INVALID_INT32 = INT32_MAX; /// @brief Parse a string into tokens breaking on delimiters. /// @param input The string to parse. /// @param delimiter A set of delimiter characters. /// @param[out] tokens the tokens parsed from the string. template void split(const INPUT_STRING & input, const DELIMITER_STRING & delimiter, STRING_CONTAINER & tokens) { size_t pos = 0; size_t end = input.length(); while(pos < end) { auto last = input.find_first_of(delimiter, pos); if(last == std::string::npos) { last = end; } tokens.push_back(input.substr(pos, last - pos)); pos = ++last; } } /// @brief Get the next token from a vector of tokens. /// @param tokens The vector of tokens /// @param[inout] pos the current position in the vector. Will be updated if a token is found. /// @return the next token or an empty string if no more tokens. std::string nextToken(const std::vector & tokens, size_t & pos); /// @brief Convert a string to uint32 /// @param input the input string. /// @return the converted number or INVALID_UINT32 if the string ill-formed. uint32_t toUint32(const std::string & input); /// @brief Convert a string to int32 /// @param input the input string. /// @return the converted number or INVALID_INT32 if the string ill-formed. uint32_t toInt32(const std::string & input); /// @brief Convert a string to Price /// @param input the input string which may be a number or "MARKET", "MKT", etc. /// @return the converted price (MARKET is 0) or INVALID_UINT32 if the string ill-formed. liquibook::book::Price stringToPrice(const std::string & input); /// @brief Display a prompt and ask the console user for a string. /// @param prompt What to display /// @returns what the user typed. std::string promptForString(const std::string & prompt, bool uppercase = true); /// @brief Display a prompt and ask the console user for a price. /// @param prompt What to display /// @returns what the user typed converted to a price. liquibook::book::Price promptForPrice(const std::string & prompt); /// @brief Display a prompt and ask the console user for a uint32. /// @param prompt What to display /// @returns what the user typed converted to a uint32. uint32_t promptForUint32(const std::string & prompt); /// @brief Display a prompt and ask the console user for a int32. /// @param prompt What to display /// @returns what the user typed converted to a int32. int32_t promptForInt32(const std::string & prompt); /// @brief Display a prompt and ask the console user for "yes" or "no" /// @param prompt What to display /// @returns what the user typed converted to a bool. bool promptForYesNo(const std::string & prompt); /// @brief Remove whitespace from the start of a string (in place) /// @param[inout] s is the string to be trimmed in place. /// @returns a reference to the string. std::string & ltrim(std::string &s); /// @brief Remove whitespace from the end of a string (in place) /// @param[inout] s is the string to be trimmed in place. /// @returns a reference to the string. std::string & rtrim(std::string &s); /// @brief Remove whitespace from both ends of a string (in place) /// @param[inout] s is the string to be trimmed in place. /// @returns a reference to the string. std::string & trim(std::string &s); /// @brief Remove whitespace from the start of a constant string /// @param s is the string to be trimmed. /// @return the string after trimming. std::string ltrimmed(std::string s); /// @brief Remove whitespace from the end of a constant string /// @param s is the string to be trimmed /// @return the string after trimming. std::string rtrimmed(std::string s); /// @brief Remove whitespace from both ends of a constant string /// @param s is the string to be trimmed. /// @return the string after trimming. std::string trimmed(std::string s); } ================================================ FILE: examples/mt_order_entry/mt_order_entry.mpc ================================================ // Copyright (c) 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. project(*) : liquibook_book, liquibook_simple, liquibook_exe { requires += example_manual exename = * } ================================================ FILE: examples/mt_order_entry/mt_order_entry_main.cpp ================================================ // Copyright (c) 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #include "Market.h" #include "Util.h" #include #include #include #include #include #include #include #include using namespace orderentry; int main(int argc, const char * argv[]) { bool done = false; bool prompt = true; bool interactive = true; bool fileActive = false; std::ostream * log = &std::cout; std::ifstream commandFile; std::ofstream logFile; if(argc > 1) { std::string filename = argv[1]; if(filename != "-") { commandFile.open(filename); if(!commandFile.good()) { std::cerr << "Can't open command file " << filename << ". Exiting." << std::endl; return -1; } interactive = false; fileActive = true; } } if(argc > 2) { const char * filename = argv[2]; logFile.open(filename); if(!logFile.good()) { std::cerr << "Can't open log file " << filename << ". Exiting." << std::endl; return -1; } log = & logFile; } Market market(log); while( !done) { std::string input; if(fileActive) { std::getline(commandFile, input); if(!commandFile.good()) { if(interactive) { input = "# Switching to console input."; fileActive = false; } else { input = "# end of command file."; done = true; } } // if it came from a file, echo it to the log if(input.substr(0,2) != "##") // don't log ## comments. { *log << input << std::endl; } } else { if(prompt) { std::cout << "Action[" << Market::prompt() << "\t(?) help for more options and detail.\n" << "\t(Quit) ]\n"; prompt = false; } std::cout << "> " << std::flush; std::getline(std::cin, input); } std::transform(input.begin(), input.end(), input.begin(), toupper); if(log != &std::cout && !fileActive) { if(input.substr(0,2) != "##") // don't log ## comments. { *log << input << std::endl; } } // if input ends in a ';' be sure there's a space before it to simplify parsing. if(input.length() > 1) { if(input.back() == ';') { input.pop_back(); if(input.back() == ' ') { input.pop_back(); } input.append(" ;"); } } std::vector< std::string> words; split(input," \t\v\n\r", words); if(!words.empty()) { const std::string command = words[0]; if(command == "QUIT") { done = true; } else if(command[0] == '#') { // nothing } else if(command == "F" || command == "FILE") { if(fileActive) { std::cout << "Only one input file at a time can be open." << std::endl; } else { std::cout << "Command file name: " << std::flush; std::string filename; std::getline(std::cin, filename); commandFile.open(filename); if(commandFile.good()) { fileActive = true; } else { std::cout << "Cannot open " << filename << std::endl; } } } else if(command == "?" || command == "HELP") { market.help(); *log << "(F)ile Open or Close a command input file\n" << "\tArguments\n" << "\t\t Required if no file is open. Must not appear if file is open.\n"; *log << "QUIT Exit from this program.\n"; bool prompt = true; } else if(!market.apply(words)) { std::cerr << "Cannot process command"; for(auto word = words.begin(); word != words.end(); ++ word) { std::cerr << ' ' << *word; } std::cerr << std::endl; bool prompt = true; } } } return 0; } ================================================ FILE: examples/mt_order_entry/order_entry.script ================================================ # Copyright (c) 2017 Object Computing, Inc. # All rights reserved. # See the file license.txt for licensing information. ## A simple test script to use with mt_order_entry. ## To use it: make this file's name the first argument on the command line, or ## type "FILE filename" at the interactive prompt. BUY 100 !IBM 50; SELL 100 IBM 50; D + ALL ================================================ FILE: examples/mt_order_entry/teststoporders.script ================================================ # Script to test stop orders # set market price to 56 buy 100 +ibm mkt; sell 100 ibm 56; # enter seed orders that won't trade with each other buy 100 ibm 53; sell 100 ibm 58; # add stop orders off market buy 100 ibm mkt stop 57; sell 100 ibm mkt stop 54; # use large AOC orders to set a new market price to 57 # without trading with existing orders # This will trigger the buy stop market order (#5) # which will match (and trade with) the sell 58 limit order #4 buy 1000 ibm 57 aon; sell 1000 ibm 57 aon; # do the same thing on the other side to # hit the stop price of 54 for order #6 sell 1000 ibm 54 aon; buy 1000 ibm 54 aon; # and display the orders, all of which should be filled. d ibm quit ================================================ FILE: license.txt ================================================ Copyright (c) 2012, 2013 Object Computing, Inc. All rights reserved. Liquibook is a licensed product, is protected by copyright, and is distributed under the following terms: Liquibook is an open source implementation of a limit order book matching engine, developed and copyrighted by Object Computing Incorporated (OCI). Since Liquibook is open source and free of licensing fees, you are free to use, modify, and distribute the source code, as long as you include this copyright statement. In particular, you can use Liquibook to build proprietary software and are under no obligation to redistribute any of your source code that is built using Liquibook. Note however, that you may not do anything to the Liquibook code, such as copyrighting it yourself or claiming authorship of the Liquibook code, that will prevent Liquibook from being distributed freely using an open source development model. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Object Computing, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: liquibook.features ================================================ // Copyright (c) 2013-2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. // This file defines MPC features for the liquibook project example_pubsub=1 example_manual=1 // BOOST IS NEEDED BY THE unit tests boost=1 // QUICKFAST IS NEEDED BY THE PUBLISH/SUBSUBSCRIBE example QuickFAST=1 ================================================ FILE: liquibook.mwc ================================================ workspace(liquibook) { cmdline += -include mpc cmdline += -include $QUICKFAST_ROOT cmdline += -feature_file liquibook.features cmdline += -expand_vars cmdline += -use_env specific(make) { cmdline += -value_template "configurations=Release Debug" } } ================================================ FILE: mpc/liquibook.mpb ================================================ project { includes += $(LIQUIBOOK_ROOT)/src libpaths += $(LIQUIBOOK_ROOT)/lib // Force use of Visual C++ DLL runtime libraries specific(vc71,vc8,vc9) { Debug::runtime_library = 3 Release::runtime_library = 2 } specific(vc10,vc11,vc12,vc13,vc14,vc15) { Debug::runtime_library = MultiThreadedDebugDLL Release::runtime_library = MultiThreadedDLL } } ================================================ FILE: mpc/liquibook_book.mpb ================================================ project : liquibook { // obsolete } ================================================ FILE: mpc/liquibook_exe.mpb ================================================ project { specific(prop:microsoft) { Release::exeout = $(LIQUIBOOK_ROOT)/Output/Release Debug::exeout = $(LIQUIBOOK_ROOT)/Output/Debug } else { exeout = $(LIQUIBOOK_ROOT)/bin } } ================================================ FILE: mpc/liquibook_lib.mpb ================================================ project : liquibook { libout = $(LIQUIBOOK_ROOT)/lib macros += LIQUIBOOK_BUILD_DLL } ================================================ FILE: mpc/liquibook_simple.mpb ================================================ project : liquibook { libs += liquibook_simple after += liquibook_simple } ================================================ FILE: mpc/liquibook_test.mpb ================================================ project : liquibook, liquibook_exe, liquibook_book, liquibook_simple{ exeout = $(LIQUIBOOK_ROOT)/bin/test } ================================================ FILE: mpc.bat ================================================ @REM Copyright (c) 2009, 2010 Object Computing, Inc. @REM All rights reserved. @REM See the file license.txt for licensing information. @REM @REM This batch file runs MWC which is part of the MPC package to create @REM Visual Studio solution and project files. @REM @REM Note: -expand_vars -use_env options force MPC to expand $(BOOST_ROOT) into the absolute path. This avoids @REM a problem that happened when people were starting Visual Studio from the Start menu rather than @REM from the command line where the BOOST_ROOT environment had been defined. "%MPC_ROOT%\mwc.pl" -type vc%VCVER% liquibook.mwc ================================================ FILE: noQuickFAST/QuickFASTApplication.mpb ================================================ project{ requires += QuickFAST } ================================================ FILE: pt_run.bat ================================================ bin\test\pt_order_book.exe ================================================ FILE: src/book/bbo_listener.h ================================================ // Copyright (c) 2012, 2013 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #pragma once namespace liquibook { namespace book { /// @brief generic listener of top-of-book events template class BboListener { public: /// @brief callback for top of book change virtual void on_bbo_change( const OrderBook* book, const typename OrderBook::DepthTracker* depth) = 0; }; } } ================================================ FILE: src/book/callback.h ================================================ // Copyright (c) 2012, 2013 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #pragma once #include "types.h" namespace liquibook { namespace book { template class OrderBook; // Callback events // New order accept // - order accept // - fill (2) and/or quote (if not complete) // - depth/bbo ? // New order reject // - order reject // Order fill // - fill (2) // - trade // - quote (2) // - depth/bbo ? // Order cancel // - order cancel // - quote // - depth/bbo ? // Order cancel reject // - order cancel reject // Order replace // - order replace // - fill (2) and/or quote (if not complete) // - depth/bbo ? // Order replace reject // - order replace reject /// @brief notification from OrderBook of an event template class Callback { public: typedef OrderBook TypedOrderBook; enum CbType { cb_unknown, cb_order_accept, cb_order_accept_stop, cb_order_trigger_stop, cb_order_reject, cb_order_fill, cb_order_cancel, cb_order_cancel_stop, cb_order_cancel_reject, cb_order_replace, cb_order_replace_reject, cb_book_update }; enum FillFlags { ff_neither_filled = 0, ff_inbound_filled = 1, ff_matched_filled = 2, ff_both_filled = 4 }; Callback(); /// @brief create a new accept callback static Callback accept(const OrderPtr& order); /// @brief create a new accept callback static Callback accept_stop(const OrderPtr& order); /// @brief create a new accept callback static Callback trigger_stop(const OrderPtr& order); /// @brief create a new reject callback static Callback reject(const OrderPtr& order, const char* reason); /// @brief create a new fill callback static Callback fill(const OrderPtr& inbound_order, const OrderPtr& matched_order, const Quantity& fill_qty, const Price& fill_price, FillFlags fill_flags); /// @brief create a new cancel callback static Callback cancel(const OrderPtr& order, const Quantity& open_qty); /// @brief create a new cancel callback static Callback cancel_stop(const OrderPtr& order); /// @brief create a new cancel reject callback static Callback cancel_reject(const OrderPtr& order, const char* reason); /// @brief create a new replace callback static Callback replace(const OrderPtr& order, const Quantity& curr_open_qty, const int64_t& size_delta, const Price& new_price); /// @brief create a new replace reject callback static Callback replace_reject(const OrderPtr& order, const char* reason); static Callback book_update(const TypedOrderBook* book = nullptr); CbType type; OrderPtr order; OrderPtr matched_order; Quantity quantity; Price price; uint8_t flags; int64_t delta; const char* reject_reason; }; template Callback::Callback() : type(cb_unknown), order(nullptr), matched_order(nullptr), quantity(0), price(0), flags(0), delta(0), reject_reason(nullptr) { } template Callback Callback::accept( const OrderPtr& order) { Callback result; result.type = cb_order_accept; result.order = order; return result; } template Callback Callback::accept_stop( const OrderPtr& order) { Callback result; result.type = cb_order_accept_stop; result.order = order; return result; } template Callback Callback::trigger_stop( const OrderPtr& order) { Callback result; result.type = cb_order_trigger_stop; result.order = order; return result; } template Callback Callback::reject( const OrderPtr& order, const char* reason) { Callback result; result.type = cb_order_reject; result.order = order; result.reject_reason = reason; return result; } template Callback Callback::fill( const OrderPtr& inbound_order, const OrderPtr& matched_order, const Quantity& fill_qty, const Price& fill_price, FillFlags fill_flags) { Callback result; result.type = cb_order_fill; result.order = inbound_order; result.matched_order = matched_order; result.quantity = fill_qty; result.price = fill_price; result.flags = fill_flags; return result; } template Callback Callback::cancel( const OrderPtr& order, const Quantity& open_qty) { // TODO save the open qty Callback result; result.type = cb_order_cancel; result.order = order; result.quantity = open_qty; return result; } template Callback Callback::cancel_stop( const OrderPtr& order) { Callback result; result.type = cb_order_cancel_stop; result.order = order; return result; } template Callback Callback::cancel_reject( const OrderPtr& order, const char* reason) { Callback result; result.type = cb_order_cancel_reject; result.order = order; result.reject_reason = reason; return result; } template Callback Callback::replace( const OrderPtr& order, const Quantity& curr_open_qty, const int64_t& size_delta, const Price& new_price) { // TODO save the order open qty Callback result; result.type = cb_order_replace; result.order = order; result.quantity = curr_open_qty; result.delta = size_delta; result.price = new_price; return result; } template Callback Callback::replace_reject( const OrderPtr& order, const char* reason) { Callback result; result.type = cb_order_replace_reject; result.order = order; result.reject_reason = reason; return result; } template Callback Callback::book_update(const OrderBook* book) { Callback result; result.type = cb_book_update; return result; } } } ================================================ FILE: src/book/comparable_price.h ================================================ // Copyright (c) 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #pragma once #include "types.h" #include namespace liquibook { namespace book { /// @brief A price that knows which side of the market it is on /// Designed to be compared to other prices on the same side using /// standard comparison operators (< > <= >= == !=) and to /// compared to prices on the other side using the match() method. /// /// Using the '<' operation to sort a set of prices will result in the /// prices being partially ordered from most liquid to least liquid. /// i.e: /// Market prices always sort first since they will match any counter price. /// Sell side low prices sort before high prices because they match more buys. /// Buy side high prices sort before low prices because they match more asks. /// class ComparablePrice { Price price_; bool buySide_; public: /// @brief construct given side and price /// @param buySide controls whether price comparison is normal or reversed /// @param price is the price for this key, or 0 (MARKET_ORDER_PRICE) for market ComparablePrice(bool buySide, Price price) : price_(price) , buySide_(buySide) { } /// @brief Check possible trade /// Assumes rhs is on the opposite side bool matches(Price rhs) const { if(price_ == rhs) { return true; } if(buySide_) { return rhs < price_ || price_ == MARKET_ORDER_PRICE ; } return price_ < rhs || rhs == MARKET_ORDER_PRICE; } /// @brief less than compare key to a price /// Assumes both prices are on the same side. /// Uses side to determine the sense of the comparison. bool operator <(Price rhs) const { // Compare difficulty finding a match. (easy is less than hard) if(price_ == MARKET_ORDER_PRICE) { return rhs != MARKET_ORDER_PRICE; } else if(rhs == MARKET_ORDER_PRICE) { return false; } else if(buySide_) { // Buying: Highest prices first. return rhs < price_ ; } else { // Selling: lowest prices first return price_ < rhs; } } /// @brief equality compare key to a price bool operator ==(Price rhs) const { // compares equal without regard to side return price_ == rhs; } /// @brief inequality compare key to a price bool operator !=(Price rhs) const { return !( price_ == rhs ); } /// @brief greater than compare key to a price /// Assumes both prices are on the same side. bool operator > (Price rhs) const { return price_!= MARKET_ORDER_PRICE && ((rhs == MARKET_ORDER_PRICE) || (buySide_ ? (rhs > price_) : (price_ > rhs))); } /// @brief less than or equal to compare key to a price /// Assumes both prices are on the same side. bool operator <=(Price rhs) const { return *this < rhs || *this == rhs; } /// @brief greater than or equal to compare key to a price /// Assumes both prices are on the same side. bool operator >=(Price rhs) const { return *this > rhs || *this == rhs; } /// @brief less than compare order map keys /// compares the prices assuming they are on the same side bool operator <(const ComparablePrice & rhs) const { return *this < rhs.price_; } /// @brief equality compare order map keys bool operator ==(const ComparablePrice & rhs) const { return *this == rhs.price_; } /// @brief inequality compare order map keys bool operator !=(const ComparablePrice & rhs) const { return *this != rhs.price_; } /// @brief greater than compare order map keys /// Assumes both prices are on the same side. bool operator >(const ComparablePrice & rhs) const { return *this > rhs.price_; } /// @brief access price. Price price() const { return price_; } /// @brief access side. bool isBuy() const { return buySide_; } /// @brief check to see if this is market price bool isMarket() const { return price_ == MARKET_ORDER_PRICE; } }; /// @brief less than compare price to key inline bool operator < (Price price, const ComparablePrice & key) { return key > price; } /// @brief greater than compare price to key inline bool operator > (Price price, const ComparablePrice & key) { return key < price; } /// @brief equality compare price to key inline bool operator == (Price price, const ComparablePrice & key) { return key == price; } /// @brief inequality compare price to key inline bool operator != (Price price, const ComparablePrice & key) { return key != price; } /// @brief less than or equal to compare price to key inline bool operator <= (Price price, const ComparablePrice & key) { return key >= price; } /// @brief greater or equal to than compare price to key inline bool operator >= (Price price, const ComparablePrice & key) { return key <= price; } inline std::ostream & operator << (std::ostream & out, const ComparablePrice & key) { out << (key.isBuy() ? "Buy at " : "Sell at "); if(key.isMarket()) { out << "Market"; } else { out << key.price(); } return out; } }} ================================================ FILE: src/book/depth.h ================================================ // Copyright (c) 2012 - 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #pragma once #include "depth_constants.h" #include "depth_level.h" #include #include #include #include #include namespace liquibook { namespace book { /// @brief container of limit order data aggregated by price. Designed so that /// the depth levels themselves are easily copyable with a single memcpy /// when used with a separate callback thread. /// /// TODO: Fix the bid and ask methods to behave like a normal iterator (i.e. begin(), back(), and end() template class Depth { public: /// @brief construct Depth(); /// @brief get the first bid level (const) const DepthLevel* bids() const; /// @brief get the last bid level (const) const DepthLevel* last_bid_level() const; /// @brief get the first ask level (const) const DepthLevel* asks() const; /// @brief get the last ask level (const) const DepthLevel* last_ask_level() const; /// @brief get one past the last ask level (const) const DepthLevel* end() const; /// @brief get the first bid level (mutable) DepthLevel* bids(); /// @brief get the last bid level (mutable) DepthLevel* last_bid_level(); /// @brief get the first ask level (mutable) DepthLevel* asks(); /// @brief get the last ask level (mutable) DepthLevel* last_ask_level(); /// @brief add an order /// @param price the price level of the order /// @param qty the open quantity of the order /// @param is_bid indicator of bid or ask void add_order(Price price, Quantity qty, bool is_bid); /// @brief ignore future fill quantity on a side, due to a match at /// accept time for an order /// @param qty the open quantity to ignore /// @param is_bid indicator of bid or ask void ignore_fill_qty(Quantity qty, bool is_bid); /// @brief handle an order fill /// @param price the price level of the order /// @param fill_qty the quantity of this fill /// @param filled was this order completely filled? /// @param is_bid indicator of bid or ask void fill_order(Price price, Quantity fill_qty, bool filled, bool is_bid); /// @brief cancel or fill an order /// @param price the price level of the order /// @param open_qty the open quantity of the order /// @param is_bid indicator of bid or ask /// @return true if the close erased a visible level bool close_order(Price price, Quantity open_qty, bool is_bid); /// @brief change quantity of an order /// @param price the price level of the order /// @param qty_delta the change in open quantity of the order (+ or -) /// @param is_bid indicator of bid or ask void change_qty_order(Price price, int64_t qty_delta, bool is_bid); /// @brief replace a order /// @param current_price the current price level of the order /// @param new_price the new price level of the order /// @param current_qty the current open quantity of the order /// @param new_qty the new open quantity of the order /// @param is_bid indicator of bid or ask /// @return true if the close erased a visible level bool replace_order(Price current_price, Price new_price, Quantity current_qty, Quantity new_qty, bool is_bid); /// @brief does this depth need bid restoration after level erasure /// @param restoration_price the price to restore after (out) /// @return true if restoration is needed (previously was full) bool needs_bid_restoration(Price& restoration_price); /// @brief does this depth need ask restoration after level erasure /// @param restoration_price the price to restore after (out) /// @return true if restoration is needed (previously was full) bool needs_ask_restoration(Price& restoration_price); /// @brief has the depth changed since the last publish bool changed() const; /// @brief what was the ID of the last change? ChangeId last_change() const; /// @brief what was the ID of the last published change? ChangeId last_published_change() const; /// @brief note the ID of last published change void published(); private: DepthLevel levels_[SIZE*2]; ChangeId last_change_; ChangeId last_published_change_; Quantity ignore_bid_fill_qty_; Quantity ignore_ask_fill_qty_; typedef std::map > BidLevelMap; typedef std::map > AskLevelMap; BidLevelMap excess_bid_levels_; AskLevelMap excess_ask_levels_; /// @brief find the level associated with the price /// @param price the price to find /// @param is_bid indicator of bid or ask /// @param should_create should a level for the price be created, if necessary /// @return the level, or nullptr if not found and full DepthLevel* find_level(Price price, bool is_bid, bool should_create = true); /// @brief insert a new level before this level and shift down /// @param level the level to insert before /// @param is_bid indicator of bid or ask /// @param price the price to initialize the level at void insert_level_before(DepthLevel* level, bool is_bid, Price price); /// @brief erase a level and shift up /// @param level the level to erase /// @param is_bid indicator of bid or ask void erase_level(DepthLevel* level, bool is_bid); }; template Depth::Depth() : last_change_(0), last_published_change_(0), ignore_bid_fill_qty_(0), ignore_ask_fill_qty_(0) { memset(levels_, 0, sizeof(DepthLevel) * SIZE * 2); } template inline const DepthLevel* Depth::bids() const { return levels_; } template inline const DepthLevel* Depth::asks() const { return levels_ + SIZE; } template inline const DepthLevel* Depth::last_bid_level() const { return levels_ + (SIZE - 1); } template inline const DepthLevel* Depth::last_ask_level() const { return levels_ + (SIZE * 2 - 1); } template inline const DepthLevel* Depth::end() const { return levels_ + (SIZE * 2); } template inline DepthLevel* Depth::bids() { return levels_; } template inline DepthLevel* Depth::asks() { return levels_ + SIZE; } template inline DepthLevel* Depth::last_bid_level() { return levels_ + (SIZE - 1); } template inline DepthLevel* Depth::last_ask_level() { return levels_ + (SIZE * 2 - 1); } template inline void Depth::add_order(Price price, Quantity qty, bool is_bid) { ChangeId last_change_copy = last_change_; DepthLevel* level = find_level(price, is_bid); if (level) { level->add_order(qty); // If this is a visible level if (!level->is_excess()) { // The depth changed last_change_ = last_change_copy + 1; // Ensure incremented level->last_change(last_change_copy + 1); } // The level is not marked as changed if it is not visible } } template inline void Depth::ignore_fill_qty(Quantity qty, bool is_bid) { if (is_bid) { if (ignore_bid_fill_qty_) { throw std::runtime_error("Unexpected ignore_bid_fill_qty_"); } ignore_bid_fill_qty_ = qty; } else { if (ignore_ask_fill_qty_) { throw std::runtime_error("Unexpected ignore_ask_fill_qty_"); } ignore_ask_fill_qty_ = qty; } } template inline void Depth::fill_order( Price price, Quantity fill_qty, bool filled, bool is_bid) { if (is_bid && ignore_bid_fill_qty_) { ignore_bid_fill_qty_ -= fill_qty; } else if ((!is_bid) && ignore_ask_fill_qty_) { ignore_ask_fill_qty_ -= fill_qty; } else if (filled) { close_order(price, fill_qty, is_bid); } else { change_qty_order(price, -(int64_t)fill_qty, is_bid); } } template inline bool Depth::close_order(Price price, Quantity open_qty, bool is_bid) { DepthLevel* level = find_level(price, is_bid, false); if (level) { // If this is the last order on the level if (level->close_order(open_qty)) { erase_level(level, is_bid); return true; // Else, mark the level as changed } else { level->last_change(++last_change_); } } return false; } template inline void Depth::change_qty_order(Price price, int64_t qty_delta, bool is_bid) { DepthLevel* level = find_level(price, is_bid, false); if (level && qty_delta) { if (qty_delta > 0) { level->increase_qty(Quantity(qty_delta)); } else { level->decrease_qty(Quantity(std::abs(qty_delta))); } level->last_change(++last_change_); } // Ignore if not found - may be beyond our depth size } template inline bool Depth::replace_order( Price current_price, Price new_price, Quantity current_qty, Quantity new_qty, bool is_bid) { bool erased = false; // If the price is unchanged, modify this level only if (current_price == new_price) { int64_t qty_delta = ((int64_t)new_qty) - current_qty; // Only change order qty. If this closes order, a cancel callback will // also be fired change_qty_order(current_price, qty_delta, is_bid); // Else this is a price change } else { // Add the new order quantity first, and possibly insert a new level add_order(new_price, new_qty, is_bid); // Remove the old order quantity, and possibly erase a level erased = close_order(current_price, current_qty, is_bid); } return erased; } template inline bool Depth::needs_bid_restoration(Price& restoration_price) { // If this depth has multiple levels if (SIZE > 1) { // Restore using the price before the last level restoration_price = (last_bid_level() - 1)->price(); // Restore if that level was valid return restoration_price != INVALID_LEVEL_PRICE; // Else this depth is BBO only } else if (SIZE == 1) { // There is no earlier level to look at, restore using the first non-market // bid price restoration_price = MARKET_ORDER_BID_SORT_PRICE; // Always restore on BBO only return true; } throw std::runtime_error("Depth size less than one not allowed"); } template inline bool Depth::needs_ask_restoration(Price& restoration_price) { // If this depth has multiple levels if (SIZE > 1) { // Restore using the price before the last level restoration_price = (last_ask_level() - 1)->price(); // Restore if that level was valid return restoration_price != INVALID_LEVEL_PRICE; // Else this depth is BBO only } else if (SIZE == 1) { // There is no earlier level to look at, restore the first non-market // ask price restoration_price = MARKET_ORDER_ASK_SORT_PRICE; // Always restore on BBO only return true; } throw std::runtime_error("Depth size less than one not allowed"); } template DepthLevel* Depth::find_level(Price price, bool is_bid, bool should_create) { // Find starting and ending point DepthLevel* level = is_bid ? bids() : asks(); const DepthLevel* past_end = is_bid ? asks() : end(); // Linear search each level for ( ; level != past_end; ++level) { if (level->price() == price) { break; // Else if the level is blank } else if (should_create && level->price() == INVALID_LEVEL_PRICE) { level->init(price, false); // Change ID will be assigned by caller break; // Blank slot // Else if the bid level price is too low } else if (is_bid && should_create && level->price() < price) { // Insert a slot insert_level_before(level, is_bid, price); break; // Else if the ask level price is too high } else if ((!is_bid) && should_create && level->price() > price) { // Insert a slot insert_level_before(level, is_bid, price); break; } } // If level was not found if (level == past_end) { if (is_bid) { // Search in excess bid levels BidLevelMap::iterator find_result = excess_bid_levels_.find(price); // If found in excess levels, return location if (find_result != excess_bid_levels_.end()) { level = &find_result->second; // Else not found, insert if one should be created } else if (should_create) { DepthLevel new_level; new_level.init(price, true); std::pair insert_result; insert_result = excess_bid_levels_.insert( std::make_pair(price, new_level)); level = &insert_result.first->second; } } else { // Search in excess ask levels AskLevelMap::iterator find_result = excess_ask_levels_.find(price); // If found in excess levels, return location if (find_result != excess_ask_levels_.end()) { level = &find_result->second; // Else not found, insert if one should be created } else if (should_create) { DepthLevel new_level; new_level.init(price, true); std::pair insert_result; insert_result = excess_ask_levels_.insert( std::make_pair(price, new_level)); level = &insert_result.first->second; } } } return level; } template void Depth::insert_level_before(DepthLevel* level, bool is_bid, Price price) { DepthLevel* last_side_level = is_bid ? last_bid_level() : last_ask_level(); // If the last level has valid data if (last_side_level->price() != INVALID_LEVEL_PRICE) { DepthLevel excess_level; excess_level.init(0, true); // Will assign over price excess_level = *last_side_level; // Save it in excess levels if (is_bid) { excess_bid_levels_.insert( std::make_pair(last_side_level->price(), excess_level)); } else { excess_ask_levels_.insert( std::make_pair(last_side_level->price(), excess_level)); } } // Back from end DepthLevel* current_level = last_side_level - 1; // Increment only once ++last_change_; // Last level to process is one passed in while (current_level >= level) { // Copy level to level one lower *(current_level + 1) = *current_level; // If the level being copied is valid if (current_level->price() != INVALID_LEVEL_PRICE) { // Update change Id (current_level + 1)->last_change(last_change_); } // Move back one --current_level; } level->init(price, false); } template void Depth::erase_level(DepthLevel* level, bool is_bid) { // If ther level being erased is from the excess, remove excess from map if (level->is_excess()) { if (is_bid) { excess_bid_levels_.erase(level->price()); } else { excess_ask_levels_.erase(level->price()); } // Else the level being erased is not excess, copy over from those worse } else { DepthLevel* last_side_level = is_bid ? last_bid_level() : last_ask_level(); // Increment once ++last_change_; DepthLevel* current_level = level; // Level to end while (current_level < last_side_level) { // If this is the first level, or the level to be overwritten is valid // (must force first level, when called already should be invalidated) if ((current_level->price() != INVALID_LEVEL_PRICE) || (current_level == level)) { // Copy to current level from one lower *current_level = *(current_level + 1); // Mark the current level as updated current_level->last_change(last_change_); } // Move forward one ++current_level; } // If I erased the last level, or the last level was valid if ((level == last_side_level) || (last_side_level->price() != INVALID_LEVEL_PRICE)) { // Attempt to restore last level from excess if (is_bid) { BidLevelMap::iterator best_bid = excess_bid_levels_.begin(); if (best_bid != excess_bid_levels_.end()) { *last_side_level = best_bid->second; excess_bid_levels_.erase(best_bid); } else { // Nothing to restore, last level is blank last_side_level->init(INVALID_LEVEL_PRICE, false); last_side_level->last_change(last_change_); } } else { AskLevelMap::iterator best_ask = excess_ask_levels_.begin(); if (best_ask != excess_ask_levels_.end()) { *last_side_level = best_ask->second; excess_ask_levels_.erase(best_ask); } else { // Nothing to restore, last level is blank last_side_level->init(INVALID_LEVEL_PRICE, false); last_side_level->last_change(last_change_); } } last_side_level->last_change(last_change_); } } } template bool Depth::changed() const { return last_change_ > last_published_change_; } template ChangeId Depth::last_change() const { return last_change_; } template ChangeId Depth::last_published_change() const { return last_published_change_; } template void Depth::published() { last_published_change_ = last_change_; } } } ================================================ FILE: src/book/depth_constants.h ================================================ // Copyright (c) 2012 - 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #pragma once #include "types.h" namespace liquibook { namespace book { namespace { // Constants used in liquibook const Price INVALID_LEVEL_PRICE(0); const Price MARKET_ORDER_BID_SORT_PRICE(QUANTITY_MAX); const Price MARKET_ORDER_ASK_SORT_PRICE(0); } }} ================================================ FILE: src/book/depth_level.h ================================================ // Copyright (c) 2012, 2013 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #pragma once #include "depth_constants.h" namespace liquibook { namespace book { /// @brief a single level of the limit order book aggregated by price class DepthLevel { public: /// @brief construct DepthLevel(); /// @brief assign DepthLevel& operator=(const DepthLevel& rhs); /// @brief get price const Price& price() const; /// @brief get count uint32_t order_count() const; /// @brief get aggregate quantity Quantity aggregate_qty() const; /// @brief is this level part of the excess bool is_excess() const { return is_excess_; } void init(Price price, bool is_excess); /// @brief add an order to the level /// @param qty open quantity of the order void add_order(Quantity qty); /// @brief increase the quantity of existing orders /// @param qty amount to increase the quantity by void increase_qty(Quantity qty); /// @brief decrease the quantity of existing orders /// @param qty amount to decrease the quantity by void decrease_qty(Quantity qty); /// @brief overwrite all values of the level /// @param price the level price /// @param qty the aggegate quantity /// @param order_count the number of orders /// @param last_change the last change ID (optional) void set(Price price, Quantity qty, uint32_t order_count, ChangeId last_change = 0); /// @brief cancel or fill an order, decrease count and quantity /// @param qty the closed quantity /// @return true if the level is now empty bool close_order(Quantity qty); /// @brief set last changed stamp on this level void last_change(ChangeId last_change) { last_change_ = last_change; } /// @brief get last change stamp for this level ChangeId last_change() const { return last_change_; } /// @brief has the level changed since the given stamp? /// @param last_published_change the stamp to compare to bool changed_since(ChangeId last_published_change) const; private: Price price_; uint32_t order_count_; Quantity aggregate_qty_; bool is_excess_; public: ChangeId last_change_; }; inline bool DepthLevel::changed_since(ChangeId last_published_change) const { return last_change_ > last_published_change; } inline DepthLevel::DepthLevel() : price_(INVALID_LEVEL_PRICE), order_count_(0), aggregate_qty_(0) { } inline DepthLevel& DepthLevel::operator=(const DepthLevel& rhs) { price_ = rhs.price_; order_count_ = rhs.order_count_; aggregate_qty_ = rhs.aggregate_qty_; if (rhs.price_ != INVALID_LEVEL_PRICE) { last_change_ = rhs.last_change_; } // Do not copy is_excess_ return *this; } inline const Price& DepthLevel::price() const { return price_; } inline void DepthLevel::init(Price price, bool is_excess) { price_ = price; order_count_ = 0; aggregate_qty_ = 0; is_excess_ = is_excess; } inline uint32_t DepthLevel::order_count() const { return order_count_; } inline Quantity DepthLevel::aggregate_qty() const { return aggregate_qty_; } inline void DepthLevel::add_order(Quantity qty) { // Increment/increase ++order_count_; aggregate_qty_ += qty; } inline bool DepthLevel::close_order(Quantity qty) { bool empty = false; // If this is the last order, reset the level if (order_count_ == 0) { throw std::runtime_error("DepthLevel::close_order " "order count too low"); } else if (order_count_ == 1) { order_count_ = 0; aggregate_qty_ = 0; empty = true; // Else, decrement/decrease } else { --order_count_; if (aggregate_qty_ >= qty) { aggregate_qty_ -= qty; } else { throw std::runtime_error("DepthLevel::close_order " "level quantity too low"); } } return empty; } inline void DepthLevel::set(Price price, Quantity qty, uint32_t order_count, ChangeId last_change) { price_ = price; aggregate_qty_ = qty; order_count_ = order_count; last_change_ = last_change; } inline void DepthLevel::increase_qty(Quantity qty) { aggregate_qty_ += qty; } inline void DepthLevel::decrease_qty(Quantity qty) { aggregate_qty_ -= qty; } } } ================================================ FILE: src/book/depth_listener.h ================================================ // Copyright (c) 2012, 2013 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #pragma once #include "order_book.h" namespace liquibook { namespace book { /// @brief listener of depth events. Implement to build an aggregate depth /// feed. template class DepthListener { public: /// @brief callback for change in tracked aggregated depth virtual void on_depth_change( const OrderBook* book, const typename OrderBook::DepthTracker* depth) = 0; }; } } ================================================ FILE: src/book/depth_order_book.h ================================================ // Copyright (c) 2012, 2013 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #pragma once #include "order_book.h" #include "depth.h" #include "bbo_listener.h" #include "depth_listener.h" namespace liquibook { namespace book { /// @brief Implementation of order book child class, that incorporates /// aggregate depth tracking. template class DepthOrderBook : public OrderBook { public: typedef Depth DepthTracker; typedef BboListenerTypedBboListener; typedef DepthListenerTypedDepthListener; /// @brief construct DepthOrderBook(const std::string & symbol = "unknown"); /// @brief set the BBO listener void set_bbo_listener(TypedBboListener* bbo_listener); /// @brief set the depth listener void set_depth_listener(TypedDepthListener* depth_listener); // @brief access the depth tracker DepthTracker& depth(); // @brief access the depth tracker const DepthTracker& depth() const; protected: ////////////////////////////////// // Implement virtual callback methods // needed to maintain depth book. virtual void on_accept(const OrderPtr& order, Quantity quantity); virtual void on_accept_stop(const OrderPtr& order); virtual void on_trigger_stop(const OrderPtr& order); virtual void on_fill(const OrderPtr& order, const OrderPtr& matched_order, Quantity fill_qty, Price fill_price, bool inbound_order_filled, bool matched_order_filled); virtual void on_cancel(const OrderPtr& order, Quantity quantity); virtual void on_cancel_stop(const OrderPtr& order); virtual void on_replace(const OrderPtr& order, Quantity current_qty, Quantity new_qty, Price new_price); virtual void on_order_book_change(); private: DepthTracker depth_; TypedBboListener* bbo_listener_; TypedDepthListener* depth_listener_; }; template DepthOrderBook::DepthOrderBook(const std::string & symbol) : OrderBook(symbol), bbo_listener_(nullptr), depth_listener_(nullptr) { } template void DepthOrderBook::set_bbo_listener(TypedBboListener* listener) { bbo_listener_ = listener; } template void DepthOrderBook::set_depth_listener(TypedDepthListener* listener) { depth_listener_ = listener; } template void DepthOrderBook::on_accept(const OrderPtr& order, Quantity quantity) { // If the order is a limit order if (order->is_limit()) { // If the order is completely filled on acceptance, do not modify // depth unnecessarily if (quantity == order->order_qty()) { // Don't tell depth about this order - it's going away immediately. // Instead tell Depth about future fills to ignore depth_.ignore_fill_qty(quantity, order->is_buy()); } else { // Add to bid or ask depth depth_.add_order(order->price(), order->order_qty(), order->is_buy()); } } } template void DepthOrderBook::on_accept_stop(const OrderPtr& order) { } template void DepthOrderBook::on_trigger_stop(const OrderPtr& order) { // Add to depth depth_.add_order(order->price(), order->order_qty(), order->is_buy()); } template void DepthOrderBook::on_fill(const OrderPtr& order, const OrderPtr& matched_order, Quantity quantity, Price fill_price, bool inbound_order_filled, bool matched_order_filled) { // If the matched order is a limit order if (matched_order->is_limit()) { // Inform the depth depth_.fill_order(matched_order->price(), quantity, matched_order_filled, matched_order->is_buy()); } // If the inbound order is a limit order if (order->is_limit()) { // Inform the depth depth_.fill_order(order->price(), quantity, inbound_order_filled, order->is_buy()); } } template void DepthOrderBook::on_cancel(const OrderPtr& order, Quantity quantity) { // If the order is a limit order if (order->is_limit()) { // If the close erases a level depth_.close_order(order->price(), quantity, order->is_buy()); } } template void DepthOrderBook::on_cancel_stop(const OrderPtr& order) { // nothing to do for STOP until triggered/submitted } template void DepthOrderBook::on_replace(const OrderPtr& order, Quantity current_qty, Quantity new_qty, Price new_price) { // Notify the depth depth_.replace_order(order->price(), new_price, current_qty, new_qty, order->is_buy()); } template void DepthOrderBook::on_order_book_change() { // Book was updated, see if the depth we track was effected if (depth_.changed()) { if (depth_listener_) { depth_listener_->on_depth_change(this, &depth_); } if (bbo_listener_) { ChangeId last_change = depth_.last_published_change(); // May have been the first level which changed if ((depth_.bids()->changed_since(last_change)) || (depth_.asks()->changed_since(last_change))) { bbo_listener_->on_bbo_change(this, &depth_); } } // Start tracking changes again... depth_.published(); } } template inline typename DepthOrderBook::DepthTracker& DepthOrderBook::depth() { return depth_; } template inline const typename DepthOrderBook::DepthTracker& DepthOrderBook::depth() const { return depth_; } } } ================================================ FILE: src/book/liquibook.mpc ================================================ project (liquibook): liquibook_exe { exename = * Header_files { *.h } Source_Files { *.cpp } } ================================================ FILE: src/book/logger.h ================================================ // Copyright (c) 2012, 2013 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #pragma once #include #include namespace liquibook { namespace book { /// @brief Interface to allow application to control error logging class Logger { public: virtual void log_exception(const std::string & context, const std::exception& ex) = 0; virtual void log_message(const std::string & message) = 0; }; }} ================================================ FILE: src/book/main.cpp ================================================ #include #include "version.h" #include "depth_order_book.h" #include "order.h" using namespace liquibook; using namespace book; namespace { // depth order book pulls in all the other header files. // except order.h which is actually a concept. DepthOrderBook unusedDepthOrderBook_; } int main(int, const char**) { std::cout << "Liquibook version " << Version::MAJOR << '.' << Version::MINOR << '.' << Version::PATCH << " (" << Version::RELEASE_DATE << ")\n"; std::cout << "Liquibook is a header-only library.\n\n"; std::cout << "This executable is a placeholder to make the book header files visible in\n"; std::cout << "Visual Studio. It also lets the compiler to do syntax checking of the header\n"; std::cout << "files at build time." << std::endl; return 0; } ================================================ FILE: src/book/order.h ================================================ // Copyright (c) 2012, 2013 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #pragma once #include "types.h" namespace liquibook { namespace book { /// @brief interface an order must implement in order to be used by OrderBook. /// Note: structly speaking, inheriting from Order should not be required, /// due to the template implementation of OrderBook. class Order { public: /// @brief is this a limit order? bool is_limit() const; /// @brief is this order a buy? virtual bool is_buy() const = 0; /// @brief get the price of this order, or 0 if a market order virtual Price price() const = 0; /// @brief get the stop price (if any) for this order. /// @returns the stop price or zero if not a stop order virtual Price stop_price() const; /// @brief get the quantity of this order virtual Quantity order_qty() const = 0; /// @brief if no trades should happen until the order /// can be filled completely. /// Note: one or more trades may be used to fill the order. virtual bool all_or_none() const; /// @brief After generating as many trades as possible against /// orders already on the market, cancel any remaining quantity. virtual bool immediate_or_cancel() const; }; inline bool Order::is_limit() const { return (price() > 0); } inline Price Order::stop_price() const { // default to not a stop order return 0; } inline bool Order::all_or_none() const { // default to normal return false; } inline bool Order::immediate_or_cancel() const { // default to normal return false; } } } ================================================ FILE: src/book/order_book.h ================================================ // Copyright (c) 2012 - 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #pragma once #include "version.h" #include "order_tracker.h" #include "callback.h" #include "order_listener.h" #include "order_book_listener.h" #include "trade_listener.h" #include "comparable_price.h" #include "logger.h" #include #include #include #include #include #include #include #include #ifdef LIQUIBOOK_IGNORES_DEPRECATED_CALLS #define COMPLAIN_ONCE(message) #else // LIQUIBOOK_IGNORES_DEPRECATED_CALLS #define COMPLAIN_ONCE(message) \ do{ \ static bool once = true; \ if(once) \ { \ once = false; \ std::cerr << "One-time Warning: " << message << std::endl; \ } \ } while(false) #endif // LIQUIBOOK_IGNORES_DEPRECATED_CALLS namespace liquibook { namespace book { template class OrderListener; template class OrderBookListener; /// @brief The limit order book of a security. Template implementation allows /// user to supply common or smart pointers, and to provide a different /// Order class completely (as long as interface is obeyed). template class OrderBook { public: typedef OrderTracker Tracker; typedef Callback TypedCallback; typedef OrderListener TypedOrderListener; typedef OrderBook MyClass; typedef TradeListener TypedTradeListener; typedef OrderBookListener TypedOrderBookListener; typedef std::vector Callbacks; typedef std::multimap TrackerMap; typedef std::vector TrackerVec; // Keep this around briefly for compatibility. typedef TrackerMap Bids; typedef TrackerMap Asks; typedef std::list DeferredMatches; /// @brief construct OrderBook(const std::string & symbol = "unknown"); /// @brief Set symbol for orders in this book. void set_symbol(const std::string & symbol); /// @ Get the symbol for orders in this book /// @return the symbol. const std::string & symbol() const; /// @brief set the order listener void set_order_listener(TypedOrderListener* listener); /// @brief set the trade listener void set_trade_listener(TypedTradeListener* listener); /// @brief set the order book listener void set_order_book_listener(TypedOrderBookListener* listener); /// @brief let the application handle reporting errors. void set_logger(Logger * logger); /// @brief add an order to book /// @param order the order to add /// @param conditions special conditions on the order /// @return true if the add resulted in a fill virtual bool add(const OrderPtr& order, OrderConditions conditions = 0); /// @brief cancel an order in the book virtual void cancel(const OrderPtr& order); /// @brief replace an order in the book /// @param order the order to replace /// @param size_delta the change in size for the order (positive or negative) /// @param new_price the new order price, or PRICE_UNCHANGED /// @return true if the replace resulted in a fill virtual bool replace(const OrderPtr& order, int64_t size_delta = SIZE_UNCHANGED, Price new_price = PRICE_UNCHANGED); /// @brief Set the current market price /// Intended to be used during initialization to establish the market /// price before this order book has generated any exceptions. /// /// If price is zero (or never set) no market-to-market trades can happen. /// @param price is the current market price for this security. void set_market_price(Price price); /// @brief Get current market price. /// The market price is normally the price at which the last trade happened. Price market_price()const; /// @brief access the bids container const TrackerMap& bids() const { return bids_; }; /// @brief access the asks container const TrackerMap& asks() const { return asks_; }; /// @brief access stop bid orders const TrackerMap & stopBids() const { return stopBids_;} /// @brief access stop ask orders const TrackerMap & stopAsks() const { return stopAsks_;} /// @brief move callbacks to another thread's container /// @deprecated This doesn't do anything now /// so don't bother to call it in new code. void move_callbacks(Callbacks& target); /// @brief perform all callbacks in the queue /// @deprecated This doesn't do anything now /// so don't bother to call it in new code. virtual void perform_callbacks(); /// @brief log the orders in the book. std::ostream & log(std::ostream & out) const; protected: /// @brief Internal method to process callbacks. /// Protected against recursive calls in case callbacks /// issue new requests. void callback_now(); /// @brief perform an individual callback virtual void perform_callback(TypedCallback& cb); /// @brief match a new order to current orders /// @param inbound_order the inbound order /// @param inbound_price price of the inbound order /// @param current_orders open orders /// @param[OUT] deferred_aons AON orders from current_orders /// that matched the inbound price, /// but were not filled due to quantity /// @return true if a match occurred virtual bool match_order(Tracker& inbound_order, Price inbound_price, TrackerMap& current_orders, DeferredMatches & deferred_aons); bool match_aon_order(Tracker& inbound, Price inbound_price, TrackerMap& current_orders, DeferredMatches & deferred_aons); bool match_regular_order(Tracker& inbound, Price inbound_price, TrackerMap& current_orders, DeferredMatches & deferred_aons); Quantity try_create_deferred_trades( Tracker& inbound, DeferredMatches & deferred_matches, Quantity maxQty, // do not exceed Quantity minQty, // must be at least TrackerMap& current_orders); /// @brief see if any deferred All Or None orders can now execute. /// @param aons iterators to the orders that might now match /// @param deferredTrackers the container of the aons /// @param marketTrackers the orders to check for matches bool check_deferred_aons(DeferredMatches & aons, TrackerMap & deferredTrackers, TrackerMap & marketTrackers); /// @brief perform fill on two orders /// @param inbound_tracker the new (or changed) order tracker /// @param current_tracker the current order tracker /// @param max_quantity maximum quantity to trade. /// @return the number of units traded (zero if unsuccessful). Quantity create_trade(Tracker& inbound_tracker, Tracker& current_tracker, Quantity max_quantity = QUANTITY_MAX); /// @brief find an order in a container /// @param order is the the order we are looking for /// @param[OUT] result will point to the entry in the container if we find a match /// @returns true: match, false: no match bool find_on_market( const OrderPtr& order, typename TrackerMap::iterator& result); /// @brief find stop order in a container. /// @param order is the the stop order we are looking for /// @param[OUT] result will point to the entry in the container if we find a match /// @returns true: match, false: no match bool find_in_stop_orders( const OrderPtr& order, typename TrackerMap::iterator& result); /// @brief add incoming stop order to stops colletion unless it's already /// on the market. /// @return true if added to stops, false if it should go directly to the order book. bool add_stop_order(Tracker & tracker); /// @brief See if any stop orders should go on the market. void check_stop_orders(bool side, Price price, TrackerMap & stops); /// @brief accept pending (formerly stop) orders. void submit_pending_orders(); /////////////////////////////// // Callback interfaces as // virtual methods to simplify // derived classes. /////////////////////////////// // Order Listener interface /// @brief callback for an order accept virtual void on_accept(const OrderPtr& order, Quantity quantity){} virtual void on_accept_stop(const OrderPtr& order){} virtual void on_trigger_stop(const OrderPtr& order){} /// @brief callback for an order reject virtual void on_reject(const OrderPtr& order, const char* reason){} /// @brief callback for an order fill /// @param order the inbound order /// @param matched_order the matched order /// @param fill_qty the quantity of this fill /// @param fill_price the price of this fill virtual void on_fill(const OrderPtr& order, const OrderPtr& matched_order, Quantity fill_qty, Price fill_price, bool inbound_order_filled, bool matched_order_filled){} /// @brief callback for an order cancellation virtual void on_cancel(const OrderPtr& order, Quantity quantity){} /// @brief callback for a STOP cancellation virtual void on_cancel_stop(const OrderPtr& order){} /// @brief callback for an order cancel rejection virtual void on_cancel_reject(const OrderPtr& order, const char* reason){} /// @brief callback for an order replace /// @param order the replaced order /// @param size_delta the change to order quantity /// @param new_price the updated order price virtual void on_replace(const OrderPtr& order, Quantity current_qty, Quantity new_qty, Price new_price){} /// @brief callback for an order replace rejection virtual void on_replace_reject(const OrderPtr& order, const char* reason){} // End of OrderListener Interface /////////////////////////////// // TradeListener Interface /// @brief callback for a trade /// @param book the order book of the fill (not defined whether this is before /// or after fill) /// @param qty the quantity of this fill /// @param price the price of this fill virtual void on_trade(const OrderBook* book, Quantity qty, Price price){} // End of TradeListener Interface /////////////////////////////// // BookListener Interface /// @brief callback for change anywhere in order book virtual void on_order_book_change(){} // End of BookListener Interface /////////////////////////////// private: bool submit_order(Tracker & inbound); bool add_order(Tracker& order_tracker, Price order_price); private: std::string symbol_; TrackerMap bids_; TrackerMap asks_; TrackerMap stopBids_; TrackerMap stopAsks_; TrackerVec pendingOrders_; Callbacks callbacks_; Callbacks workingCallbacks_; bool handling_callbacks_; TypedOrderListener* order_listener_; TypedTradeListener* trade_listener_; TypedOrderBookListener* order_book_listener_; Logger * logger_; Price marketPrice_; }; template OrderBook::OrderBook(const std::string & symbol) : symbol_(symbol), handling_callbacks_(false), order_listener_(nullptr), trade_listener_(nullptr), order_book_listener_(nullptr), logger_(nullptr), marketPrice_(MARKET_ORDER_PRICE) { callbacks_.reserve(16); // Why 16? Why not? workingCallbacks_.reserve(callbacks_.capacity()); } template void OrderBook::set_logger(Logger * logger) { logger_ = logger; } template void OrderBook::set_symbol(const std::string & symbol) { symbol_ = symbol; } template const std::string & OrderBook::symbol() const { return symbol_; } template void OrderBook:: set_market_price(Price price) { Price oldMarketPrice = marketPrice_; marketPrice_ = price; if(price > oldMarketPrice || oldMarketPrice == MARKET_ORDER_PRICE) { // price has gone up: check stop bids bool buySide = true; check_stop_orders(buySide, price, stopBids_); } else if(price < oldMarketPrice || oldMarketPrice == MARKET_ORDER_PRICE) { // price has gone down: check stop asks bool buySide = false; check_stop_orders(buySide, price, stopAsks_); } } /// @brief Get current market price. /// The market price is normally the price at which the last trade happened. template Price OrderBook::market_price() const { return marketPrice_; } template void OrderBook::set_order_listener(TypedOrderListener* listener) { order_listener_ = listener; } template void OrderBook::set_trade_listener(TypedTradeListener* listener) { trade_listener_ = listener; } template void OrderBook::set_order_book_listener(TypedOrderBookListener* listener) { order_book_listener_ = listener; } template bool OrderBook::add(const OrderPtr& order, OrderConditions conditions) { bool matched = false; // If the order is invalid, ignore it if (order->order_qty() == 0) { callbacks_.push_back(TypedCallback::reject(order, "size must be positive")); } else { Tracker inbound(order, conditions); if(inbound.ptr()->stop_price() != 0 && add_stop_order(inbound)) { // The order has been added to stops callbacks_.push_back(TypedCallback::accept_stop(order)); } else { size_t accept_cb_index = callbacks_.size(); callbacks_.push_back(TypedCallback::accept(order)); matched = submit_order(inbound); // Note the filled qty in the accept callback callbacks_[accept_cb_index].quantity = inbound.filled_qty(); // Cancel any unfilled IOC order if (inbound.immediate_or_cancel() && !inbound.filled()) { // NOTE - this may need he actual open qty??? callbacks_.push_back(TypedCallback::cancel(order, 0)); } } // If adding this order triggered any stops // handle those stops now while(!pendingOrders_.empty()) { submit_pending_orders(); } callbacks_.push_back(TypedCallback::book_update(this)); } callback_now(); return matched; } template void OrderBook::cancel(const OrderPtr& order) { bool found = false; bool foundStop = false; Quantity open_qty; // If the cancel is a buy order if (order->is_buy()) { typename TrackerMap::iterator bid; find_on_market(order, bid); if (bid != bids_.end()) { open_qty = bid->second.open_qty(); // Remove from container for cancel bids_.erase(bid); found = true; } else if (order->stop_price()) { find_in_stop_orders(order, bid); if (bid != stopBids_.end()) { stopBids_.erase(bid); foundStop = true; } } // Else the cancel is a sell order } else { typename TrackerMap::iterator ask; find_on_market(order, ask); if (ask != asks_.end()) { open_qty = ask->second.open_qty(); // Remove from container for cancel asks_.erase(ask); found = true; } else if (order->stop_price()) { find_in_stop_orders(order, ask); if (ask != stopAsks_.end()) { stopAsks_.erase(ask); foundStop = true; } } } // If the cancel was found, issue callback if (found) { callbacks_.push_back(TypedCallback::cancel(order, open_qty)); callbacks_.push_back(TypedCallback::book_update(this)); } else if (foundStop) { callbacks_.push_back(TypedCallback::cancel_stop(order)); callbacks_.push_back(TypedCallback::book_update(this)); } else { callbacks_.push_back(TypedCallback::cancel_reject(order, "not found")); } callback_now(); } template bool OrderBook::replace( const OrderPtr& order, int64_t size_delta, Price new_price) { bool matched = false; bool price_change = new_price && (new_price != order->price()); Price price = (new_price == PRICE_UNCHANGED) ? order->price() : new_price; // If the order to replace is a buy order TrackerMap & market = order->is_buy() ? bids_ : asks_; typename TrackerMap::iterator pos; if(find_on_market(order, pos)) { // If this is a valid replace const Tracker& tracker = pos->second; // If there is not enough open quantity for the size reduction if (size_delta < 0 && ((int)tracker.open_qty() < -size_delta)) { // get rid of as much as we can size_delta = -int(tracker.open_qty()); if(size_delta == 0) { // if there is nothing to get rid of // Reject the replace callbacks_.push_back(TypedCallback::replace_reject(tracker.ptr(), "order is already filled")); return false; } } // Accept the replace callbacks_.push_back( TypedCallback::replace(order, pos->second.open_qty(), size_delta, price)); Quantity new_open_qty = pos->second.open_qty() + size_delta; pos->second.change_qty(size_delta); // Update my copy // If the size change will close the order if (!new_open_qty) { // Cancel with NO open qty (should be zero after replace) callbacks_.push_back(TypedCallback::cancel(order, 0)); market.erase(pos); // Remove order } else { // Else rematch the new order - there could be a price change // or size change - that could cause all or none match auto order = pos->second; market.erase(pos); // Remove old order order matched = add_order(order, price); // Add order } // If replace any order this order triggered any trades // which triggered any stops // handle those stops now while(!pendingOrders_.empty()) { submit_pending_orders(); } callbacks_.push_back(TypedCallback::book_update(this)); } else { // not found callbacks_.push_back( TypedCallback::replace_reject(order, "not found")); } callback_now(); return matched; } template bool OrderBook::add_stop_order(Tracker & tracker) { bool isBuy = tracker.ptr()->is_buy(); ComparablePrice key(isBuy, tracker.ptr()->stop_price()); // if the market price is a better deal then the stop price, it's not time to panic bool isStopped = key < marketPrice_; if(isStopped) { if(isBuy) { stopBids_.emplace(key, std::move(tracker)); } else { stopAsks_.emplace(key, std::move(tracker)); } } return isStopped; } template void OrderBook::check_stop_orders(bool side, Price price, TrackerMap & stops) { ComparablePrice until(side, price); auto pos = stops.begin(); while(pos != stops.end()) { auto here = pos++; if(until > here->first) { break; } pendingOrders_.push_back(std::move(here->second)); stops.erase(here); } } template void OrderBook::submit_pending_orders() { TrackerVec pending; pending.swap(pendingOrders_); for(auto pos = pending.begin(); pos != pending.end(); ++pos) { Tracker & tracker = *pos; submit_order(tracker); callbacks_.push_back(TypedCallback::trigger_stop(tracker.ptr())); } } template bool OrderBook::submit_order(Tracker & inbound) { Price order_price = inbound.ptr()->price(); return add_order(inbound, order_price); } template bool OrderBook::find_on_market( const OrderPtr& order, typename TrackerMap::iterator& result) { const ComparablePrice key(order->is_buy(), order->price()); TrackerMap & sideMap = order->is_buy() ? bids_ : asks_; for (result = sideMap.find(key); result != sideMap.end(); ++result) { // If this is the correct bid if (result->second.ptr() == order) { return true; } else if (key < result->first) { // exit early if result is beyond the matching prices result = sideMap.end(); return false; } } return false; } template bool OrderBook::find_in_stop_orders( const OrderPtr& order, typename TrackerMap::iterator& result) { const ComparablePrice key(order->is_buy(), order->stop_price()); TrackerMap & sideMap = order->is_buy() ? stopBids_ : stopAsks_; for (result = sideMap.find(key); result != sideMap.end(); ++result) { // If this is the correct bid if (result->second.ptr() == order) { return true; } else if (key < result->first) { // exit early if result is beyond the matching prices result = sideMap.end(); return false; } } return false; } // Try to match order. Generate trades. // If not completely filled and not IOC, // add the order to the order book template bool OrderBook::add_order(Tracker& inbound, Price order_price) { bool matched = false; OrderPtr& order = inbound.ptr(); DeferredMatches deferred_aons; // Try to match with current orders if (order->is_buy()) { matched = match_order(inbound, order_price, asks_, deferred_aons); } else { matched = match_order(inbound, order_price, bids_, deferred_aons); } // If order has remaining open quantity and is not immediate or cancel if (inbound.open_qty() && !inbound.immediate_or_cancel()) { // If this is a buy order if (order->is_buy()) { // Insert into bids bids_.insert(std::make_pair(ComparablePrice(true, order_price), inbound)); // and see if that satisfies any ask orders if(check_deferred_aons(deferred_aons, asks_, bids_)) { matched = true; } } else { // Else this is a sell order // Insert into asks asks_.insert(std::make_pair(ComparablePrice(false, order_price), inbound)); if(check_deferred_aons(deferred_aons, bids_, asks_)) { matched = true; } } } return matched; } template bool OrderBook::check_deferred_aons(DeferredMatches & aons, TrackerMap & deferredTrackers, TrackerMap & marketTrackers) { bool result = false; DeferredMatches ignoredAons; for(auto pos = aons.begin(); pos != aons.end(); ++pos) { auto entry = *pos; ComparablePrice current_price = entry->first; Tracker & tracker = entry->second; bool matched = match_order(tracker, current_price.price(), marketTrackers, ignoredAons); result |= matched; if(tracker.filled()) { deferredTrackers.erase(entry); } } return result; } /// Try to match order at 'price' against 'current' orders /// If successful /// generate trade(s) /// if any current order is complete, remove from 'current' orders template bool OrderBook::match_order(Tracker& inbound, Price inbound_price, TrackerMap& current_orders, DeferredMatches & deferred_aons) { if(inbound.all_or_none()) { return match_aon_order(inbound, inbound_price, current_orders, deferred_aons); } return match_regular_order(inbound, inbound_price, current_orders, deferred_aons); } template bool OrderBook::match_regular_order(Tracker& inbound, Price inbound_price, TrackerMap& current_orders, DeferredMatches & deferred_aons) { // while incoming ! satisfied // current is reg->trade // current is AON: // incoming satisfies AON ->TRADE // add AON to deferred // loop bool matched = false; Quantity inbound_qty = inbound.open_qty(); typename TrackerMap::iterator pos = current_orders.begin(); while(pos != current_orders.end() && !inbound.filled()) { auto entry = pos++; const ComparablePrice & current_price = entry->first; if(!current_price.matches(inbound_price)) { // no more trades against current orders are possible break; } ////////////////////////////////////// // Current price matches inbound price Tracker & current_order = entry->second; Quantity current_quantity = current_order.open_qty(); if(current_order.all_or_none()) { // if the inbound order can satisfy the current order's AON condition if(current_quantity <= inbound_qty) { // current is AON, inbound is not AON. // inbound can satisfy current's AON Quantity traded = create_trade(inbound, current_order); if(traded > 0) { matched = true; // assert traded == current_quantity current_orders.erase(entry); inbound_qty -= traded; } } else { // current is AON, inbound is not AON. // inbound is not enough to satisfy current order's AON deferred_aons.push_back(entry); } } else { // neither are AON Quantity traded = create_trade(inbound, current_order); if(traded > 0) { matched = true; if(current_order.filled()) { current_orders.erase(entry); } inbound_qty -= traded; } } } return matched; } template bool OrderBook::match_aon_order(Tracker& inbound, Price inbound_price, TrackerMap& current_orders, DeferredMatches & deferred_aons) { bool matched = false; Quantity inbound_qty = inbound.open_qty(); Quantity deferred_qty = 0; DeferredMatches deferred_matches; typename TrackerMap::iterator pos = current_orders.begin(); while(pos != current_orders.end() && !inbound.filled()) { auto entry = pos++; const ComparablePrice current_price = entry->first; if(!current_price.matches(inbound_price)) { // no more trades against current orders are possible break; } ////////////////////////////////////// // Current price matches inbound price Tracker & current_order = entry->second; Quantity current_quantity = current_order.open_qty(); if(current_order.all_or_none()) { // AON::AON // if the inbound order can satisfy the current order's AON condition if(current_quantity <= inbound_qty) { // if the the matched quantity can satisfy // the inbound order's AON condition if(inbound_qty <= current_quantity + deferred_qty) { // Try to create the deferred trades (if any) before creating // the trade with the current order. // What quantity will we need from the deferred orders? Quantity maxQty = inbound_qty - current_quantity; if(maxQty == try_create_deferred_trades( inbound, deferred_matches, maxQty, maxQty, current_orders)) { inbound_qty -= maxQty; // finally execute this trade Quantity traded = create_trade(inbound, current_order); if(traded > 0) { // assert traded == current_quantity inbound_qty -= traded; matched = true; current_orders.erase(entry); } } } else { // AON::AON -- inbound could satisfy current, but // current cannot satisfy inbound; deferred_qty += current_quantity; deferred_matches.push_back(entry); } } else { // AON::AON -- inbound cannot satisfy current's AON deferred_aons.push_back(entry); } } else { // AON::REG // if we have enough to satisfy inbound if(inbound_qty <= current_quantity + deferred_qty) { Quantity traded = try_create_deferred_trades( inbound, deferred_matches, inbound_qty, // create as many as possible (inbound_qty > current_quantity) ? (inbound_qty - current_quantity) : 0, // but we need at least this many current_orders); if(inbound_qty <= current_quantity + traded) { traded += create_trade(inbound, current_order); if(traded > 0) { inbound_qty -= traded; matched = true; } if(current_order.filled()) { current_orders.erase(entry); } } } else { // not enough to satisfy inbound, yet. // remember the current order for later use deferred_qty += current_quantity; deferred_matches.push_back(entry); } } } return matched; } namespace { const size_t AON_LIMIT = 5; } template Quantity OrderBook::try_create_deferred_trades( Tracker& inbound, DeferredMatches & deferred_matches, Quantity maxQty, // do not exceed Quantity minQty, // must be at least TrackerMap& current_orders) { Quantity traded = 0; // create a vector of proposed trade quantities: std::vector fills(deferred_matches.size()); std::fill(fills.begin(), fills.end(), 0); Quantity foundQty = 0; auto pos = deferred_matches.begin(); for(size_t index = 0; foundQty < maxQty && pos != deferred_matches.end(); ++index) { auto entry = *pos++; Tracker & tracker = entry->second; Quantity qty = tracker.open_qty(); // if this would put us over the limit if(foundQty + qty > maxQty) { if(tracker.all_or_none()) { qty = 0; } else { qty = maxQty - foundQty; // assert qty <= tracker.open_qty(); } } foundQty += qty; fills[index] = qty; } if(foundQty >= minQty && foundQty <= maxQty) { // pass through deferred matches again, doing the trades. auto pos = deferred_matches.begin(); for(size_t index = 0; traded < foundQty && pos != deferred_matches.end(); ++index) { auto entry = *pos++; Tracker & tracker = entry->second; traded += create_trade(inbound, tracker, fills[index]); if(tracker.filled()) { current_orders.erase(entry); } } } return traded; } template Quantity OrderBook::create_trade(Tracker& inbound_tracker, Tracker& current_tracker, Quantity maxQuantity) { Price cross_price = current_tracker.ptr()->price(); // If current order is a market order, cross at inbound price if (MARKET_ORDER_PRICE == cross_price) { cross_price = inbound_tracker.ptr()->price(); } if(MARKET_ORDER_PRICE == cross_price) { cross_price = marketPrice_; } if(MARKET_ORDER_PRICE == cross_price) { // No price available for this order return 0; } Quantity fill_qty = (std::min)(maxQuantity, (std::min)(inbound_tracker.open_qty(), current_tracker.open_qty())); if(fill_qty > 0) { inbound_tracker.fill(fill_qty); current_tracker.fill(fill_qty); set_market_price(cross_price); typename TypedCallback::FillFlags fill_flags = TypedCallback::ff_neither_filled; if (!inbound_tracker.open_qty()) { fill_flags = (typename TypedCallback::FillFlags)( fill_flags | TypedCallback::ff_inbound_filled); } if (!current_tracker.open_qty()) { fill_flags = (typename TypedCallback::FillFlags)( fill_flags | TypedCallback::ff_matched_filled); } callbacks_.push_back(TypedCallback::fill(inbound_tracker.ptr(), current_tracker.ptr(), fill_qty, cross_price, fill_flags)); } return fill_qty; } template void OrderBook::move_callbacks(Callbacks& target) { COMPLAIN_ONCE("Ignoring call to deprecated method: move_callbacks"); // We get to decide when callbacks happen. // And it *certainly* doesn't happen on another thread! } template void OrderBook::perform_callbacks() { COMPLAIN_ONCE("Ignoring call to deprecated method: perform_callbacks"); // We get to decide when callbacks happen. } template void OrderBook::callback_now() { // protect against recursive calls // callbacks generated in response to previous callbacks // will be handled before this method returns. if(!handling_callbacks_) { handling_callbacks_ = true; // remove all accumulated callbacks in case // new callbacks are generated by the application code. while(!callbacks_.empty()) { // if we needed more entries, be sure that both containers have them. workingCallbacks_.reserve(callbacks_.capacity()); workingCallbacks_.swap(callbacks_); for (auto cb = workingCallbacks_.begin(); cb != workingCallbacks_.end(); ++cb) { try { perform_callback(*cb); } catch(const std::exception & ex) { if(logger_) { logger_->log_exception("Caught exception during callback: ", ex); } else { std::cerr << "Caught exception during callback: " << ex.what() << std::endl; } } catch(...) { if(logger_) { logger_->log_message("Caught unknown exception during callback"); } else { std::cerr << "Caught unknown exception during callback" << std::endl; } } } workingCallbacks_.clear(); } handling_callbacks_ = false; } } template void OrderBook::perform_callback(TypedCallback& cb) { switch (cb.type) { case TypedCallback::cb_order_fill: { bool inbound_filled = (cb.flags & (TypedCallback::ff_inbound_filled | TypedCallback::ff_both_filled)) != 0; bool matched_filled = (cb.flags & (TypedCallback::ff_matched_filled | TypedCallback::ff_both_filled)) != 0; on_fill(cb.order, cb.matched_order, cb.quantity, cb.price, inbound_filled, matched_filled); if(order_listener_) { order_listener_->on_fill(cb.order, cb.matched_order, cb.quantity, cb.price); } on_trade(this, cb.quantity, cb.price); if(trade_listener_) { trade_listener_->on_trade(this, cb.quantity, cb.price); } break; } case TypedCallback::cb_order_accept: on_accept(cb.order, cb.quantity); if(order_listener_) { order_listener_->on_accept(cb.order); } break; case TypedCallback::cb_order_accept_stop: on_accept_stop(cb.order); if(order_listener_) { order_listener_->on_accept(cb.order); } break; case TypedCallback::cb_order_trigger_stop: on_trigger_stop(cb.order); if(order_listener_) { order_listener_->on_trigger_stop(cb.order); } break; case TypedCallback::cb_order_reject: on_reject(cb.order, cb.reject_reason); if(order_listener_) { order_listener_->on_reject(cb.order, cb.reject_reason); } break; case TypedCallback::cb_order_cancel: on_cancel(cb.order, cb.quantity); if(order_listener_) { order_listener_->on_cancel(cb.order); } break; case TypedCallback::cb_order_cancel_stop: on_cancel_stop(cb.order); if(order_listener_) { order_listener_->on_cancel(cb.order); } break; case TypedCallback::cb_order_cancel_reject: on_cancel_reject(cb.order, cb.reject_reason); if(order_listener_) { order_listener_->on_cancel_reject(cb.order, cb.reject_reason); } break; case TypedCallback::cb_order_replace: on_replace(cb.order, cb.order->order_qty(), cb.order->order_qty() + cb.delta, cb.price); if(order_listener_) { order_listener_->on_replace(cb.order, cb.delta, cb.price); } break; case TypedCallback::cb_order_replace_reject: on_replace_reject(cb.order, cb.reject_reason); if(order_listener_) { order_listener_->on_replace_reject(cb.order, cb.reject_reason); } break; case TypedCallback::cb_book_update: on_order_book_change(); if(order_book_listener_) { order_book_listener_->on_order_book_change(this); } break; default: { std::stringstream msg; msg << "Unexpected callback type " << cb.type; std::runtime_error(msg.str()); break; } } } template std::ostream & OrderBook::log(std::ostream & out) const { for(auto ask = asks_.rbegin(); ask != asks_.rend(); ++ask) { out << " Ask " << ask->second.open_qty() << " @ " << ask->first << std::endl; } for(auto bid = bids_.begin(); bid != bids_.end(); ++bid) { out << " Bid " << bid->second.open_qty() << " @ " << bid->first << std::endl; } return out; } } } ================================================ FILE: src/book/order_book_listener.h ================================================ // Copyright (c) 2012, 2013 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #pragma once #include "order_book.h" namespace liquibook { namespace book { /// @brief generic listener of order book events template class OrderBookListener { public: /// @brief callback for change anywhere in order book virtual void on_order_book_change(const OrderBook* book) = 0; }; } } ================================================ FILE: src/book/order_listener.h ================================================ // Copyright (c) 2012, 2013 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #pragma once namespace liquibook { namespace book { /// @brief generic listener of order events. Implement to build a full order book feed. // Used by common version of OrderBook::process_callback(). template class OrderListener { public: /// @brief callback for an order accept virtual void on_accept(const OrderPtr& order) = 0; /// @brief callback for triggered STOP order virtual void on_trigger_stop(const OrderPtr& order) {} /// @brief callback for an order reject virtual void on_reject(const OrderPtr& order, const char* reason) = 0; /// @brief callback for an order fill /// @param order the inbound order /// @param matched_order the matched order /// @param fill_qty the quantity of this fill /// @param fill_price the price of this fill virtual void on_fill(const OrderPtr& order, const OrderPtr& matched_order, Quantity fill_qty, Price fill_price) = 0; /// @brief callback for an order cancellation virtual void on_cancel(const OrderPtr& order) = 0; /// @brief callback for an order cancel rejection virtual void on_cancel_reject(const OrderPtr& order, const char* reason) = 0; /// @brief callback for an order replace /// @param order the replaced order /// @param size_delta the change to order quantity /// @param new_price the updated order price virtual void on_replace(const OrderPtr& order, const int64_t& size_delta, Price new_price) = 0; /// @brief callback for an order replace rejection virtual void on_replace_reject(const OrderPtr& order, const char* reason) = 0; }; } } ================================================ FILE: src/book/order_tracker.h ================================================ // Copyright (c) 2012 - 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #pragma once #include "types.h" namespace liquibook { namespace book { /// @brief Tracker of an order's state, to keep inside the OrderBook. /// Kept separate from the order itself. template class OrderTracker { public: /// @brief construct OrderTracker(const OrderPtr& order, OrderConditions conditions = 0); /// @brief modify the order quantity void change_qty(int64_t delta); /// @brief fill an order /// @param qty the number of shares filled in this fill void fill(Quantity qty); /// @brief is there no remaining open quantity in this order? bool filled() const; /// @brief get the total filled quantity of this order Quantity filled_qty() const; /// @brief get the open quantity of this order Quantity open_qty() const; /// @brief get the order pointer const OrderPtr& ptr() const; /// @brief get the order pointer OrderPtr& ptr(); /// @ brief is this order marked all or none? bool all_or_none() const; /// @ brief is this order marked immediate or cancel? bool immediate_or_cancel() const; Quantity reserve(int64_t reserved); private: OrderPtr order_; Quantity open_qty_; int64_t reserved_; OrderConditions conditions_; }; template OrderTracker::OrderTracker( const OrderPtr& order, OrderConditions conditions) : order_(order), open_qty_(order->order_qty()), reserved_(0), conditions_(conditions) { #if defined(LIQUIBOOK_ORDER_KNOWS_CONDITIONS) if(order->all_or_none()) { conditions |= oc_all_or_none; } if(order->immediate_or_cancel()) { conditions |= oc_immediate_or_cancel; } #endif } template Quantity OrderTracker::reserve(int64_t reserved) { reserved_ += reserved; return open_qty_ - reserved_; } template void OrderTracker::change_qty(int64_t delta) { if ((delta < 0 && (int)open_qty_ < std::abs(delta))) { throw std::runtime_error("Replace size reduction larger than open quantity"); } open_qty_ += delta; } template void OrderTracker::fill(Quantity qty) { if (qty > open_qty_) { throw std::runtime_error("Fill size larger than open quantity"); } open_qty_ -= qty; } template bool OrderTracker::filled() const { return open_qty_ == 0; } template Quantity OrderTracker::filled_qty() const { return order_->order_qty() - open_qty(); } // TODO: Rename this to be available and change the rest of the // system to use that, then provide a method to get to the open // quantity without considering reserved template Quantity OrderTracker::open_qty() const { return open_qty_ - reserved_; } template const OrderPtr& OrderTracker::ptr() const { return order_; } template OrderPtr& OrderTracker::ptr() { return order_; } template bool OrderTracker::all_or_none() const { return bool(conditions_ & oc_all_or_none); } template bool OrderTracker::immediate_or_cancel() const { return bool((conditions_ & oc_immediate_or_cancel) != 0); } } } ================================================ FILE: src/book/trade_listener.h ================================================ // Copyright (c) 2012, 2013 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #pragma once namespace liquibook { namespace book { /// @brief listener of trade events. Implement to build a trade feed. template class TradeListener { public: /// @brief callback for a trade /// @param book the order book of the fill (not defined whether this is before /// or after fill) /// @param qty the quantity of this fill /// @param price the price of this fill virtual void on_trade(const OrderBook* book, Quantity qty, Price price) = 0; }; } } ================================================ FILE: src/book/types.h ================================================ // Copyright (c) 2012, 2013 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #pragma once #include #include #include namespace liquibook { namespace book { // Types used in Liquibook typedef uint64_t Price; typedef uint64_t Quantity; typedef uint64_t Cost; typedef uint32_t FillId; typedef uint32_t ChangeId; typedef uint32_t OrderConditions; enum OrderCondition { oc_no_conditions = 0, oc_all_or_none = 1, oc_immediate_or_cancel = oc_all_or_none << 1, oc_fill_or_kill = oc_all_or_none | oc_immediate_or_cancel, oc_stop = oc_immediate_or_cancel << 1 }; namespace { // Constants used in liquibook API const Price MARKET_ORDER_PRICE(0); const Price PRICE_UNCHANGED(0); const Quantity QUANTITY_MAX(UINT64_MAX); const int64_t SIZE_UNCHANGED(0); } } } // namespace ================================================ FILE: src/book/version.h ================================================ // Copyright (c) 2012 - 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #pragma once namespace liquibook { struct Version { static const int MAJOR = 2; static const int MINOR = 0; static const int PATCH = 0; static const int RELEASE_DATE = 20170222; }; } ================================================ FILE: src/liquibook_export.h ================================================ // Copyright (c) 2013 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #ifdef _MSC_VER # pragma once #endif #ifndef liquibook_export_H #define liquibook_export_H // Compile time controls for library generation. Define with /D or #define // To produce or use a static library: #define LIQUIBOOK_HAS_DLL=0 // Default is to produce/use a DLL // While building the LIQUIBOOK_ library: #define LIQUIBOOK_BUILD_DLL // Default is to export symbols from a pre-built LIQUIBOOK DLL // // Within LIQUIBOOK use the Liquibook_Export macro where a __declspec is needed. #if defined (_WIN32) # if !defined (LIQUIBOOK_HAS_DLL) # define LIQUIBOOK_HAS_DLL 1 # endif /* ! LIQUIBOOK_HAS_DLL */ # if defined (LIQUIBOOK_HAS_DLL) && (LIQUIBOOK_HAS_DLL == 1) # if defined (LIQUIBOOK_BUILD_DLL) # define Liquibook_Export __declspec(dllexport) # else /* LIQUIBOOK_BUILD_DLL */ # define Liquibook_Export __declspec(dllimport) # endif /* LIQUIBOOK_BUILD_DLL */ # else /* LIQUIBOOK_HAS_DLL == 1 */ # define Liquibook_Export # endif /* LIQUIBOOK_HAS_DLL == 1 */ # else /* !_WIN32 */ # define Liquibook_Export __attribute__ ((visibility("default"))) # endif /* _WIN32 */ #endif /* LIQUIBOOK_EXPORT_H */ ================================================ FILE: src/simple/liquibook_simple.mpc ================================================ project : liquibook_lib, liquibook_book { sharedname = staticname = * } ================================================ FILE: src/simple/simple_order.cpp ================================================ // Copyright (c) 2012, 2013 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #include "simple_order.h" #include namespace liquibook { namespace simple { uint32_t SimpleOrder::last_order_id_(0); SimpleOrder::SimpleOrder( bool is_buy, book::Price price, book::Quantity qty, book::Price stop_price, book::OrderConditions conditions) : state_(os_new), is_buy_(is_buy), order_qty_(qty), price_(price), stop_price_(stop_price), conditions_(conditions), filled_qty_(0), filled_cost_(0), order_id_(++last_order_id_) { } const OrderState& SimpleOrder::state() const { return state_; } bool SimpleOrder::is_buy() const { return is_buy_; } book::Price SimpleOrder::price() const { return price_; } book::Price SimpleOrder::stop_price() const { return stop_price_; } book::OrderConditions SimpleOrder::conditions() const { return conditions_; } bool SimpleOrder::all_or_none() const { return (conditions_ & book::OrderCondition::oc_all_or_none) != 0; } bool SimpleOrder::immediate_or_cancel() const { return (conditions_ & book::OrderCondition::oc_immediate_or_cancel) != 0; } book::Quantity SimpleOrder::order_qty() const { return order_qty_; } book::Quantity SimpleOrder::open_qty() const { // If not completely filled, calculate if (filled_qty_ < order_qty_) { return order_qty_ - filled_qty_; // Else prevent accidental overflow } else { return 0; } } const book::Quantity& SimpleOrder::filled_qty() const { return filled_qty_; } const book::Cost& SimpleOrder::filled_cost() const { return filled_cost_; } void SimpleOrder::fill(book::Quantity fill_qty, book::Cost fill_cost, book::FillId /*fill_id*/) { filled_qty_ += fill_qty; filled_cost_ += fill_cost; if (!open_qty()) { state_ = os_complete; } } void SimpleOrder::accept() { if (os_new == state_) { state_ = os_accepted; } } void SimpleOrder::cancel() { if (os_complete != state_) { state_ = os_cancelled; } } void SimpleOrder::replace(book::Quantity size_delta, book::Price new_price) { if (os_accepted == state_) { order_qty_ += size_delta; price_ = new_price; } } } } ================================================ FILE: src/simple/simple_order.h ================================================ // Copyright (c) 2012, 2013 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #pragma once #define LIQUIBOOK_ORDER_KNOWS_CONDITIONS #include #include namespace liquibook { namespace simple { enum OrderState { os_new, os_accepted, os_complete, os_cancelled, os_rejected }; /// @brief impelementation of the Order interface for testing purposes. class SimpleOrder : public book::Order { public: SimpleOrder(bool is_buy, book::Price price, book::Quantity qty, book::Price stop_price = 0, book::OrderConditions conditions = book::OrderCondition::oc_no_conditions); /// @brief get the order's state const OrderState& state() const; /// @brief is this order a buy? virtual bool is_buy() const; /// @brief get the limit price of this order virtual book::Price price() const; virtual book::Price stop_price()const; /// @brief get the quantity of this order virtual book::Quantity order_qty() const; /// @brief get the open quantity of this order virtual book::Quantity open_qty() const; /// @brief get the filled quantity of this order virtual const book::Quantity& filled_qty() const; /// @brief get the total filled cost of this order const book::Cost& filled_cost() const; /// @brief notify of a fill of this order /// @param fill_qty the number of shares in this fill /// @param fill_cost the total amount of this fill /// @fill_id the unique identifier of this fill virtual void fill(book::Quantity fill_qty, book::Cost fill_cost, book::FillId fill_id); /// @brief get order conditions as a bit mask virtual book::OrderConditions conditions() const; /// @brief if no trades should happen until the order /// can be filled completely. /// Note: one or more trades may be used to fill the order. virtual bool all_or_none() const; /// @brief After generating as many trades as possible against /// orders already on the market, cancel any remaining quantity. virtual bool immediate_or_cancel() const; /// @brief exchange accepted this order void accept(); /// @brief exchange cancelled this order void cancel(); /// @brief exchange replaced this order /// @param size_delta change to the order quantity /// @param new_price the new price void replace(book::Quantity size_delta, book::Price new_price); private: OrderState state_; bool is_buy_; book::Quantity order_qty_; book::Price price_; book::Price stop_price_; book::OrderConditions conditions_; book::Quantity filled_qty_; book::Cost filled_cost_; static uint32_t last_order_id_; public: const uint32_t order_id_; }; } } ================================================ FILE: src/simple/simple_order_book.h ================================================ // Copyright (c) 2012, 2013 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #pragma once #include "simple_order.h" #include #include namespace liquibook { namespace simple { // @brief binding of DepthOrderBook template with SimpleOrder* order pointer. template class SimpleOrderBook : public book::DepthOrderBook { public: typedef book::Callback SimpleCallback; typedef uint32_t FillId; SimpleOrderBook(); // Override callback handling to update SimpleOrder state virtual void perform_callback(SimpleCallback& cb); private: FillId fill_id_; }; template SimpleOrderBook::SimpleOrderBook() : fill_id_(0) { } template inline void SimpleOrderBook::perform_callback(SimpleCallback& cb) { book::DepthOrderBook::perform_callback(cb); switch(cb.type) { case SimpleCallback::cb_order_accept: cb.order->accept(); break; case SimpleCallback::cb_order_fill: { // Increment fill ID once ++fill_id_; // Update the orders book::Cost fill_cost = cb.quantity * cb.price; cb.matched_order->fill(cb.quantity, fill_cost, fill_id_); cb.order->fill(cb.quantity, fill_cost, fill_id_); break; } case SimpleCallback::cb_order_cancel: cb.order->cancel(); break; case SimpleCallback::cb_order_replace: // Modify the order itself cb.order->replace(cb.delta, cb.price); break; default: // Nothing break; } } } } ================================================ FILE: test/.gitignore ================================================ pt_order_book ut_all_or_none ut_bbo_order_book ut_depth ut_immediate_or_cancel ut_listeners ut_order_book ut_order_book_shared_ptr ================================================ FILE: test/latency/clock_gettime.h ================================================ #pragma once // clock_gettime is not defined on Windows / msvc // Let's fix that. #include #ifdef _MSC_VER // Windows.h should already be included, but Just In Case #include static const int CLOCK_REALTIME = 0; // The following code was painstakingly programmed (i.e. copy/pasted from stack overflow) // http://stackoverflow.com/questions/5404277/porting-clock-gettime-to-windows // Thanks, StackOverflow LARGE_INTEGER getFILETIMEoffset() { SYSTEMTIME s; FILETIME f; LARGE_INTEGER t; s.wYear = 1970; s.wMonth = 1; s.wDay = 1; s.wHour = 0; s.wMinute = 0; s.wSecond = 0; s.wMilliseconds = 0; SystemTimeToFileTime(&s,&f); t.QuadPart = f.dwHighDateTime; t.QuadPart <<= 32; t.QuadPart |= f.dwLowDateTime; return (t); } int clock_gettime(int X,struct timespec *tv) { LARGE_INTEGER t; FILETIME f; double microseconds; static LARGE_INTEGER offset; static double frequencyToMicroseconds; static int initialized = 0; static BOOL usePerformanceCounter = 0; if(!initialized) { LARGE_INTEGER performanceFrequency; initialized = 1; usePerformanceCounter = QueryPerformanceFrequency(&performanceFrequency); if(usePerformanceCounter) { QueryPerformanceCounter(&offset); frequencyToMicroseconds = (double)performanceFrequency.QuadPart / 1000000.; } else { offset = getFILETIMEoffset(); frequencyToMicroseconds = 10.; } } if(usePerformanceCounter) QueryPerformanceCounter(&t); else { GetSystemTimeAsFileTime(&f); t.QuadPart = f.dwHighDateTime; t.QuadPart <<= 32; t.QuadPart |= f.dwLowDateTime; } t.QuadPart -= offset.QuadPart; microseconds = (double)t.QuadPart / frequencyToMicroseconds; t.QuadPart = (LONGLONG)microseconds; tv->tv_sec = (time_t)( t.QuadPart / 1000000); unsigned int uSec = t.QuadPart % 1000000; tv->tv_nsec = uSec * 1000; return 0; } #endif ================================================ FILE: test/latency/liquibook_latency.mpc ================================================ project (lt_order_book) : liquibook_book, liquibook_simple, liquibook_test { exename = * } ================================================ FILE: test/latency/lt_order_book.cpp ================================================ // Copyright (c) 2012, 2013 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #include #include #include "clock_gettime.h" #include #include #include using namespace liquibook; using namespace liquibook::book; typedef simple::SimpleOrderBook<5> FullDepthOrderBook; typedef simple::SimpleOrderBook<1> BboOrderBook; typedef book::OrderBook NoDepthOrderBook; void build_histogram(timespec* timestamps, int count) { timespec* prev = nullptr; std::cout << "Latency (ns) " << std::endl; for (timespec* timestamp = timestamps; (timestamp - timestamps) <= count; ++timestamp) { if (prev) { timespec elapsed; if (timestamp->tv_sec == prev->tv_sec) { elapsed.tv_sec = 0; elapsed.tv_nsec = timestamp->tv_nsec - prev->tv_nsec; } else { // Second changed elapsed.tv_sec = timestamp->tv_sec - prev->tv_sec; // Borrow from sec if necessary if (prev->tv_nsec > timestamp->tv_nsec) { --elapsed.tv_sec; elapsed.tv_nsec = 1000000000 + timestamp->tv_nsec - prev->tv_nsec; } else { elapsed.tv_nsec = timestamp->tv_nsec - prev->tv_nsec; } } if (elapsed.tv_sec) { std::cout << elapsed.tv_sec * 1000000000 + elapsed.tv_nsec << std::endl; } else { std::cout << elapsed.tv_nsec << std::endl; } } prev = timestamp; } } template int run_test(TypedOrderBook& order_book, TypedOrder** orders, timespec* timestamps) { int count = 0; TypedOrder** pp_order = orders; timespec* timestamp = timestamps; do { // Take timestamp at start of each order int status = clock_gettime(CLOCK_REALTIME, timestamp); if (status) { throw std::runtime_error("clock_gettime() failed"); } order_book.add(*pp_order); ++pp_order; ++timestamp; if (*pp_order == nullptr) { break; } ++count; } while (true); // Take timestamp at end int status = clock_gettime(CLOCK_REALTIME, timestamp); if (status) { throw std::runtime_error("clock_gettime() failed"); } return int(pp_order - orders); } template bool build_and_run_test(uint32_t num_to_try, bool dry_run = false) { TypedOrderBook order_book; simple::SimpleOrder** orders = new simple::SimpleOrder*[num_to_try + 1]; timespec* timestamps = new timespec[num_to_try + 1]; for (uint32_t i = 0; i < num_to_try; ++i) { bool is_buy((i % 2) == 0); uint32_t delta = is_buy ? 1880 : 1884; // AsSK 1893 // ASK 1892 // ASK 1891 // ASK 1890 // ASK 1889 crossable // ASK 1888 crossable // ASK 1887 crossable // ASK 1886 crossable // ASK 1885 crossable // ASK 1884 crossable // BID 1889 crossable // BID 1888 crossable // BID 1887 crossable // BID 1886 crossable // BID 1885 crossable // BID 1884 crossable // BID 1883 // BID 1882 // BID 1881 // BID 1880 Price price = (rand() % 10) + delta; Quantity qty = ((rand() % 10) + 1) * 100; orders[i] = new simple::SimpleOrder(is_buy, price, qty); } orders[num_to_try] = nullptr; // Final null run_test(order_book, orders, timestamps); for (uint32_t i = 0; i <= num_to_try; ++i) { delete orders[i]; } delete [] orders; std::cout << " - complete!" << std::endl; uint32_t remain = uint32_t(order_book.bids().size() + order_book.asks().size()); if (!dry_run) { std::cout << "Building histogram" << std::endl; build_histogram(timestamps, num_to_try); } delete [] timestamps; return true; } int main(int argc, const char* argv[]) { uint32_t num_to_try = 10000; if (argc > 1) { num_to_try = atoi(argv[1]); if (!num_to_try) { num_to_try = 10000; } } std::cout << num_to_try << " order latency test of order book" << std::endl; srand(num_to_try); { std::cout << "starting dry run" << std::endl; build_and_run_test(num_to_try, true); } { std::cout << "testing order book with depth" << std::endl; build_and_run_test(num_to_try); } { std::cout << "testing order book with bbo" << std::endl; build_and_run_test(num_to_try); } { std::cout << "testing order book without depth" << std::endl; build_and_run_test(num_to_try); } } ================================================ FILE: test/perf/liquibook_perf.mpc ================================================ project (pt_order_book) : liquibook_book, liquibook_simple, liquibook_test { exename = * } ================================================ FILE: test/perf/pt_order_book.cpp ================================================ // Copyright (c) 2012, 2013 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #include #include #include #include #include #include using namespace liquibook; using namespace liquibook::book; typedef simple::SimpleOrderBook<5> FullDepthOrderBook; typedef simple::SimpleOrderBook<1> BboOrderBook; typedef book::OrderBook NoDepthOrderBook; template int run_test(TypedOrderBook& order_book, TypedOrder** orders, clock_t end) { int count = 0; TypedOrder** pp_order = orders; do { order_book.add(*pp_order); ++pp_order; if (*pp_order == nullptr) { return -1; } ++count; } while (clock() < end); return int(pp_order - orders); } template bool build_and_run_test(uint32_t dur_sec, uint32_t num_to_try) { std::cout << "trying run of " << num_to_try << " orders"; TypedOrderBook order_book; simple::SimpleOrder** orders = new simple::SimpleOrder*[num_to_try + 1]; for (uint32_t i = 0; i <= num_to_try; ++i) { bool is_buy((i % 2) == 0); uint32_t delta = is_buy ? 1880 : 1884; // ASK 1893 // ASK 1892 // ASK 1891 // ASK 1890 // ASK 1889 crossable // ASK 1888 crossable // ASK 1887 crossable // ASK 1886 crossable // ASK 1885 crossable // ASK 1884 crossable // BID 1889 crossable // BID 1888 crossable // BID 1887 crossable // BID 1886 crossable // BID 1885 crossable // BID 1884 crossable // BID 1883 // BID 1882 // BID 1881 // BID 1880 Price price = (rand() % 10) + delta; Quantity qty = ((rand() % 10) + 1) * 100; orders[i] = new simple::SimpleOrder(is_buy, price, qty); } orders[num_to_try] = nullptr; // Final null clock_t start = clock(); clock_t stop = start + (dur_sec * CLOCKS_PER_SEC); int count = run_test(order_book, orders, stop); for (uint32_t i = 0; i <= num_to_try; ++i) { delete orders[i]; } delete [] orders; if (count > 0) { std::cout << " - complete!" << std::endl; std::cout << "Inserted " << count << " orders in " << dur_sec << " seconds" << ", or " << count / dur_sec << " insertions per sec" << std::endl; uint32_t remain = uint32_t(order_book.bids().size() + order_book.asks().size()); std::cout << "Run matched " << count - remain << " orders" << std::endl; return true; } else { std::cout << " - not enough orders" << std::endl; return false; } return count > 0; } int main(int argc, const char* argv[]) { uint32_t dur_sec = 3; if (argc > 1) { dur_sec = atoi(argv[1]); if (!dur_sec) { dur_sec = 3; } } std::cout << dur_sec << " sec performance test of order book" << std::endl; srand(dur_sec); { std::cout << "testing order book with depth" << std::endl; uint32_t num_to_try = dur_sec * 125000; while (true) { if (build_and_run_test(dur_sec, num_to_try)) { break; } else { num_to_try *= 2; } } } { std::cout << "testing order book with bbo" << std::endl; uint32_t num_to_try = dur_sec * 125000; while (true) { if (build_and_run_test(dur_sec, num_to_try)) { break; } else { num_to_try *= 2; } } } { std::cout << "testing order book without depth" << std::endl; uint32_t num_to_try = dur_sec * 125000; while (true) { if (build_and_run_test(dur_sec, num_to_try)) { break; } else { num_to_try *= 2; } } } } ================================================ FILE: test/unit/changed_checker.h ================================================ // Copyright (c) 2012, 2013 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #include #include namespace liquibook { using book::Depth; using book::DepthLevel; using book::ChangeId; namespace test { template class ChangedChecker { public: typedef Depth SizedDepth; ChangedChecker(SizedDepth& depth) : depth_(depth) { reset(); } void reset() { last_change_ = depth_.last_change(); } bool verify_bid_changed(bool l0, bool l1, bool l2, bool l3, bool l4) { return verify_side_changed(depth_.bids(), l0, l1, l2, l3, l4); } bool verify_ask_changed(bool l0, bool l1, bool l2, bool l3, bool l4) { return verify_side_changed(depth_.asks(), l0, l1, l2, l3, l4); } bool verify_bid_stamps(ChangeId l0, ChangeId l1, ChangeId l2, ChangeId l3, ChangeId l4) { return verify_side_stamps(depth_.bids(), l0, l1, l2, l3, l4); } bool verify_ask_stamps(ChangeId l0, ChangeId l1, ChangeId l2, ChangeId l3, ChangeId l4) { return verify_side_stamps(depth_.asks(), l0, l1, l2, l3, l4); } bool verify_bbo_changed(bool bid_changed, bool ask_changed) { bool matched = true; if (depth_.bids()->changed_since(last_change_)) { if(! bid_changed) { std::cout << "best bid unexpected change" << std::endl; matched = false; } } else if(bid_changed){ std::cout << "best bid expected change" << std::endl; matched = false; } if (depth_.asks()->changed_since(last_change_)){ if(!ask_changed) { std::cout << "best ask unexpected change" << std::endl; matched = false; } } else if(ask_changed){ std::cout << "best ask expected change" << std::endl; matched = false; } return matched; } bool verify_bbo_stamps(ChangeId bid_stamp, ChangeId ask_stamp) { bool matched = true; if (depth_.bids()->last_change() != bid_stamp) { std::cout << "best bid change " << depth_.bids()->last_change() << std::endl; matched = false; } if (depth_.asks()->last_change() != ask_stamp) { std::cout << "best ask change " << depth_.asks()->last_change() << std::endl; matched = false; } return matched; } private: ChangeId last_change_; bool verify_side_stamps(const DepthLevel* start, ChangeId l0, ChangeId l1, ChangeId l2, ChangeId l3, ChangeId l4) { bool matched = true; if (start[0].last_change() != l0) { std::cout << "change id[0] " << start[0].last_change() << std::endl; matched = false; } if (start[1].last_change() != l1) { std::cout << "change id[1] " << start[1].last_change() << std::endl; matched = false; } if (start[2].last_change() != l2) { std::cout << "change id[2] " << start[2].last_change() << std::endl; matched = false; } if (start[3].last_change() != l3) { std::cout << "change id[3] " << start[3].last_change() << std::endl; matched = false; } if (start[4].last_change() != l4) { std::cout << "change id[4] " << start[4].last_change() << std::endl; matched = false; } return matched; } bool verify_level(const DepthLevel* levels, size_t index, bool expected) { bool matched = true; if (levels[index].changed_since(last_change_) != expected) { matched = false; if(expected) { std::cout << "expected change level[" << index << "] " << levels[index].price() << std::endl; } else { std::cout << "unexpected change level[" << index << "] " << levels[index].price() << std::endl; } } return matched; } bool verify_side_changed(const DepthLevel* start, bool l0, bool l1, bool l2, bool l3, bool l4) { bool matched = true; matched = verify_level(start, 0, l0) && matched; matched = verify_level(start, 1, l1) && matched; matched = verify_level(start, 2, l2) && matched; matched = verify_level(start, 3, l3) && matched; matched = verify_level(start, 4, l4) && matched; return matched; } private: SizedDepth& depth_; }; } } // namespace ================================================ FILE: test/unit/depth_check.h ================================================ // Copyright (c) 2012=2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #pragma once #include #include #include using namespace liquibook::book; namespace liquibook { template class DepthCheck { typedef typename SimpleOrderBook::DepthTracker SimpleDepth; public: DepthCheck(const SimpleDepth& depth) : depth_(depth) { reset(); } static bool verify_depth(const DepthLevel& level, const Price& price, uint32_t count, const Quantity& qty) { bool matched = true; if (level.price() != price) { std::cout << "Price " << level.price() << " expecting " << price << std::endl; matched = false; } if (level.order_count() != count) { std::cout << "Level: " << level.price() << " Count " << level.order_count() << " expecting " << count << std::endl; matched = false; } if (level.aggregate_qty() != qty) { std::cout << "Level: " << level.price() << " Quantity " << level.aggregate_qty() << " expecting " << qty << std::endl; matched = false; } if (level.is_excess()) { std::cout << "Marked as excess" << std::endl; matched = false; } return matched; } bool verify_bid(const Price& price, int count, const Quantity& qty) { return verify_depth(*next_bid_++, price, count, qty); } bool verify_ask(const Price& price, int count, const Quantity& qty) { return verify_depth(*next_ask_++, price, count, qty); } bool verify_bids_done() { while(next_bid_ != depth_.last_bid_level() + 1) { auto level = *next_bid_; if(level.order_count() != 0) { return false; } ++next_bid_; } return true; } bool verify_adds_done() { while(next_ask_ != depth_.last_ask_level() + 1) { auto level = *next_ask_; if(level.order_count() != 0) { return false; } ++next_bid_; } return true; } void reset() { next_bid_ = depth_.bids(); next_ask_ = depth_.asks(); } private: const SimpleDepth& depth_; const DepthLevel* next_bid_; const DepthLevel* next_ask_; }; } // namespace ================================================ FILE: test/unit/liquibook_unit.mpc ================================================ project (liquibook_unit_test) : liquibook_test, boost_unit_test_framework, boost_base{ exename = * specific(make) { macros += BOOST_TEST_DYN_LINK } } ================================================ FILE: test/unit/ut_all_or_none.cpp ================================================ // Copyright (c) 2012 - 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #define BOOST_TEST_NO_MAIN LiquibookTest #include #include "ut_utils.h" namespace liquibook { using simple::SimpleOrder; typedef FillCheck SimpleFillCheck; namespace { const OrderConditions AON(oc_all_or_none); const OrderConditions noConditions(0); const Quantity qty1 = 100; const Quantity qty2 = qty1 + qty1; const Quantity qty3 = qty2 + qty1; const Quantity qty4 = qty2 + qty2; const Quantity qty6 = qty2 + qty4; const Quantity qty7 = 700; // derive this? const Quantity qtyNone = 0; const Price prc0 = 1250; const Price prc1 = 1251; const Price prc2 = 1252; const Price prc3 = 1253; const Price prcNone = 0; const Price MARKET_ORDER_PRICE = MARKET_ORDER_PRICE; const bool buySide = true; const bool sellSide = false; const bool expectMatch = true; const bool expectNoMatch = false; const bool expectComplete = true; const bool expectNoComplete = false; } BOOST_AUTO_TEST_CASE(TestRegBidMatchAon) { SimpleOrderBook order_book; SimpleOrder ask2(sellSide, prc2, qty1); SimpleOrder ask1(sellSide, prc1, qty1); // AON SimpleOrder ask0(sellSide, prc1, qty2); // AON, but skipped SimpleOrder bid1(buySide, prc1, qty1); SimpleOrder bid0(buySide, prc0, qty1); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &ask0, expectNoMatch, expectNoComplete, AON)); BOOST_CHECK(add_and_verify(order_book, &ask1, expectNoMatch, expectNoComplete, AON)); BOOST_CHECK(add_and_verify(order_book, &ask2, expectNoMatch)); // Verify sizes BOOST_CHECK_EQUAL(1UL, order_book.bids().size()); BOOST_CHECK_EQUAL(3UL, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(prc0, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc1, 2, qty1 + qty2)); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); // Match - complete { SimpleFillCheck fc1(&bid1, qty1, prc1 * qty1); SimpleFillCheck fc2(&ask1, qty1, prc1 * qty1); BOOST_CHECK(add_and_verify(order_book, &bid1, expectMatch, expectComplete)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(prc0, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc1, 1, qty2)); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestRegBidMatchMulti) { SimpleOrderBook order_book; SimpleOrder ask2(sellSide, prc1, qty7); SimpleOrder ask1(sellSide, prc1, qty1); // AON SimpleOrder ask0(sellSide, prc1, qty1); // AON SimpleOrder bid1(buySide, prc1, qty4); SimpleOrder bid0(buySide, prc0, qty1); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &ask0, expectNoMatch, expectNoComplete, AON)); BOOST_CHECK(add_and_verify(order_book, &ask1, expectNoMatch, expectNoComplete, AON)); BOOST_CHECK(add_and_verify(order_book, &ask2, expectNoMatch, expectNoComplete)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(prc0, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc1, 3, qty7 + qty1 + qty1)); // Match - complete { SimpleFillCheck fc0(&bid1, qty4, prc1 * qty4); SimpleFillCheck fc1(&ask0, qty1, prc1 * qty1); SimpleFillCheck fc2(&ask1, qty1, prc1 * qty1); SimpleFillCheck fc3(&ask2, qty2, prc1 * qty2); BOOST_CHECK(add_and_verify(order_book, &bid1, expectMatch, expectComplete)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(prc0, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc1, 1, qty4 + qty1)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestAonBidNoMatch) { SimpleOrderBook order_book; SimpleOrder ask1(sellSide, prc2, qty1); // no match, price SimpleOrder ask0(sellSide, prc1, qty1); SimpleOrder bid1(buySide, prc1, qty3); // no match, AON SimpleOrder bid0(buySide, prc0, qty1); // no match, price // No match BOOST_CHECK(add_and_verify(order_book, &bid0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &ask0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &ask1, expectNoMatch)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(prc0, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc1, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); // Match - complete { SimpleFillCheck fc1(&bid1, qtyNone, prcNone); SimpleFillCheck fc2(&ask0, qtyNone, prcNone); BOOST_CHECK(add_and_verify(order_book, &bid1, expectNoMatch, expectNoComplete, AON)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(prc1, 1, qty3)); BOOST_CHECK(dc.verify_bid(prc0, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc1, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestAonBidMatchReg) { SimpleOrderBook order_book; SimpleOrder ask1(sellSide, prc2, qty1); SimpleOrder ask0(sellSide, prc1, qty4); SimpleOrder bid1(buySide, prc1, qty3); // AON SimpleOrder bid0(buySide, prc0, qty1); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &ask0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &ask1, expectNoMatch)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(prc0, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc1, 1, qty4)); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); // Match - complete { SimpleFillCheck fc1(&bid1, qty3, prc1 * qty3); SimpleFillCheck fc2(&ask0, qty3, prc1 * qty3); BOOST_CHECK(add_and_verify(order_book, &bid1, expectMatch, expectComplete, AON)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(prc0, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc1, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestAonBidMatchMulti) { SimpleOrderBook order_book; SimpleOrder ask3(sellSide, prc2, qty1); SimpleOrder ask2(sellSide, prc2, qty1); SimpleOrder ask1(sellSide, prc1, qty4); // AON no match SimpleOrder ask0(sellSide, prc1, qty4); SimpleOrder bid1(buySide, MARKET_ORDER_PRICE, qty6); // AON SimpleOrder bid0(buySide, prc0, qty1); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &ask0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &ask1, expectNoMatch, expectNoComplete, AON)); BOOST_CHECK(add_and_verify(order_book, &ask2, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &ask3, expectNoMatch)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(4, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(prc0, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc1, 2, qty4 + qty4)); BOOST_CHECK(dc.verify_ask(prc2, 2, qty1 + qty1)); // Match - complete { //ASSERT_NO_THROW( SimpleFillCheck fc1(&bid1, qty6, prc1 * qty2 + prc1 * qty4); SimpleFillCheck fc2(&ask0, qty2, prc1 * qty2); SimpleFillCheck fc3(&ask1, qty4, prc1 * qty4); SimpleFillCheck fc4(&ask2, 0, prc2 * 0); SimpleFillCheck fc5(&ask3, 0, prc2 * 0); BOOST_CHECK(add_and_verify(order_book, &bid1, expectMatch, expectComplete, AON)); //); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(prc0, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc1, 1, qty2)); BOOST_CHECK(dc.verify_ask(prc2, 2, qty1 + qty1)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestAonBidNoMatchMulti) { SimpleOrderBook order_book; SimpleOrder ask2(sellSide, prc2, qty4); // AON no match SimpleOrder ask1(sellSide, prc2, qty1); SimpleOrder ask0(sellSide, prc1, qty4); SimpleOrder bid1(buySide, MARKET_ORDER_PRICE, qty6); // AON SimpleOrder bid0(buySide, prc0, qty1); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &ask0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &ask1, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &ask2, expectNoMatch, expectNoComplete, AON)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(prc0, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc1, 1, qty4)); BOOST_CHECK(dc.verify_ask(prc2, 2, qty4 + qty1)); // Match - complete { //ASSERT_NO_THROW( SimpleFillCheck fc0(&bid0, qtyNone, prcNone); SimpleFillCheck fc1(&bid1, qty6, qty2 * prc1 + qty4 * prc2); // filled 600 @ 751000 SimpleFillCheck fc2(&ask0, qty2, qty2 * prc1); // filled 200 @ 250200 SimpleFillCheck fc3(&ask1, qtyNone, prcNone); // 0 SimpleFillCheck fc4(&ask2, qty4, qty4 * prc2); // filled 400 @ 500800 BOOST_CHECK(add_and_verify(order_book, &bid1, expectMatch, expectComplete, AON)); //); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(prc0, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc1, 1, qty2)); } BOOST_AUTO_TEST_CASE(TestAonBidMatchAon) { SimpleOrderBook order_book; SimpleOrder ask1(sellSide, prc2, qty1); SimpleOrder ask0(sellSide, prc1, qty3); // AON SimpleOrder bid1(buySide, prc1, qty3); // AON SimpleOrder bid0(buySide, prc0, qty1); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &ask0, expectNoMatch, expectNoComplete, AON)); BOOST_CHECK(add_and_verify(order_book, &ask1, expectNoMatch)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(prc0, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc1, 1, qty3)); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); // Match - complete { SimpleFillCheck fc1(&bid1, qty3, prc1 * qty3); SimpleFillCheck fc2(&ask0, qty3, prc1 * qty3); BOOST_CHECK(add_and_verify(order_book, &bid1, expectMatch, expectComplete, AON)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(prc0, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestRegAskMatchAon) { SimpleOrderBook order_book; SimpleOrder ask0(sellSide, prc2, qty1); SimpleOrder ask1(sellSide, prc1, qty1); SimpleOrder bid1(buySide, prc1, qty2); // AON, but skipped SimpleOrder bid2(buySide, prc1, qty1); // AON SimpleOrder bid0(buySide, prc0, qty1); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &bid1, expectNoMatch, expectNoComplete, AON)); BOOST_CHECK(add_and_verify(order_book, &bid2, expectNoMatch, expectNoComplete, AON)); BOOST_CHECK(add_and_verify(order_book, &ask0, expectNoMatch)); // Verify sizes BOOST_CHECK_EQUAL(3, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(prc1, 2, qty3)); BOOST_CHECK(dc.verify_bid(prc0, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); // Match - complete { SimpleFillCheck fc1(&bid2, qty1, prc1 * qty1); SimpleFillCheck fc2(&ask1, qty1, prc1 * qty1); BOOST_CHECK(add_and_verify(order_book, &ask1, expectMatch, expectComplete)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(prc1, 1, qty2)); BOOST_CHECK(dc.verify_bid(prc0, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestRegAskMatchMulti) { SimpleOrderBook order_book; SimpleOrder ask0(sellSide, prc2, qty1); SimpleOrder ask1(sellSide, prc0, qty4); SimpleOrder bid1(buySide, prc1, qty1); // AON SimpleOrder bid2(buySide, prc1, qty1); // AON SimpleOrder bid0(buySide, prc0, qty7); // Calculate some expected results // ask1(400) matches bid 1(100), bid2(100), and part(200) of bid0 // leaving 500 shares of bid 0) Quantity bid0FillQty = qty4 - qty1 - qty1; Quantity bid0RemainQty = qty7 - bid0FillQty; uint32_t bid0FillAmount = bid0FillQty * prc0; uint32_t bid1FillAmount = prc1 * qty1; uint32_t bid2FillAmount = prc1 * qty1; uint32_t ask1FillAmount = bid1FillAmount + bid2FillAmount + bid0FillAmount; // No match BOOST_CHECK(add_and_verify(order_book, &ask0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &bid0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &bid1, expectNoMatch, expectNoComplete, AON)); BOOST_CHECK(add_and_verify(order_book, &bid2, expectNoMatch, expectNoComplete, AON)); // Verify sizes BOOST_CHECK_EQUAL(3, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(prc1, 2, qty2)); BOOST_CHECK(dc.verify_bid(prc0, 1, qty7)); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); // Match - complete { SimpleFillCheck fc0(&bid1, qty1, prc1 * qty1); SimpleFillCheck fc1(&bid2, qty1, prc1 * qty1); SimpleFillCheck fc2(&bid0, qty2, prc0 * qty2); SimpleFillCheck fc3(&ask1, qty4, ask1FillAmount); BOOST_CHECK(add_and_verify(order_book, &ask1, expectMatch, expectComplete)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(prc0, 1, bid0RemainQty)); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestAonAskNoMatch) { SimpleOrderBook order_book; SimpleOrder ask0(sellSide, prc2, qty1); SimpleOrder ask1(sellSide, prc1, qty4); // AON SimpleOrder bid1(buySide, prc1, qty1); SimpleOrder bid2(buySide, prc1, qty1); SimpleOrder bid0(buySide, prc0, qty7); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &bid0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &bid1, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &bid2, expectNoMatch)); // Verify sizes BOOST_CHECK_EQUAL(3, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(prc1, 2, qty2)); BOOST_CHECK(dc.verify_bid(prc0, 1, qty7)); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); // Match - complete { SimpleFillCheck fc0(&bid1, qtyNone, prcNone); SimpleFillCheck fc1(&bid2, qtyNone, prcNone); SimpleFillCheck fc2(&bid0, qtyNone, prcNone); SimpleFillCheck fc3(&ask1, qtyNone, prcNone); BOOST_CHECK(add_and_verify(order_book, &ask1, expectNoMatch, expectNoComplete, AON)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(prc1, 2, qty2)); BOOST_CHECK(dc.verify_bid(prc0, 1, qty7)); BOOST_CHECK(dc.verify_ask(prc1, 1, qty4)); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); // Verify sizes BOOST_CHECK_EQUAL(3, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestAonAskMatchReg) { SimpleOrderBook order_book; SimpleOrder ask0(sellSide, prc2, qty1); SimpleOrder ask1(sellSide, prc1, qty1); // AON SimpleOrder bid1(buySide, prc1, qty1); SimpleOrder bid0(buySide, prc0, qty7); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &bid0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &bid1, expectNoMatch)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(prc1, 1, qty1)); BOOST_CHECK(dc.verify_bid(prc0, 1, qty7)); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); // Match - complete { SimpleFillCheck fc0(&bid1, qty1, prc1 * qty1); SimpleFillCheck fc3(&ask1, qty1, prc1 * qty1); BOOST_CHECK(add_and_verify(order_book, &ask1, expectMatch, expectComplete, AON)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(prc0, 1, qty7)); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestAonAskMatchMulti) { SimpleOrderBook order_book; SimpleOrder ask0(sellSide, prc2, qty1); // no match due to price SimpleOrder ask1(sellSide, prc0, qty6); // AON SimpleOrder bid1(buySide, prc1, qty1); // AON SimpleOrder bid2(buySide, prc1, qty1); SimpleOrder bid3(buySide, prc1, qty1); SimpleOrder bid0(buySide, prc0, qty7); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &bid0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &bid1, expectNoMatch, expectNoComplete, AON)); BOOST_CHECK(add_and_verify(order_book, &bid2, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &bid3, expectNoMatch)); // Verify sizes BOOST_CHECK_EQUAL(4, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(prc1, 3, qty3)); BOOST_CHECK(dc.verify_bid(prc0, 1, qty7)); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); // Match - complete { // ASSERT_NO_THROW( uint32_t b1Cost = prc1 * qty1; SimpleFillCheck fc0(&bid1, qty1, b1Cost); uint32_t b2Cost = prc1 * qty1; SimpleFillCheck fc1(&bid2, qty1, b2Cost); uint32_t b3Cost = prc1 * qty1; SimpleFillCheck fc2(&bid3, qty1, b3Cost); Quantity b0Fill = qty6 - qty1 - qty1 - qty1; uint32_t b0Cost = b0Fill * prc0; SimpleFillCheck fc3(&bid0, b0Fill, b0Cost); uint32_t a1Cost = b0Cost + b1Cost +b2Cost + b3Cost; SimpleFillCheck fc4(&ask1, qty6, a1Cost); BOOST_CHECK(add_and_verify(order_book, &ask1, expectMatch, expectComplete, AON)); // ); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(prc0, 1, qty4)); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); } /////////////////// BOOST_AUTO_TEST_CASE(TestOneAonBidOneAonAsk) { SimpleOrderBook order_book; SimpleOrder bid1(buySide,prc1,qty1); // AON SimpleOrder ask1(sellSide,prc1,qty1); // AON // Prime the order book: No Matches BOOST_CHECK(add_and_verify(order_book,&bid1,expectNoMatch,expectNoComplete,AON)); // Verify sizes BOOST_CHECK_EQUAL(1u,order_book.bids().size()); BOOST_CHECK_EQUAL(0u,order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(prc1 , 1, qty1)); // Add matching order { SimpleFillCheck fc1(&bid1,qty1, qty1 * prc1); SimpleFillCheck fc3(&ask1,qty1, qty1 * prc1); BOOST_CHECK(add_and_verify(order_book, &ask1, expectMatch, expectComplete, AON)); } // Verify sizes BOOST_CHECK_EQUAL(0,order_book.bids().size()); BOOST_CHECK_EQUAL(0,order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestTwoAonBidOneAonAsk) { SimpleOrderBook order_book; SimpleOrder bid1(buySide,prc1,qty1); // AON SimpleOrder bid2(buySide,prc1,qty2); // AON SimpleOrder ask1(sellSide,prc1,qty3); // AON // Prime the order book: No Matches BOOST_CHECK(add_and_verify(order_book, &bid1, expectNoMatch, expectNoComplete,AON));//AON)); //noConditions BOOST_CHECK(add_and_verify(order_book, &bid2,expectNoMatch,expectNoComplete,AON)); // Verify sizes BOOST_CHECK_EQUAL(2u,order_book.bids().size()); BOOST_CHECK_EQUAL(0u,order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(prc1, 2, qty1 + qty2)); // Add matching order { SimpleFillCheck fc1(&bid1, qty1, qty1 * prc1); SimpleFillCheck fc2(&bid2, qty2, qty2 * prc1); SimpleFillCheck fc3(&ask1, qty3, qty3 * prc1); BOOST_CHECK(add_and_verify(order_book, &ask1, expectMatch, expectComplete, AON)); } // Verify sizes BOOST_CHECK_EQUAL(0,order_book.bids().size()); BOOST_CHECK_EQUAL(0,order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestOneAonBidTwoAsk) { SimpleOrderBook order_book; SimpleOrder bid1(buySide,prc1,qty3); // AON SimpleOrder ask1(sellSide,prc1,qty1); // No Conditions SimpleOrder ask2(sellSide,prc1,qty2); // No Conditions // Prime the order book: No Matches BOOST_CHECK(add_and_verify(order_book,&bid1,expectNoMatch,expectNoComplete,AON));//AON)); //noConditions // Add an order that does NOT meet the AON condition BOOST_CHECK(add_and_verify(order_book,&ask1,expectNoMatch,expectNoComplete,noConditions)); // Verify sizes BOOST_CHECK_EQUAL(1u,order_book.bids().size()); BOOST_CHECK_EQUAL(1u,order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(prc1,1,qty3)); BOOST_CHECK(dc.verify_ask(prc1,1,qty1)); // Add matching order { SimpleFillCheck fc1(&bid1,qty3,qty3 * prc1); SimpleFillCheck fc2(&ask1,qty1,qty1 * prc1); SimpleFillCheck fc3(&ask2,qty2,qty2 * prc1); BOOST_CHECK(add_and_verify(order_book,&ask2,expectMatch,expectComplete,noConditions)); } // Verify sizes BOOST_CHECK_EQUAL(0,order_book.bids().size()); BOOST_CHECK_EQUAL(0,order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestOneBidTwoAonAsk) { SimpleOrderBook order_book; SimpleOrder bid1(buySide,prc1,qty3); // noConditions SimpleOrder ask1(sellSide,prc1,qty1); // AON SimpleOrder ask2(sellSide,prc1,qty2); // AON // Prime the order book: No Matches BOOST_CHECK(add_and_verify(order_book,&bid1,expectNoMatch,expectNoComplete,AON)); // Verify sizes BOOST_CHECK_EQUAL(1u,order_book.bids().size()); BOOST_CHECK_EQUAL(0u,order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(prc1,1,qty3)); // Add matching order { SimpleFillCheck fc1(&bid1,qty3,qty3 * prc1); SimpleFillCheck fc2(&ask1,qty1,qty1 * prc1); SimpleFillCheck fc3(&ask2,qty2,qty2 * prc1); BOOST_CHECK(add_and_verify(order_book,&ask1,expectNoMatch,expectNoComplete,noConditions)); BOOST_CHECK(add_and_verify(order_book,&ask2,expectMatch,expectComplete,noConditions)); } // Verify sizes BOOST_CHECK_EQUAL(0,order_book.bids().size()); BOOST_CHECK_EQUAL(0,order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestTwoAonBidTwoAonAsk) { #if 1 int todo_FixTestAonsTwoBidTwoAsk; std::cout << "***** WARNING TEST " << "TestAonsTwoBidTwoAsk" << " is disabled" << std::endl; #else // The current match algorithm tries to match one order from one side of the market to "N" orders // from the other side. This test won't pass because it requires two orders from each side. // I'm leaving the test here as a challenge to future developers who want to improve the matching // algorithm (good luck) SimpleOrderBook order_book; SimpleOrder ask1(sellSide,prc1,qty3); // AON SimpleOrder ask2(sellSide,prc1,qty2); // AON SimpleOrder bid1(buySide,prc1,qty1); // AON SimpleOrder bid2(buySide,prc1,qty4); // AON // Prime the order book: No Matches BOOST_CHECK(add_and_verify(order_book,&bid1,expectNoMatch,expectNoComplete,AON)); BOOST_CHECK(add_and_verify(order_book,&bid2,expectNoMatch,expectNoComplete,AON)); BOOST_CHECK(add_and_verify(order_book,&ask1,expectNoMatch,expectNoComplete,AON)); // Verify sizes BOOST_CHECK_EQUAL(2u,order_book.bids().size()); BOOST_CHECK_EQUAL(1u,order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(prc1,2,qty1 + qty4)); BOOST_CHECK(dc.verify_ask(prc1,1,qty3)); // Add matching order { SimpleFillCheck fc1(&bid1,qty1,qty3 * prc1); SimpleFillCheck fc2(&bid2,qty4,qty3 * prc1); SimpleFillCheck fc3(&ask1,qty3,qty1 * prc1); SimpleFillCheck fc4(&ask2,qty2,qty2 * prc1); BOOST_CHECK(add_and_verify(order_book,&ask2,expectMatch,expectComplete,AON)); } // Verify sizes BOOST_CHECK_EQUAL(0,order_book.bids().size()); BOOST_CHECK_EQUAL(0,order_book.asks().size()); #endif } BOOST_AUTO_TEST_CASE(TestAonAskNoMatchMulti) { SimpleOrderBook order_book; SimpleOrder ask0(sellSide, prc2, qty1); // no match (price) SimpleOrder ask1(sellSide, prc0, qty6); // AON SimpleOrder bid0(buySide, prc0, qty4); // AON no match SimpleOrder bid1(buySide, prc1, qty1); // AON SimpleOrder bid2(buySide, prc1, qty4); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &bid0, expectNoMatch, expectNoComplete,AON)); BOOST_CHECK(add_and_verify(order_book, &bid1, expectNoMatch, expectNoComplete,AON));//AON)); //noConditions BOOST_CHECK(add_and_verify(order_book, &bid2, expectNoMatch)); // Verify sizes BOOST_CHECK_EQUAL(3, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(prc1, 2, qty1 + qty4)); BOOST_CHECK(dc.verify_bid(prc0, 1, qty4)); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); // This test was bogus -- testing a bug in the matching algorithm // I fixed the bug and the test started to fail. // So fixed the test to expect: // Ask1 (600 AON) should match bid0 (400 AON) + bid1(100) + bid 2(100 of 400) // // Now we need a new test of an AON that should NOT match! // No match { // ASSERT_NO_THROW( SimpleFillCheck fc0(&bid0, qty4, prc0 * qty4); SimpleFillCheck fc1(&bid1, qty1, qty1 * prc1); SimpleFillCheck fc2(&bid2, qty1, prc1 * qty1); SimpleFillCheck fc3(&ask1, qty6, prc0 * qty4 + qty1 * prc1 + prc1 * qty1); BOOST_CHECK(add_and_verify(order_book, &ask1, expectMatch, expectComplete, AON)); //); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(prc1, 1, qty4 - qty1)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestAonAskMatchAon) { SimpleOrderBook order_book; SimpleOrder ask0(sellSide, prc2, qty1); SimpleOrder ask1(sellSide, prc1, qty2); // AON SimpleOrder bid1(buySide, prc1, qty2); // AON SimpleOrder bid0(buySide, prc0, qty4); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &bid0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &bid1, expectNoMatch, expectNoComplete, AON)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); BOOST_CHECK(dc.verify_bid(prc1, 1, qty2)); BOOST_CHECK(dc.verify_bid(prc0, 1, qty4)); // Match complete { SimpleFillCheck fc1(&bid1, qty2, prc1 * qty2); SimpleFillCheck fc3(&ask1, qty2, prc1 * qty2); BOOST_CHECK(add_and_verify(order_book, &ask1, expectMatch, expectComplete, AON)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); BOOST_CHECK(dc.verify_bid(prc0, 1, qty4)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestReplaceAonBidSmallerMatch) { SimpleOrderBook order_book; SimpleOrder ask2(sellSide, prc3, qty1); SimpleOrder ask1(sellSide, prc2, qty1); SimpleOrder ask0(sellSide, prc1, qty1); SimpleOrder bid1(buySide, prc1, qty2); // AON SimpleOrder bid0(buySide, prc0, qty1); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &bid1, expectNoMatch, expectNoComplete, AON)); BOOST_CHECK(add_and_verify(order_book, &ask0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &ask1, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &ask2, expectNoMatch)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(prc1, 1, qty2)); BOOST_CHECK(dc.verify_bid(prc0, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc1, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc3, 1, qty1)); // Match - complete { SimpleFillCheck fc2(&ask0, qty1, prc1 * qty1); BOOST_CHECK(replace_and_verify( order_book, &bid1, -(int32_t)qty1, PRICE_UNCHANGED, simple::os_complete, qty1)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(prc0, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc3, 1, qty1)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestReplaceAonBidPriceMatch) { SimpleOrderBook order_book; SimpleOrder ask2(sellSide, prc3, qty1); SimpleOrder ask1(sellSide, prc2, qty1); SimpleOrder ask0(sellSide, prc1, qty1); SimpleOrder bid1(buySide, prc1, qty2); // AON SimpleOrder bid0(buySide, prc0, qty1); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &bid1, expectNoMatch, expectNoComplete, AON)); BOOST_CHECK(add_and_verify(order_book, &ask0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &ask1, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &ask2, expectNoMatch)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(prc1, 1, qty2)); BOOST_CHECK(dc.verify_bid(prc0, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc1, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc3, 1, qty1)); // Match - complete { SimpleFillCheck fc1(&ask0, qty1, prc1 * qty1); SimpleFillCheck fc2(&ask1, qty1, prc2 * qty1); BOOST_CHECK(replace_and_verify( order_book, &bid1, qtyNone, prc2, simple::os_complete, qty2)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(prc0, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc3, 1, qty1)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestReplaceBidLargerMatchAon) { SimpleOrderBook order_book; SimpleOrder ask2(sellSide, prc3, qty1); SimpleOrder ask1(sellSide, prc2, qty1); SimpleOrder ask0(sellSide, prc1, qty2); // AON SimpleOrder bid1(buySide, prc1, qty1); SimpleOrder bid0(buySide, prc0, qty1); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &bid1, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &ask0, expectNoMatch, expectNoComplete, AON)); BOOST_CHECK(add_and_verify(order_book, &ask1, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &ask2, expectNoMatch)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(prc1, 1, qty1)); BOOST_CHECK(dc.verify_bid(prc0, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc1, 1, qty2)); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc3, 1, qty1)); // Match - complete { SimpleFillCheck fc2(&ask0, qty2, qty2 * prc1); BOOST_CHECK(replace_and_verify( order_book, &bid1, qty1, PRICE_UNCHANGED, simple::os_complete, qty2)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(prc0, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc2, 1, qty1)); BOOST_CHECK(dc.verify_ask(prc3, 1, qty1)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); } } // Namespace ================================================ FILE: test/unit/ut_bbo_order_book.cpp ================================================ // Copyright (c) 2012 - 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #define BOOST_TEST_NO_MAIN LiquibookTest #include #include #include #include #include #include "changed_checker.h" #include "depth_check.h" #include "ut_utils.h" using namespace liquibook::book; namespace liquibook { using book::DepthLevel; using book::OrderBook; using book::OrderTracker; using simple::SimpleOrder; typedef OrderTracker SimpleTracker; typedef test::ChangedChecker<5> ChangedChecker; typedef SimpleOrderBook::DepthTracker SimpleDepth; typedef FillCheck SimpleFillCheck; BOOST_AUTO_TEST_CASE(TestBboBidsMultimapSortCorrect) { SimpleOrderBook::Bids bids; SimpleOrder order0(true, 1250, 100); SimpleOrder order1(true, 1255, 100); SimpleOrder order2(true, 1240, 100); SimpleOrder order3(true, 0, 100); SimpleOrder order4(true, 1245, 100); // Insert out of price order bids.insert(std::make_pair(ComparablePrice(true, order0.price()), SimpleTracker(&order0))); bids.insert(std::make_pair(ComparablePrice(true, order1.price()), SimpleTracker(&order1))); bids.insert(std::make_pair(ComparablePrice(true, order2.price()), SimpleTracker(&order2))); bids.insert(std::make_pair(ComparablePrice(true, 0), SimpleTracker(&order3))); bids.insert(std::make_pair(ComparablePrice(true, order4.price()), SimpleTracker(&order4))); // Should access in price order SimpleOrder* expected_order[] = { &order3, &order1, &order0, &order4, &order2 }; SimpleOrderBook::Bids::iterator bid; int index = 0; for (bid = bids.begin(); bid != bids.end(); ++bid, ++index) { if (expected_order[index]->price() == MARKET_ORDER_PRICE) { BOOST_CHECK_EQUAL(MARKET_ORDER_PRICE, bid->first); } else { BOOST_CHECK_EQUAL(expected_order[index]->price(), bid->first); } BOOST_CHECK_EQUAL(expected_order[index], bid->second.ptr()); } // Should be able to search and find BOOST_CHECK((bids.upper_bound(book::ComparablePrice(true, 1245)))->second.ptr()->price() == 1240); BOOST_CHECK((bids.lower_bound(book::ComparablePrice(true, 1245)))->second.ptr()->price() == 1245); } BOOST_AUTO_TEST_CASE(TestBboAsksMultimapSortCorrect) { SimpleOrderBook::Asks asks; SimpleOrder order0(false, 3250, 100); SimpleOrder order1(false, 3235, 800); SimpleOrder order2(false, 3230, 200); SimpleOrder order3(false, 0, 200); SimpleOrder order4(false, 3245, 100); SimpleOrder order5(false, 3265, 200); // Insert out of price order asks.insert(std::make_pair(book::ComparablePrice(false, order0.price()), SimpleTracker(&order0))); asks.insert(std::make_pair(book::ComparablePrice(false, order1.price()), SimpleTracker(&order1))); asks.insert(std::make_pair(book::ComparablePrice(false, order2.price()), SimpleTracker(&order2))); asks.insert(std::make_pair(book::ComparablePrice(false, MARKET_ORDER_PRICE), SimpleTracker(&order3))); asks.insert(std::make_pair(book::ComparablePrice(false, order4.price()), SimpleTracker(&order4))); asks.insert(std::make_pair(book::ComparablePrice(false, order5.price()), SimpleTracker(&order5))); // Should access in price order SimpleOrder* expected_order[] = { &order3, &order2, &order1, &order4, &order0, &order5 }; SimpleOrderBook::Asks::iterator ask; int index = 0; for (ask = asks.begin(); ask != asks.end(); ++ask, ++index) { if (expected_order[index]->price() == MARKET_ORDER_PRICE) { BOOST_CHECK_EQUAL(MARKET_ORDER_ASK_SORT_PRICE, ask->first); } else { BOOST_CHECK_EQUAL(expected_order[index]->price(), ask->first); } BOOST_CHECK_EQUAL(expected_order[index], ask->second.ptr()); } BOOST_CHECK((asks.upper_bound(book::ComparablePrice(false, 3235)))->second.ptr()->price() == 3245); BOOST_CHECK((asks.lower_bound(book::ComparablePrice(false, 3235)))->second.ptr()->price() == 3235); } BOOST_AUTO_TEST_CASE(TestBboAddCompleteBid) { SimpleOrderBook order_book; SimpleOrder ask1(false, 1252, 100); SimpleOrder ask0(false, 1251, 100); SimpleOrder bid1(true, 1251, 100); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); // Match - complete { SimpleFillCheck fc1(&bid1, 100, 125100); SimpleFillCheck fc2(&ask0, 100, 125100); BOOST_CHECK(add_and_verify(order_book, &bid1, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestBboAddCompleteAsk) { SimpleOrderBook order_book; SimpleOrder ask0(false, 1251, 100); SimpleOrder ask1(false, 1250, 100); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); // Match - complete { SimpleFillCheck fc1(&ask1, 100, 125000); SimpleFillCheck fc2(&bid0, 100, 125000); BOOST_CHECK(add_and_verify(order_book, &ask1, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); // Verify sizes BOOST_CHECK_EQUAL(0, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestBboAddMultiMatchBid) { SimpleOrderBook order_book; SimpleOrder ask1(false, 1252, 100); SimpleOrder ask0(false, 1251, 300); SimpleOrder ask2(false, 1251, 200); SimpleOrder bid1(true, 1251, 500); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 2, 500)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Match - complete { SimpleFillCheck fc1(&bid1, 500, 1251 * 500); SimpleFillCheck fc2(&ask2, 200, 1251 * 200); SimpleFillCheck fc3(&ask0, 300, 1251 * 300); BOOST_CHECK(add_and_verify(order_book, &bid1, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify remaining BOOST_CHECK_EQUAL(&ask1, order_book.asks().begin()->second.ptr()); } BOOST_AUTO_TEST_CASE(TestBboAddMultiMatchAsk) { SimpleOrderBook order_book; SimpleOrder ask1(false, 9252, 100); SimpleOrder ask0(false, 9251, 300); SimpleOrder ask2(false, 9251, 200); SimpleOrder ask3(false, 9250, 600); SimpleOrder bid0(true, 9250, 100); SimpleOrder bid1(true, 9250, 500); SimpleOrder bid2(true, 9248, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); // Verify sizes BOOST_CHECK_EQUAL(3, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(9250, 2, 600)); BOOST_CHECK(dc.verify_ask(9251, 2, 500)); // Match - complete { SimpleFillCheck fc1(&ask3, 600, 9250 * 600); SimpleFillCheck fc2(&bid0, 100, 9250 * 100); SimpleFillCheck fc3(&bid1, 500, 9250 * 500); BOOST_CHECK(add_and_verify(order_book, &ask3, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(9248, 1, 100)); BOOST_CHECK(dc.verify_ask(9251, 2, 500)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify remaining BOOST_CHECK_EQUAL(&bid2, order_book.bids().begin()->second.ptr()); } BOOST_AUTO_TEST_CASE(TestBboAddPartialMatchBid) { SimpleOrderBook order_book; SimpleOrder ask0(false, 7253, 300); SimpleOrder ask1(false, 7252, 100); SimpleOrder ask2(false, 7251, 200); SimpleOrder bid1(true, 7251, 350); SimpleOrder bid0(true, 7250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(7250, 1, 100)); BOOST_CHECK(dc.verify_ask(7251, 1, 200)); // Match - partial { SimpleFillCheck fc1(&bid1, 200, 7251 * 200); SimpleFillCheck fc2(&ask2, 200, 7251 * 200); BOOST_CHECK(add_and_verify(order_book, &bid1, true, false)); } // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(7251, 1, 150)); BOOST_CHECK(dc.verify_ask(7252, 1, 100)); // Verify remaining BOOST_CHECK_EQUAL(&ask1, order_book.asks().begin()->second.ptr()); BOOST_CHECK_EQUAL(&bid1, order_book.bids().begin()->second.ptr()); } BOOST_AUTO_TEST_CASE(TestBboAddPartialMatchAsk) { SimpleOrderBook order_book; SimpleOrder ask0(false, 1253, 300); SimpleOrder ask1(false, 1251, 400); SimpleOrder bid1(true, 1251, 350); SimpleOrder bid0(true, 1250, 100); SimpleOrder bid2(true, 1250, 200); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); // Verify sizes BOOST_CHECK_EQUAL(3, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1251, 1, 350)); BOOST_CHECK(dc.verify_ask(1253, 1, 300)); // Match - partial { SimpleFillCheck fc1(&ask1, 350, 1251 * 350); SimpleFillCheck fc2(&bid1, 350, 1251 * 350); BOOST_CHECK(add_and_verify(order_book, &ask1, true, false)); } // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 2, 300)); BOOST_CHECK(dc.verify_ask(1251, 1, 50)); // Verify remaining BOOST_CHECK_EQUAL(&bid0, order_book.bids().begin()->second.ptr()); BOOST_CHECK_EQUAL(&ask1, order_book.asks().begin()->second.ptr()); } BOOST_AUTO_TEST_CASE(TestBboAddMultiPartialMatchBid) { SimpleOrderBook order_book; SimpleOrder ask1(false, 1252, 100); SimpleOrder ask2(false, 1251, 200); SimpleOrder ask0(false, 1251, 300); SimpleOrder bid1(true, 1251, 750); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 2, 500)); // Match - partial { SimpleFillCheck fc1(&bid1, 500, 1251 * 500); SimpleFillCheck fc2(&ask0, 300, 1251 * 300); SimpleFillCheck fc3(&ask2, 200, 1251 * 200); BOOST_CHECK(add_and_verify(order_book, &bid1, true, false)); } // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 250)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Verify remaining BOOST_CHECK_EQUAL(&ask1, order_book.asks().begin()->second.ptr()); BOOST_CHECK_EQUAL(&bid1, order_book.bids().begin()->second.ptr()); } BOOST_AUTO_TEST_CASE(TestBboAddMultiPartialMatchAsk) { SimpleOrderBook order_book; SimpleOrder ask0(false, 1253, 300); SimpleOrder ask1(false, 1251, 700); SimpleOrder bid1(true, 1251, 370); SimpleOrder bid2(true, 1251, 200); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); // Verify sizes BOOST_CHECK_EQUAL(3, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1251, 2, 570)); BOOST_CHECK(dc.verify_ask(1253, 1, 300)); // Match - partial { SimpleFillCheck fc1(&ask1, 570, 1251 * 570); SimpleFillCheck fc2(&bid1, 370, 1251 * 370); SimpleFillCheck fc3(&bid2, 200, 1251 * 200); BOOST_CHECK(add_and_verify(order_book, &ask1, true, false)); } // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 130)); // Verify remaining BOOST_CHECK_EQUAL(&bid0, order_book.bids().begin()->second.ptr()); BOOST_CHECK_EQUAL(100, order_book.bids().begin()->second.open_qty()); BOOST_CHECK_EQUAL(&ask1, order_book.asks().begin()->second.ptr()); BOOST_CHECK_EQUAL(130, order_book.asks().begin()->second.open_qty()); } BOOST_AUTO_TEST_CASE(TestBboRepeatMatchBid) { SimpleOrderBook order_book; SimpleOrder ask3(false, 1251, 400); SimpleOrder ask2(false, 1251, 200); SimpleOrder ask1(false, 1251, 300); SimpleOrder ask0(false, 1251, 100); SimpleOrder bid1(true, 1251, 900); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1251, 1, 900)); // Match - repeated { SimpleFillCheck fc1(&bid1, 100, 125100); SimpleFillCheck fc2(&ask0, 100, 125100); BOOST_CHECK(add_and_verify(order_book, &ask0, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 800)); { SimpleFillCheck fc1(&bid1, 300, 1251 * 300); SimpleFillCheck fc2(&ask1, 300, 1251 * 300); BOOST_CHECK(add_and_verify(order_book, &ask1, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 500)); { SimpleFillCheck fc1(&bid1, 200, 1251 * 200); SimpleFillCheck fc2(&ask2, 200, 1251 * 200); BOOST_CHECK(add_and_verify(order_book, &ask2, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 300)); { SimpleFillCheck fc1(&bid1, 300, 1251 * 300); SimpleFillCheck fc2(&ask3, 300, 1251 * 300); BOOST_CHECK(add_and_verify(order_book, &ask3, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestBboRepeatMatchAsk) { SimpleOrderBook order_book; SimpleOrder ask0(false, 1252, 100); SimpleOrder ask1(false, 1251, 900); SimpleOrder bid0(true, 1251, 100); SimpleOrder bid1(true, 1251, 300); SimpleOrder bid2(true, 1251, 200); SimpleOrder bid3(true, 1251, 400); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_ask(1251, 1, 900)); BOOST_CHECK_EQUAL(&ask1, order_book.asks().begin()->second.ptr()); // Match - repeated { SimpleFillCheck fc1(&ask1, 100, 125100); SimpleFillCheck fc2(&bid0, 100, 125100); BOOST_CHECK(add_and_verify(order_book, &bid0, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_ask(1251, 1, 800)); { SimpleFillCheck fc1(&ask1, 300, 1251 * 300); SimpleFillCheck fc2(&bid1, 300, 1251 * 300); BOOST_CHECK(add_and_verify(order_book, &bid1, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_ask(1251, 1, 500)); { SimpleFillCheck fc1(&ask1, 200, 1251 * 200); SimpleFillCheck fc2(&bid2, 200, 1251 * 200); BOOST_CHECK(add_and_verify(order_book, &bid2, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_ask(1251, 1, 300)); { SimpleFillCheck fc1(&ask1, 300, 1251 * 300); SimpleFillCheck fc2(&bid3, 300, 1251 * 300); BOOST_CHECK(add_and_verify(order_book, &bid3, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestBboAddMarketOrderBid) { SimpleOrderBook order_book; SimpleOrder ask1(false, 1252, 100); SimpleOrder ask0(false, 1251, 100); SimpleOrder bid1(true, 0, 100); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); // Match - complete { SimpleFillCheck fc1(&bid1, 100, 125100); SimpleFillCheck fc2(&ask0, 100, 125100); BOOST_CHECK(add_and_verify(order_book, &bid1, true, true)); } // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); } BOOST_AUTO_TEST_CASE(TestBboAddMarketOrderAsk) { SimpleOrderBook order_book; SimpleOrder ask0(false, 1252, 100); SimpleOrder ask1(false, 0, 100); SimpleOrder bid1(true, 1251, 100); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Match - complete { SimpleFillCheck fc1(&bid1, 100, 125100); SimpleFillCheck fc2(&ask1, 100, 125100); BOOST_CHECK(add_and_verify(order_book, &ask1, true, true)); } // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); } BOOST_AUTO_TEST_CASE(TestBboAddMarketOrderBidMultipleMatch) { SimpleOrderBook order_book; SimpleOrder ask1(false, 12520, 300); SimpleOrder ask0(false, 12510, 200); SimpleOrder bid1(true, 0, 500); SimpleOrder bid0(true, 12500, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(12500, 1, 100)); BOOST_CHECK(dc.verify_ask(12510, 1, 200)); // Match - complete { SimpleFillCheck fc1(&bid1, 500, 12510 * 200 + 12520 * 300); SimpleFillCheck fc2(&ask0, 200, 12510 * 200); SimpleFillCheck fc3(&ask1, 300, 12520 * 300); BOOST_CHECK(add_and_verify(order_book, &bid1, true, true)); } // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(0, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(12500, 1, 100)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); } BOOST_AUTO_TEST_CASE(TestBboAddMarketOrderAskMultipleMatch) { SimpleOrderBook order_book; SimpleOrder ask0(false, 12520, 100); SimpleOrder ask1(false, 0, 600); SimpleOrder bid1(true, 12510, 200); SimpleOrder bid0(true, 12500, 400); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(12510, 1, 200)); BOOST_CHECK(dc.verify_ask(12520, 1, 100)); // Match - complete { SimpleFillCheck fc1(&bid0, 400, 12500 * 400); SimpleFillCheck fc2(&bid1, 200, 12510 * 200); SimpleFillCheck fc3(&ask1, 600, 12500 * 400 + 12510 * 200); BOOST_CHECK(add_and_verify(order_book, &ask1, true, true)); } // Verify sizes BOOST_CHECK_EQUAL(0, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); BOOST_CHECK(dc.verify_ask(12520, 1, 100)); } BOOST_AUTO_TEST_CASE(TestBboMatchMarketOrderBid) { SimpleOrderBook order_book; SimpleOrder ask0(false, 1253, 100); SimpleOrder bid1(true, 0, 100); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(0, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); // Match - complete { SimpleFillCheck fc1(&bid1, 100, 125300); SimpleFillCheck fc2(&ask0, 100, 125300); BOOST_CHECK(add_and_verify(order_book, &ask0, true, true)); } // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(0, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); } BOOST_AUTO_TEST_CASE(TestBboMatchMarketOrderAsk) { SimpleOrderBook order_book; SimpleOrder ask0(false, 1252, 100); SimpleOrder ask1(false, 0, 100); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify sizes BOOST_CHECK_EQUAL(0, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); // Match - complete { SimpleFillCheck fc1(&bid0, 100, 125000); SimpleFillCheck fc2(&ask1, 100, 125000); BOOST_CHECK(add_and_verify(order_book, &bid0, true, true)); } // Verify sizes BOOST_CHECK_EQUAL(0, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); } BOOST_AUTO_TEST_CASE(TestBboMatchMultipleMarketOrderBid) { SimpleOrderBook order_book; SimpleOrder ask0(false, 1253, 400); SimpleOrder bid1(true, 0, 100); SimpleOrder bid2(true, 0, 200); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); // Verify sizes BOOST_CHECK_EQUAL(3, order_book.bids().size()); BOOST_CHECK_EQUAL(0, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); // Match - complete { SimpleFillCheck fc1(&bid1, 100, 1253 * 100); SimpleFillCheck fc2(&bid2, 200, 1253 * 200); SimpleFillCheck fc3(&ask0, 300, 1253 * 300); BOOST_CHECK(add_and_verify(order_book, &ask0, true, false)); } // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_ask(1253, 1, 100)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); } BOOST_AUTO_TEST_CASE(TestBboMatchMultipleMarketOrderAsk) { SimpleOrderBook order_book; SimpleOrder ask0(false, 1252, 100); SimpleOrder ask2(false, 0, 400); SimpleOrder ask1(false, 0, 100); SimpleOrder bid0(true, 1250, 300); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); // Verify sizes BOOST_CHECK_EQUAL(0, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); // Match - partiaL { SimpleFillCheck fc1(&bid0, 300, 1250 * 300); SimpleFillCheck fc2(&ask1, 100, 1250 * 100); SimpleFillCheck fc3(&ask2, 200, 1250 * 200); BOOST_CHECK(add_and_verify(order_book, &bid0, true, true)); } // Verify sizes BOOST_CHECK_EQUAL(0, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); } BOOST_AUTO_TEST_CASE(TestBboCancelBid) { SimpleOrderBook order_book; SimpleOrder ask1(false, 1252, 100); SimpleOrder ask0(false, 1251, 100); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); // Cancel bid BOOST_CHECK(cancel_and_verify(order_book, &bid0, simple::os_cancelled)); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(0, 0, 0)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); // Verify sizes BOOST_CHECK_EQUAL(0, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestBboCancelAskAndMatch) { SimpleOrderBook order_book; SimpleOrder ask1(false, 1252, 100); SimpleOrder ask0(false, 1251, 100); SimpleOrder bid2(true, 1252, 100); SimpleOrder bid0(true, 1250, 100); SimpleOrder bid1(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 2, 200)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); // Cancel bid BOOST_CHECK(cancel_and_verify(order_book, &ask0, simple::os_cancelled)); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 2, 200)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Match - partiaL { SimpleFillCheck fc1(&bid2, 100, 1252 * 100); SimpleFillCheck fc2(&ask1, 100, 1252 * 100); BOOST_CHECK(add_and_verify(order_book, &bid2, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 2, 200)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); // Cancel bid BOOST_CHECK(cancel_and_verify(order_book, &bid0, simple::os_cancelled)); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(0, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestBboCancelBidFail) { SimpleOrderBook order_book; SimpleOrder ask0(false, 1251, 100); SimpleOrder ask1(false, 1250, 100); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); // Match - complete { SimpleFillCheck fc1(&ask1, 100, 125000); SimpleFillCheck fc2(&bid0, 100, 125000); BOOST_CHECK(add_and_verify(order_book, &ask1, true, true)); } // Verify sizes BOOST_CHECK_EQUAL(0, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); // Cancel a filled order BOOST_CHECK(cancel_and_verify(order_book, &bid0, simple::os_complete)); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); } BOOST_AUTO_TEST_CASE(TestBboCancelAskFail) { SimpleOrderBook order_book; SimpleOrder ask1(false, 1252, 100); SimpleOrder ask0(false, 1251, 100); SimpleOrder bid1(true, 1251, 100); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); // Match - complete { SimpleFillCheck fc1(&bid1, 100, 125100); SimpleFillCheck fc2(&ask0, 100, 125100); BOOST_CHECK(add_and_verify(order_book, &bid1, true, true)); } // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); // Cancel a filled order BOOST_CHECK(cancel_and_verify(order_book, &ask0, simple::os_complete)); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); } BOOST_AUTO_TEST_CASE(TestBboCancelBidRestore) { SimpleOrderBook order_book; SimpleOrder ask10(false, 1258, 600); SimpleOrder ask9(false, 1257, 700); SimpleOrder ask8(false, 1256, 100); SimpleOrder ask7(false, 1256, 100); SimpleOrder ask6(false, 1255, 500); SimpleOrder ask5(false, 1255, 200); SimpleOrder ask4(false, 1254, 300); SimpleOrder ask3(false, 1252, 200); SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 400); SimpleOrder ask0(false, 1250, 500); SimpleOrder bid0(true, 1249, 100); SimpleOrder bid1(true, 1249, 200); SimpleOrder bid2(true, 1249, 200); SimpleOrder bid3(true, 1248, 400); SimpleOrder bid4(true, 1246, 600); SimpleOrder bid5(true, 1246, 500); SimpleOrder bid6(true, 1245, 200); SimpleOrder bid7(true, 1245, 100); SimpleOrder bid8(true, 1245, 200); SimpleOrder bid9(true, 1244, 700); SimpleOrder bid10(true, 1244, 300); SimpleOrder bid11(true, 1242, 300); SimpleOrder bid12(true, 1241, 400); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &ask3, false)); BOOST_CHECK(add_and_verify(order_book, &ask4, false)); BOOST_CHECK(add_and_verify(order_book, &ask5, false)); BOOST_CHECK(add_and_verify(order_book, &ask6, false)); BOOST_CHECK(add_and_verify(order_book, &ask7, false)); BOOST_CHECK(add_and_verify(order_book, &ask8, false)); BOOST_CHECK(add_and_verify(order_book, &ask9, false)); BOOST_CHECK(add_and_verify(order_book, &ask10, false)); BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); BOOST_CHECK(add_and_verify(order_book, &bid3, false)); BOOST_CHECK(add_and_verify(order_book, &bid4, false)); BOOST_CHECK(add_and_verify(order_book, &bid5, false)); BOOST_CHECK(add_and_verify(order_book, &bid6, false)); BOOST_CHECK(add_and_verify(order_book, &bid7, false)); BOOST_CHECK(add_and_verify(order_book, &bid8, false)); BOOST_CHECK(add_and_verify(order_book, &bid9, false)); BOOST_CHECK(add_and_verify(order_book, &bid10, false)); BOOST_CHECK(add_and_verify(order_book, &bid11, false)); BOOST_CHECK(add_and_verify(order_book, &bid12, false)); // Verify sizes BOOST_CHECK_EQUAL(13, order_book.bids().size()); BOOST_CHECK_EQUAL(11, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1249, 3, 500)); BOOST_CHECK(dc.verify_ask(1250, 1, 500)); // Cancel a bid level (erase) BOOST_CHECK(cancel_and_verify(order_book, &bid3, simple::os_cancelled)); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1249, 3, 500)); BOOST_CHECK(dc.verify_ask(1250, 1, 500)); // Cancel common bid levels (not erased) BOOST_CHECK(cancel_and_verify(order_book, &bid7, simple::os_cancelled)); BOOST_CHECK(cancel_and_verify(order_book, &bid4, simple::os_cancelled)); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1249, 3, 500)); BOOST_CHECK(dc.verify_ask(1250, 1, 500)); // Cancel the best bid level (erased) BOOST_CHECK(cancel_and_verify(order_book, &bid1, simple::os_cancelled)); BOOST_CHECK(cancel_and_verify(order_book, &bid0, simple::os_cancelled)); BOOST_CHECK(cancel_and_verify(order_book, &bid2, simple::os_cancelled)); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1246, 1, 500)); BOOST_CHECK(dc.verify_ask(1250, 1, 500)); } BOOST_AUTO_TEST_CASE(TestBboCancelAskRestore) { SimpleOrderBook order_book; SimpleOrder ask10(false, 1258, 600); SimpleOrder ask9(false, 1257, 700); SimpleOrder ask8(false, 1256, 100); SimpleOrder ask7(false, 1256, 100); SimpleOrder ask6(false, 1255, 500); SimpleOrder ask5(false, 1255, 200); SimpleOrder ask4(false, 1254, 300); SimpleOrder ask3(false, 1252, 200); SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 400); SimpleOrder ask0(false, 1250, 500); SimpleOrder bid0(true, 1249, 100); SimpleOrder bid1(true, 1249, 200); SimpleOrder bid2(true, 1249, 200); SimpleOrder bid3(true, 1248, 400); SimpleOrder bid4(true, 1246, 600); SimpleOrder bid5(true, 1246, 500); SimpleOrder bid6(true, 1245, 200); SimpleOrder bid7(true, 1245, 100); SimpleOrder bid8(true, 1245, 200); SimpleOrder bid9(true, 1244, 700); SimpleOrder bid10(true, 1244, 300); SimpleOrder bid11(true, 1242, 300); SimpleOrder bid12(true, 1241, 400); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &ask3, false)); BOOST_CHECK(add_and_verify(order_book, &ask4, false)); BOOST_CHECK(add_and_verify(order_book, &ask5, false)); BOOST_CHECK(add_and_verify(order_book, &ask6, false)); BOOST_CHECK(add_and_verify(order_book, &ask7, false)); BOOST_CHECK(add_and_verify(order_book, &ask8, false)); BOOST_CHECK(add_and_verify(order_book, &ask9, false)); BOOST_CHECK(add_and_verify(order_book, &ask10, false)); BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); BOOST_CHECK(add_and_verify(order_book, &bid3, false)); BOOST_CHECK(add_and_verify(order_book, &bid4, false)); BOOST_CHECK(add_and_verify(order_book, &bid5, false)); BOOST_CHECK(add_and_verify(order_book, &bid6, false)); BOOST_CHECK(add_and_verify(order_book, &bid7, false)); BOOST_CHECK(add_and_verify(order_book, &bid8, false)); BOOST_CHECK(add_and_verify(order_book, &bid9, false)); BOOST_CHECK(add_and_verify(order_book, &bid10, false)); BOOST_CHECK(add_and_verify(order_book, &bid11, false)); BOOST_CHECK(add_and_verify(order_book, &bid12, false)); // Verify sizes BOOST_CHECK_EQUAL(13, order_book.bids().size()); BOOST_CHECK_EQUAL(11, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1249, 3, 500)); BOOST_CHECK(dc.verify_ask(1250, 1, 500)); // Cancel an ask level (erase) BOOST_CHECK(cancel_and_verify(order_book, &ask1, simple::os_cancelled)); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1249, 3, 500)); BOOST_CHECK(dc.verify_ask(1250, 1, 500)); // Cancel common ask levels (not erased) BOOST_CHECK(cancel_and_verify(order_book, &ask2, simple::os_cancelled)); BOOST_CHECK(cancel_and_verify(order_book, &ask6, simple::os_cancelled)); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1249, 3, 500)); BOOST_CHECK(dc.verify_ask(1250, 1, 500)); // Cancel the best ask level (erased) BOOST_CHECK(cancel_and_verify(order_book, &ask0, simple::os_cancelled)); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1249, 3, 500)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); } BOOST_AUTO_TEST_CASE(TestBboFillCompleteBidRestoreDepth) { SimpleOrderBook order_book; SimpleOrder ask10(false, 1258, 600); SimpleOrder ask9(false, 1257, 700); SimpleOrder ask8(false, 1256, 100); SimpleOrder ask7(false, 1256, 100); SimpleOrder ask6(false, 1255, 500); SimpleOrder ask5(false, 1255, 200); SimpleOrder ask4(false, 1254, 300); SimpleOrder ask3(false, 1252, 200); SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 400); SimpleOrder ask0(false, 1250, 500); SimpleOrder bid0(true, 1249, 100); SimpleOrder bid1(true, 1249, 200); SimpleOrder bid2(true, 1249, 200); SimpleOrder bid3(true, 1248, 400); SimpleOrder bid4(true, 1246, 600); SimpleOrder bid5(true, 1246, 500); SimpleOrder bid6(true, 1245, 200); SimpleOrder bid7(true, 1245, 100); SimpleOrder bid8(true, 1245, 200); SimpleOrder bid9(true, 1244, 700); SimpleOrder bid10(true, 1244, 300); SimpleOrder bid11(true, 1242, 300); SimpleOrder bid12(true, 1241, 400); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &ask3, false)); BOOST_CHECK(add_and_verify(order_book, &ask4, false)); BOOST_CHECK(add_and_verify(order_book, &ask5, false)); BOOST_CHECK(add_and_verify(order_book, &ask6, false)); BOOST_CHECK(add_and_verify(order_book, &ask7, false)); BOOST_CHECK(add_and_verify(order_book, &ask8, false)); BOOST_CHECK(add_and_verify(order_book, &ask9, false)); BOOST_CHECK(add_and_verify(order_book, &ask10, false)); BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); BOOST_CHECK(add_and_verify(order_book, &bid3, false)); BOOST_CHECK(add_and_verify(order_book, &bid4, false)); BOOST_CHECK(add_and_verify(order_book, &bid5, false)); BOOST_CHECK(add_and_verify(order_book, &bid6, false)); BOOST_CHECK(add_and_verify(order_book, &bid7, false)); BOOST_CHECK(add_and_verify(order_book, &bid8, false)); BOOST_CHECK(add_and_verify(order_book, &bid9, false)); BOOST_CHECK(add_and_verify(order_book, &bid10, false)); BOOST_CHECK(add_and_verify(order_book, &bid11, false)); BOOST_CHECK(add_and_verify(order_book, &bid12, false)); // Verify sizes BOOST_CHECK_EQUAL(13, order_book.bids().size()); BOOST_CHECK_EQUAL(11, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1249, 3, 500)); BOOST_CHECK(dc.verify_ask(1250, 1, 500)); // Fill the top bid level (erase) and add an ask level (insert) SimpleOrder cross_ask(false, 1249, 800); { SimpleFillCheck fc1(&bid0, 100, 1249 * 100); SimpleFillCheck fc2(&bid1, 200, 1249 * 200); SimpleFillCheck fc3(&bid2, 200, 1249 * 200); SimpleFillCheck fc4(&cross_ask, 500, 1249 * 500); BOOST_CHECK(add_and_verify(order_book, &cross_ask, true, false)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1248, 1, 400)); BOOST_CHECK(dc.verify_ask(1249, 1, 300)); // Inserted // Fill the top bid level (erase) but do not add an ask level (no insert) SimpleOrder cross_ask2(false, 1248, 400); { SimpleFillCheck fc1(&bid3, 400, 1248 * 400); SimpleFillCheck fc4(&cross_ask2, 400, 1248 * 400); BOOST_CHECK(add_and_verify(order_book, &cross_ask2, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1246, 2, 1100)); BOOST_CHECK(dc.verify_ask(1249, 1, 300)); // Fill the top bid level (erase) and add ask level (insert), // but nothing to restore SimpleOrder cross_ask3(false, 1246, 2400); { SimpleFillCheck fc1(&bid4, 600, 1246 * 600); SimpleFillCheck fc2(&bid5, 500, 1246 * 500); SimpleFillCheck fc3(&cross_ask3, 1100, 1246 * 1100); BOOST_CHECK(add_and_verify(order_book, &cross_ask3, true, false)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1245, 3, 500)); BOOST_CHECK(dc.verify_ask(1246, 1, 1300)); // Partial fill the top bid level (reduce) SimpleOrder cross_ask4(false, 1245, 250); { SimpleFillCheck fc1(&bid6, 200, 1245 * 200); SimpleFillCheck fc2(&bid7, 50, 1245 * 50); SimpleFillCheck fc3(&cross_ask4, 250, 1245 * 250); BOOST_CHECK(add_and_verify(order_book, &cross_ask4, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1245, 2, 250)); // 1 filled, 1 reduced BOOST_CHECK(dc.verify_ask(1246, 1, 1300)); } BOOST_AUTO_TEST_CASE(TestBboFillCompleteAskRestoreDepth) { SimpleOrderBook order_book; SimpleOrder ask10(false, 1258, 600); SimpleOrder ask9(false, 1257, 700); SimpleOrder ask8(false, 1256, 100); SimpleOrder ask7(false, 1256, 100); SimpleOrder ask6(false, 1255, 500); SimpleOrder ask5(false, 1255, 200); SimpleOrder ask4(false, 1254, 300); SimpleOrder ask3(false, 1252, 200); SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 400); SimpleOrder ask0(false, 1250, 500); SimpleOrder bid0(true, 1249, 100); SimpleOrder bid1(true, 1249, 200); SimpleOrder bid2(true, 1249, 200); SimpleOrder bid3(true, 1248, 400); SimpleOrder bid4(true, 1246, 600); SimpleOrder bid5(true, 1246, 500); SimpleOrder bid6(true, 1245, 200); SimpleOrder bid7(true, 1245, 100); SimpleOrder bid8(true, 1245, 200); SimpleOrder bid9(true, 1244, 700); SimpleOrder bid10(true, 1244, 300); SimpleOrder bid11(true, 1242, 300); SimpleOrder bid12(true, 1241, 400); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &ask3, false)); BOOST_CHECK(add_and_verify(order_book, &ask4, false)); BOOST_CHECK(add_and_verify(order_book, &ask5, false)); BOOST_CHECK(add_and_verify(order_book, &ask6, false)); BOOST_CHECK(add_and_verify(order_book, &ask7, false)); BOOST_CHECK(add_and_verify(order_book, &ask8, false)); BOOST_CHECK(add_and_verify(order_book, &ask9, false)); BOOST_CHECK(add_and_verify(order_book, &ask10, false)); BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); BOOST_CHECK(add_and_verify(order_book, &bid3, false)); BOOST_CHECK(add_and_verify(order_book, &bid4, false)); BOOST_CHECK(add_and_verify(order_book, &bid5, false)); BOOST_CHECK(add_and_verify(order_book, &bid6, false)); BOOST_CHECK(add_and_verify(order_book, &bid7, false)); BOOST_CHECK(add_and_verify(order_book, &bid8, false)); BOOST_CHECK(add_and_verify(order_book, &bid9, false)); BOOST_CHECK(add_and_verify(order_book, &bid10, false)); BOOST_CHECK(add_and_verify(order_book, &bid11, false)); BOOST_CHECK(add_and_verify(order_book, &bid12, false)); // Verify sizes BOOST_CHECK_EQUAL(13, order_book.bids().size()); BOOST_CHECK_EQUAL(11, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1249, 3, 500)); BOOST_CHECK(dc.verify_ask(1250, 1, 500)); // Fill the top ask level (erase) and add a bid level (insert) SimpleOrder cross_bid(true, 1250, 800); { SimpleFillCheck fc1(&ask0, 500, 1250 * 500); SimpleFillCheck fc4(&cross_bid, 500, 1250 * 500); BOOST_CHECK(add_and_verify(order_book, &cross_bid, true, false)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 300)); BOOST_CHECK(dc.verify_ask(1251, 1, 400)); // Fill the top ask level (erase) but do not add an bid level (no insert) SimpleOrder cross_bid2(true, 1251, 400); { SimpleFillCheck fc1(&ask1, 400, 1251 * 400); SimpleFillCheck fc4(&cross_bid2, 400, 1251 * 400); BOOST_CHECK(add_and_verify(order_book, &cross_bid2, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 300)); BOOST_CHECK(dc.verify_ask(1252, 2, 300)); // Fill the top ask level (erase) and add bid level (insert), // but nothing to restore SimpleOrder cross_bid3(true, 1252, 2400); { SimpleFillCheck fc1(&ask2, 100, 1252 * 100); SimpleFillCheck fc2(&ask3, 200, 1252 * 200); SimpleFillCheck fc3(&cross_bid3, 300, 1252 * 300); BOOST_CHECK(add_and_verify(order_book, &cross_bid3, true, false)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1252, 1, 2100)); // Insert BOOST_CHECK(dc.verify_ask(1254, 1, 300)); // Fill the top ask level (erase) but nothing to restore SimpleOrder cross_bid4(true, 1254, 300); { SimpleFillCheck fc2(&ask4, 300, 1254 * 300); SimpleFillCheck fc3(&cross_bid4, 300, 1254 * 300); BOOST_CHECK(add_and_verify(order_book, &cross_bid4, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1252, 1, 2100)); BOOST_CHECK(dc.verify_ask(1255, 2, 700)); // Partial fill the top ask level (reduce) SimpleOrder cross_bid5(true, 1255, 550); { SimpleFillCheck fc1(&ask5, 200, 1255 * 200); SimpleFillCheck fc2(&ask6, 350, 1255 * 350); SimpleFillCheck fc3(&cross_bid5, 550, 1255 * 550); BOOST_CHECK(add_and_verify(order_book, &cross_bid5, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1252, 1, 2100)); BOOST_CHECK(dc.verify_ask(1255, 1, 150)); // 1 filled, 1 reduced } BOOST_AUTO_TEST_CASE(TestBboReplaceSizeDecrease) { SimpleOrderBook order_book; ChangedChecker cc(order_book.depth()); SimpleOrder ask1(false, 1252, 200); SimpleOrder ask0(false, 1252, 300); SimpleOrder bid1(true, 1251, 100); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 2, 500)); // Verify changed stamps BOOST_CHECK(cc.verify_bbo_changed(true, true)); cc.reset(); // Replace size BOOST_CHECK(replace_and_verify(order_book, &bid0, -60)); BOOST_CHECK(replace_and_verify(order_book, &ask0, -150)); // Verify orders BOOST_CHECK_EQUAL(40, bid0.order_qty()); BOOST_CHECK_EQUAL(150, ask0.order_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 2, 350)); // Verify changed stamps BOOST_CHECK(cc.verify_bbo_changed(false, true)); } BOOST_AUTO_TEST_CASE(TestBboReplaceSizeDecreaseCancel) { SimpleOrderBook order_book; ChangedChecker cc(order_book.depth()); SimpleOrder ask1(false, 1252, 200); SimpleOrder ask0(false, 1252, 300); SimpleOrder bid1(true, 1251, 400); SimpleOrder bid0(true, 1250, 100); SimpleOrder bid2(true, 1249, 700); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1251, 1, 400)); BOOST_CHECK(dc.verify_ask(1252, 2, 500)); // Partial Fill existing book SimpleOrder cross_bid(true, 1252, 125); SimpleOrder cross_ask(false, 1251, 100); { SimpleFillCheck fc1(&cross_bid, 125, 1252 * 125); SimpleFillCheck fc2(&ask0, 125, 1252 * 125); BOOST_CHECK(add_and_verify(order_book, &cross_bid, true, true)); } { SimpleFillCheck fc1(&cross_ask, 100, 1251 * 100); SimpleFillCheck fc2(&bid1, 100, 1251 * 100); BOOST_CHECK(add_and_verify(order_book, &cross_ask, true, true)); } // Verify quantity BOOST_CHECK_EQUAL(175, ask0.open_qty()); BOOST_CHECK_EQUAL(300, bid1.open_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 300)); BOOST_CHECK(dc.verify_ask(1252, 2, 375)); // Replace size - cancel BOOST_CHECK(replace_and_verify( order_book, &ask0, -175, PRICE_UNCHANGED, simple::os_cancelled)); // Verify orders BOOST_CHECK_EQUAL(125, ask0.order_qty()); BOOST_CHECK_EQUAL(0, ask0.open_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 300)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); // Replace size - reduce level BOOST_CHECK(replace_and_verify( order_book, &bid1, -100, PRICE_UNCHANGED, simple::os_accepted)); // Verify orders BOOST_CHECK_EQUAL(300, bid1.order_qty()); BOOST_CHECK_EQUAL(200, bid1.open_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 200)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); // Replace size - cancel and erase level BOOST_CHECK(replace_and_verify( order_book, &bid1, -200, PRICE_UNCHANGED, simple::os_cancelled)); // Verify orders BOOST_CHECK_EQUAL(100, bid1.order_qty()); BOOST_CHECK_EQUAL(0, bid1.open_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); } BOOST_AUTO_TEST_CASE(TestBboReplaceSizeDecreaseTooMuch) { SimpleOrderBook order_book; SimpleOrder ask1(false, 1252, 200); SimpleOrder ask0(false, 1252, 300); SimpleOrder bid1(true, 1251, 100); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 2, 500)); SimpleOrder cross_bid(true, 1252, 200); // Partial fill existing order { SimpleFillCheck fc1(&cross_bid, 200, 1252 * 200); SimpleFillCheck fc2(&ask0, 200, 1252 * 200); BOOST_CHECK(add_and_verify(order_book, &cross_bid, true, true)); } // Verify open quantity BOOST_CHECK_EQUAL(100, ask0.open_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 2, 300)); // Replace size - not enough left order_book.replace(&ask0, -150, PRICE_UNCHANGED); // Verify ask0 state BOOST_CHECK_EQUAL(0, ask0.open_qty()); BOOST_CHECK_EQUAL(200, ask0.order_qty()); BOOST_CHECK_EQUAL(simple::os_cancelled, ask0.state()); // Verify depth unchanged dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); } BOOST_AUTO_TEST_CASE(TestBboReplaceSizeIncreaseDecrease) { SimpleOrderBook order_book; SimpleOrder ask1(false, 1252, 200); SimpleOrder ask0(false, 1251, 300); SimpleOrder bid1(true, 1251, 100); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 300)); // Replace size BOOST_CHECK(replace_and_verify(order_book, &ask0, 50)); BOOST_CHECK(replace_and_verify(order_book, &bid0, 25)); BOOST_CHECK(replace_and_verify(order_book, &ask0, -100)); BOOST_CHECK(replace_and_verify(order_book, &bid0, 25)); BOOST_CHECK(replace_and_verify(order_book, &ask0, 300)); BOOST_CHECK(replace_and_verify(order_book, &bid0, -75)); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 75)); BOOST_CHECK(dc.verify_ask(1251, 1, 550)); } BOOST_AUTO_TEST_CASE(TestBboReplaceBidPriceChange) { SimpleOrderBook order_book; SimpleOrder ask0(false, 1253, 300); SimpleOrder ask1(false, 1252, 200); SimpleOrder bid1(true, 1251, 140); SimpleOrder bid0(true, 1250, 120); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1251, 1, 140)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); // Replace price increase BOOST_CHECK(replace_and_verify(order_book, &bid0, SIZE_UNCHANGED, 1251)); // Verify price change in book SimpleOrderBook::Bids::const_iterator bid = order_book.bids().begin(); BOOST_CHECK_EQUAL(1251, bid->first); BOOST_CHECK_EQUAL(&bid1, bid->second.ptr()); BOOST_CHECK_EQUAL(1251, (++bid)->first); BOOST_CHECK_EQUAL(&bid0, bid->second.ptr()); BOOST_CHECK(order_book.bids().end() == ++bid); // Verify order BOOST_CHECK_EQUAL(1251, bid0.price()); BOOST_CHECK_EQUAL(120, bid0.order_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 2, 260)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); // Replace price decrease BOOST_CHECK(replace_and_verify(order_book, &bid1, SIZE_UNCHANGED, 1250)); // Verify price change in book bid = order_book.bids().begin(); BOOST_CHECK_EQUAL(1251, bid->first); BOOST_CHECK_EQUAL(&bid0, bid->second.ptr()); BOOST_CHECK_EQUAL(1250, (++bid)->first); BOOST_CHECK_EQUAL(&bid1, bid->second.ptr()); BOOST_CHECK(order_book.bids().end() == ++bid); // Verify order BOOST_CHECK_EQUAL(1250, bid1.price()); BOOST_CHECK_EQUAL(140, bid1.order_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 120)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); } BOOST_AUTO_TEST_CASE(TestBboReplaceAskPriceChange) { SimpleOrderBook order_book; ChangedChecker cc(order_book.depth()); SimpleOrder ask0(false, 1253, 300); SimpleOrder ask1(false, 1252, 200); SimpleOrder bid1(true, 1251, 140); SimpleOrder bid0(true, 1250, 120); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1251, 1, 140)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); // Replace price increase 1252 -> 1253 BOOST_CHECK(replace_and_verify(order_book, &ask1, SIZE_UNCHANGED, 1253)); // Verify price change in book SimpleOrderBook::Asks::const_iterator ask = order_book.asks().begin(); BOOST_CHECK_EQUAL(1253, ask->first); BOOST_CHECK_EQUAL(&ask0, ask->second.ptr()); BOOST_CHECK_EQUAL(1253, (++ask)->first); BOOST_CHECK_EQUAL(&ask1, ask->second.ptr()); BOOST_CHECK(order_book.asks().end() == ++ask); // Verify order BOOST_CHECK_EQUAL(1253, ask1.price()); BOOST_CHECK_EQUAL(200, ask1.order_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 140)); BOOST_CHECK(dc.verify_ask(1253, 2, 500)); // Replace price decrease 1253 -> 1252 BOOST_CHECK(replace_and_verify(order_book, &ask0, SIZE_UNCHANGED, 1252)); // Verify price change in book ask = order_book.asks().begin(); BOOST_CHECK_EQUAL(1252, ask->first); BOOST_CHECK_EQUAL(&ask0, ask->second.ptr()); BOOST_CHECK_EQUAL(1253, (++ask)->first); BOOST_CHECK_EQUAL(&ask1, ask->second.ptr()); BOOST_CHECK(order_book.asks().end() == ++ask); // Verify order BOOST_CHECK_EQUAL(1252, ask0.price()); BOOST_CHECK_EQUAL(300, ask0.order_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 140)); BOOST_CHECK(dc.verify_ask(1252, 1, 300)); } } // namespace ================================================ FILE: test/unit/ut_depth.cpp ================================================ // Copyright (c) 2012 - 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #define BOOST_TEST_NO_MAIN LiquibookTest #include #include #include "changed_checker.h" #include namespace liquibook { using book::Depth; using book::DepthLevel; typedef Depth<5> SizedDepth; typedef test::ChangedChecker<5> ChangedChecker; bool verify_level(const DepthLevel*& level, book::Price price, uint32_t order_count, book::Quantity aggregate_qty) { bool matched = true; if (price != level->price()) { std::cout << "Level price " << level->price() << std::endl; matched = false; } if (order_count != level->order_count()) { std::cout << "Level order count " << level->order_count() << std::endl; matched = false; } if (aggregate_qty != level->aggregate_qty()) { std::cout << "Level aggregate qty " << level->aggregate_qty() << std::endl; matched = false; } ++level; return matched; } BOOST_AUTO_TEST_CASE(TestAddBid) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1234, 100, true); const DepthLevel* first_bid = depth.bids(); BOOST_CHECK(verify_level(first_bid, 1234, 1, 100)); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); } BOOST_AUTO_TEST_CASE(TestAddBids) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1234, 100, true); depth.add_order(1234, 200, true); depth.add_order(1234, 300, true); const DepthLevel* first_bid = depth.bids(); BOOST_CHECK(verify_level(first_bid, 1234, 3, 600)); BOOST_CHECK(cc.verify_bid_changed(true,false, false, false, false)); } BOOST_AUTO_TEST_CASE(TestAppendBidLevels) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1236, 300, true); depth.add_order(1235, 200, true); depth.add_order(1232, 100, true); depth.add_order(1235, 400, true); const DepthLevel* bid = depth.bids(); BOOST_CHECK(verify_level(bid, 1236, 1, 300)); BOOST_CHECK(verify_level(bid, 1235, 2, 600)); BOOST_CHECK(verify_level(bid, 1232, 1, 100)); BOOST_CHECK(cc.verify_bid_changed(true, true, true, false, false)); } BOOST_AUTO_TEST_CASE(TestInsertBidLevels) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1234, 800, true); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1232, 100, true); BOOST_CHECK(cc.verify_bid_changed(false, true, false, false, false)); cc.reset(); depth.add_order(1236, 300, true); BOOST_CHECK(cc.verify_bid_changed(true, true, true, false, false)); cc.reset(); depth.add_order(1235, 200, true); BOOST_CHECK(cc.verify_bid_changed(false, true, true, true, false)); cc.reset(); depth.add_order(1234, 900, true); BOOST_CHECK(cc.verify_bid_changed(false, false, true, false, false)); cc.reset(); depth.add_order(1231, 700, true); BOOST_CHECK(cc.verify_bid_changed(false, false, false, false, true)); cc.reset(); depth.add_order(1235, 400, true); BOOST_CHECK(cc.verify_bid_changed(false, true, false, false, false)); cc.reset(); depth.add_order(1231, 500, true); BOOST_CHECK(cc.verify_bid_changed(false, false, false, false, true)); cc.reset(); depth.add_order(1233, 200, true); BOOST_CHECK(cc.verify_bid_changed(false, false, false, true, true)); cc.reset(); const DepthLevel* bid = depth.bids(); BOOST_CHECK(verify_level(bid, 1236, 1, 300)); BOOST_CHECK(verify_level(bid, 1235, 2, 600)); BOOST_CHECK(verify_level(bid, 1234, 2, 1700)); BOOST_CHECK(verify_level(bid, 1233, 1, 200)); BOOST_CHECK(verify_level(bid, 1232, 1, 100)); } BOOST_AUTO_TEST_CASE(TestInsertBidLevelsPast5) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1234, 800, true); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1232, 100, true); BOOST_CHECK(cc.verify_bid_changed(false, true, false, false, false)); cc.reset(); depth.add_order(1236, 300, true); BOOST_CHECK(cc.verify_bid_changed(true, true, true, false, false)); cc.reset(); depth.add_order(1231, 700, true); BOOST_CHECK(cc.verify_bid_changed(false, false, false, true, false)); cc.reset(); depth.add_order(1234, 900, true); BOOST_CHECK(cc.verify_bid_changed(false, true, false, false, false)); cc.reset(); depth.add_order(1235, 400, true); BOOST_CHECK(cc.verify_bid_changed(false, true, true, true, true)); cc.reset(); depth.add_order(1235, 200, true); BOOST_CHECK(cc.verify_bid_changed(false, true, false, false, false)); cc.reset(); depth.add_order(1231, 500, true); BOOST_CHECK(cc.verify_bid_changed(false, false, false, false, true)); cc.reset(); depth.add_order(1230, 200, true); BOOST_CHECK(cc.verify_bid_changed(false, false, false, false, false)); cc.reset(); depth.add_order(1229, 200, true); BOOST_CHECK(cc.verify_bid_changed(false, false, false, false, false)); cc.reset(); const DepthLevel* bid = depth.bids(); BOOST_CHECK(verify_level(bid, 1236, 1, 300)); BOOST_CHECK(verify_level(bid, 1235, 2, 600)); BOOST_CHECK(verify_level(bid, 1234, 2, 1700)); BOOST_CHECK(verify_level(bid, 1232, 1, 100)); BOOST_CHECK(verify_level(bid, 1231, 2, 1200)); } BOOST_AUTO_TEST_CASE(TestInsertBidLevelsTruncate5) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1234, 800, true); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1232, 100, true); BOOST_CHECK(cc.verify_bid_changed(false, true, false, false, false)); cc.reset(); depth.add_order(1236, 300, true); BOOST_CHECK(cc.verify_bid_changed(true, true, true, false, false)); cc.reset(); depth.add_order(1231, 700, true); BOOST_CHECK(cc.verify_bid_changed(false, false, false, true, false)); cc.reset(); depth.add_order(1234, 900, true); BOOST_CHECK(cc.verify_bid_changed(false, true, false, false, false)); cc.reset(); depth.add_order(1235, 400, true); BOOST_CHECK(cc.verify_bid_changed(false, true, true, true, true)); cc.reset(); depth.add_order(1235, 200, true); BOOST_CHECK(cc.verify_bid_changed(false, true, false, false, false)); cc.reset(); depth.add_order(1231, 500, true); BOOST_CHECK(cc.verify_bid_changed(false, false, false, false, true)); cc.reset(); depth.add_order(1230, 200, true); BOOST_CHECK(cc.verify_bid_changed(false, false, false, false, false)); cc.reset(); depth.add_order(1238, 200, true); BOOST_CHECK(cc.verify_bid_changed(true, true, true, true, true)); cc.reset(); depth.add_order(1238, 250, true); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1237, 500, true); BOOST_CHECK(cc.verify_bid_changed(false, true, true, true, true)); cc.reset(); const DepthLevel* bid = depth.bids(); BOOST_CHECK(verify_level(bid, 1238, 2, 450)); BOOST_CHECK(verify_level(bid, 1237, 1, 500)); BOOST_CHECK(verify_level(bid, 1236, 1, 300)); BOOST_CHECK(verify_level(bid, 1235, 2, 600)); BOOST_CHECK(verify_level(bid, 1234, 2, 1700)); } BOOST_AUTO_TEST_CASE(TestCloseBid) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1234, 300, true); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1234, 500, true); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); BOOST_CHECK(!depth.close_order(1234, 300, true)); // Does not erase BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); const DepthLevel* first_bid = depth.bids(); BOOST_CHECK(verify_level(first_bid, 1234, 1, 500)); } BOOST_AUTO_TEST_CASE(TestCloseEraseBid) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1235, 300, true); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1235, 400, true); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1234, 500, true); BOOST_CHECK(cc.verify_bid_changed(false, true, false, false, false)); cc.reset(); depth.add_order(1233, 200, true); BOOST_CHECK(cc.verify_bid_changed(false, false, true, false, false)); cc.reset(); BOOST_CHECK(!depth.close_order(1235, 300, true)); // Does not erase BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); BOOST_CHECK(depth.close_order(1235, 400, true)); // Erase BOOST_CHECK(cc.verify_bid_changed(true, true, true, false, false)); const DepthLevel* bid = depth.bids(); BOOST_CHECK(verify_level(bid, 1234, 1, 500)); BOOST_CHECK(verify_level(bid, 1233, 1, 200)); } BOOST_AUTO_TEST_CASE(TestAddCloseAddBid) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1234, 300, true); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); depth.close_order(1234, 300, true); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1233, 200, true); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); const DepthLevel* bid = depth.bids(); BOOST_CHECK(verify_level(bid, 1233, 1, 200)); BOOST_CHECK(verify_level(bid, false, false, false)); } BOOST_AUTO_TEST_CASE(TestAddCloseAddHigherBid) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1234, 300, true); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); depth.close_order(1234, 300, true); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1235, 200, true); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); const DepthLevel* bid = depth.bids(); BOOST_CHECK(verify_level(bid, 1235, 1, 200)); BOOST_CHECK(verify_level(bid, false, false, false)); } BOOST_AUTO_TEST_CASE(TestCloseBidsFreeLevels) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1234, 800, true); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1232, 100, true); BOOST_CHECK(cc.verify_bid_changed(false, true, false, false, false)); cc.reset(); depth.add_order(1236, 300, true); BOOST_CHECK(cc.verify_bid_changed(true, true, true, false, false)); cc.reset(); depth.add_order(1235, 200, true); BOOST_CHECK(cc.verify_bid_changed(false, true, true, true, false)); cc.reset(); depth.add_order(1234, 900, true); BOOST_CHECK(cc.verify_bid_changed(false, false, true, false, false)); cc.reset(); depth.add_order(1231, 700, true); BOOST_CHECK(cc.verify_bid_changed(false, false, false, false, true)); cc.reset(); depth.add_order(1235, 400, true); BOOST_CHECK(cc.verify_bid_changed(false, true, false, false, false)); cc.reset(); depth.add_order(1231, 500, true); BOOST_CHECK(cc.verify_bid_changed(false, false, false, false, true)); cc.reset(); depth.close_order(1234, 900, true); // No erase BOOST_CHECK(cc.verify_bid_changed(false, false, true, false, false)); cc.reset(); depth.close_order(1232, 100, true); // Erase BOOST_CHECK(cc.verify_bid_changed(false, false, false, true, true)); cc.reset(); depth.close_order(1236, 300, true); // Erase BOOST_CHECK(cc.verify_bid_changed(true, true, true, true, false)); cc.reset(); const DepthLevel* bid = depth.bids(); BOOST_CHECK(verify_level(bid, 1235, 2, 600)); BOOST_CHECK(verify_level(bid, 1234, 1, 800)); BOOST_CHECK(verify_level(bid, 1231, 2, 1200)); BOOST_CHECK(verify_level(bid, 0, 0, 0)); BOOST_CHECK(verify_level(bid, 0, 0, 0)); depth.add_order(1233, 350, true); // Insert BOOST_CHECK(cc.verify_bid_changed(false, false, true, true, false)); cc.reset(); depth.add_order(1236, 300, true); // Insert BOOST_CHECK(cc.verify_bid_changed(true, true, true, true, true)); cc.reset(); depth.add_order(1231, 700, true); BOOST_CHECK(cc.verify_bid_changed(false, false, false, false, true)); cc.reset(); bid = depth.bids(); // reset BOOST_CHECK(verify_level(bid, 1236, 1, 300)); BOOST_CHECK(verify_level(bid, 1235, 2, 600)); BOOST_CHECK(verify_level(bid, 1234, 1, 800)); BOOST_CHECK(verify_level(bid, 1233, 1, 350)); BOOST_CHECK(verify_level(bid, 1231, 3, 1900)); } BOOST_AUTO_TEST_CASE(TestIncreaseBid) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1236, 300, true); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1235, 200, true); BOOST_CHECK(cc.verify_bid_changed(false, true, false, false, false)); cc.reset(); depth.add_order(1232, 100, true); BOOST_CHECK(cc.verify_bid_changed(false, false, true, false, false)); cc.reset(); depth.add_order(1235, 400, true); BOOST_CHECK(cc.verify_bid_changed(false, true, false, false, false)); cc.reset(); depth.change_qty_order(1232, 37, true); BOOST_CHECK(cc.verify_bid_changed(false, false, true, false, false)); cc.reset(); depth.change_qty_order(1232, 41, true); BOOST_CHECK(cc.verify_bid_changed(false, false, true, false, false)); cc.reset(); depth.change_qty_order(1235, 201, true); BOOST_CHECK(cc.verify_bid_changed(false, true, false, false, false)); cc.reset(); const DepthLevel* bid = depth.bids(); BOOST_CHECK(verify_level(bid, 1236, 1, 300)); BOOST_CHECK(verify_level(bid, 1235, 2, 801)); BOOST_CHECK(verify_level(bid, 1232, 1, 178)); } BOOST_AUTO_TEST_CASE(TestDecreaseBid) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1236, 300, true); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1235, 200, true); BOOST_CHECK(cc.verify_bid_changed(false, true, false, false, false)); cc.reset(); depth.add_order(1232, 100, true); BOOST_CHECK(cc.verify_bid_changed(false, false, true, false, false)); cc.reset(); depth.add_order(1235, 400, true); BOOST_CHECK(cc.verify_bid_changed(false, true, false, false, false)); cc.reset(); depth.change_qty_order(1236, -37, true); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); depth.change_qty_order(1236, -41, true); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); depth.change_qty_order(1235, -201, true); BOOST_CHECK(cc.verify_bid_changed(false, true, false, false, false)); cc.reset(); const DepthLevel* bid = depth.bids(); BOOST_CHECK(verify_level(bid, 1236, 1, 222)); BOOST_CHECK(verify_level(bid, 1235, 2, 399)); BOOST_CHECK(verify_level(bid, 1232, 1, 100)); } BOOST_AUTO_TEST_CASE(TestIncreaseDecreaseBid) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1236, 300, true); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1235, 200, true); BOOST_CHECK(cc.verify_bid_changed(false, true, false, false, false)); cc.reset(); depth.add_order(1232, 100, true); BOOST_CHECK(cc.verify_bid_changed(false, false, true, false, false)); cc.reset(); depth.add_order(1235, 400, true); BOOST_CHECK(cc.verify_bid_changed(false, true, false, false, false)); cc.reset(); depth.change_qty_order(1236, 37, true); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); depth.change_qty_order(1235, -41, true); BOOST_CHECK(cc.verify_bid_changed(false, true, false, false, false)); cc.reset(); depth.change_qty_order(1232, 60, true); BOOST_CHECK(cc.verify_bid_changed(false, false, true, false, false)); cc.reset(); depth.change_qty_order(1236, -41, true); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); depth.change_qty_order(1236, 210, true); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); const DepthLevel* bid = depth.bids(); BOOST_CHECK(verify_level(bid, 1236, 1, 506)); BOOST_CHECK(verify_level(bid, 1235, 2, 559)); BOOST_CHECK(verify_level(bid, 1232, 1, 160)); } BOOST_AUTO_TEST_CASE(TestAddAsk) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1234, 100, false); const DepthLevel* first_ask = depth.asks(); BOOST_CHECK(verify_level(first_ask, 1234, 1, 100)); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); } BOOST_AUTO_TEST_CASE(TestAddAsks) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1234, 100, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1234, 200, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1234, 300, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); const DepthLevel* first_ask = depth.asks(); BOOST_CHECK(verify_level(first_ask, 1234, 3, 600)); } BOOST_AUTO_TEST_CASE(TestAppendAskLevels) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1236, 300, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1235, 200, false); BOOST_CHECK(cc.verify_ask_changed(true, true, false, false, false)); cc.reset(); depth.add_order(1232, 100, false); BOOST_CHECK(cc.verify_ask_changed(true, true, true, false, false)); cc.reset(); depth.add_order(1235, 400, false); BOOST_CHECK(cc.verify_ask_changed(false, true, false, false, false)); cc.reset(); const DepthLevel* ask = depth.asks(); BOOST_CHECK(verify_level(ask, 1232, 1, 100)); BOOST_CHECK(verify_level(ask, 1235, 2, 600)); BOOST_CHECK(verify_level(ask, 1236, 1, 300)); } BOOST_AUTO_TEST_CASE(TestInsertAskLevels) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1234, 800, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1232, 100, false); BOOST_CHECK(cc.verify_ask_changed(true, true, false, false, false)); cc.reset(); depth.add_order(1236, 300, false); BOOST_CHECK(cc.verify_ask_changed(false, false, true, false, false)); cc.reset(); depth.add_order(1235, 200, false); BOOST_CHECK(cc.verify_ask_changed(false, false, true, true, false)); cc.reset(); depth.add_order(1234, 900, false); BOOST_CHECK(cc.verify_ask_changed(false, true, false, false, false)); cc.reset(); depth.add_order(1231, 700, false); depth.add_order(1235, 400, false); depth.add_order(1231, 500, false); const DepthLevel* ask = depth.asks(); BOOST_CHECK(verify_level(ask, 1231, 2, 1200)); BOOST_CHECK(verify_level(ask, 1232, 1, 100)); BOOST_CHECK(verify_level(ask, 1234, 2, 1700)); BOOST_CHECK(verify_level(ask, 1235, 2, 600)); BOOST_CHECK(verify_level(ask, 1236, 1, 300)); } BOOST_AUTO_TEST_CASE(TestInsertAskLevelsPast5) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1234, 800, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1232, 100, false); BOOST_CHECK(cc.verify_ask_changed(true, true, false, false, false)); cc.reset(); depth.add_order(1236, 300, false); BOOST_CHECK(cc.verify_ask_changed(false, false, true, false, false)); cc.reset(); depth.add_order(1231, 700, false); BOOST_CHECK(cc.verify_ask_changed(true, true, true, true, false)); cc.reset(); depth.add_order(1234, 900, false); BOOST_CHECK(cc.verify_ask_changed(false, false, true, false, false)); cc.reset(); depth.add_order(1235, 400, false); BOOST_CHECK(cc.verify_ask_changed(false, false, false, true, true)); cc.reset(); depth.add_order(1235, 200, false); BOOST_CHECK(cc.verify_ask_changed(false, false, false, true, false)); cc.reset(); depth.add_order(1231, 500, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1230, 200, false); BOOST_CHECK(cc.verify_ask_changed(true, true, true, true, true)); cc.reset(); depth.add_order(1229, 200, false); BOOST_CHECK(cc.verify_ask_changed(true, true, true, true, true)); cc.reset(); const DepthLevel* ask = depth.asks(); BOOST_CHECK(verify_level(ask, 1229, 1, 200)); BOOST_CHECK(verify_level(ask, 1230, 1, 200)); BOOST_CHECK(verify_level(ask, 1231, 2, 1200)); BOOST_CHECK(verify_level(ask, 1232, 1, 100)); BOOST_CHECK(verify_level(ask, 1234, 2, 1700)); } BOOST_AUTO_TEST_CASE(TestInsertAskLevelsTruncate5) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1234, 800, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1232, 100, false); BOOST_CHECK(cc.verify_ask_changed(true, true, false, false, false)); cc.reset(); depth.add_order(1236, 300, false); BOOST_CHECK(cc.verify_ask_changed(false, false, true, false, false)); cc.reset(); depth.add_order(1231, 700, false); BOOST_CHECK(cc.verify_ask_changed(true, true, true, true, false)); cc.reset(); depth.add_order(1234, 900, false); BOOST_CHECK(cc.verify_ask_changed(false, false, true, false, false)); cc.reset(); depth.add_order(1235, 400, false); BOOST_CHECK(cc.verify_ask_changed(false, false, false, true, true)); cc.reset(); depth.add_order(1235, 200, false); BOOST_CHECK(cc.verify_ask_changed(false, false, false, true, false)); cc.reset(); depth.add_order(1231, 500, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1230, 200, false); BOOST_CHECK(cc.verify_ask_changed(true, true, true, true, true)); cc.reset(); depth.add_order(1238, 200, false); BOOST_CHECK(cc.verify_ask_changed(false, false, false, false, false)); cc.reset(); depth.add_order(1232, 250, false); BOOST_CHECK(cc.verify_ask_changed(false, false, true, false, false)); cc.reset(); depth.add_order(1237, 500, false); BOOST_CHECK(cc.verify_ask_changed(false, false, false, false, false)); cc.reset(); const DepthLevel* ask = depth.asks(); BOOST_CHECK(verify_level(ask, 1230, 1, 200)); BOOST_CHECK(verify_level(ask, 1231, 2, 1200)); BOOST_CHECK(verify_level(ask, 1232, 2, 350)); BOOST_CHECK(verify_level(ask, 1234, 2, 1700)); BOOST_CHECK(verify_level(ask, 1235, 2, 600)); } BOOST_AUTO_TEST_CASE(TestCloseAsk) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1234, 300, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1234, 500, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); BOOST_CHECK(!depth.close_order(1234, 300, false)); // Does not erase const DepthLevel* first_ask = depth.asks(); BOOST_CHECK(verify_level(first_ask, 1234, 1, 500)); } BOOST_AUTO_TEST_CASE(TestCloseEraseAsk) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1233, 300, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1234, 500, false); BOOST_CHECK(cc.verify_ask_changed(false, true, false, false, false)); cc.reset(); depth.add_order(1233, 400, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); BOOST_CHECK(!depth.close_order(1233, 300, false)); // Does not erase BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); BOOST_CHECK(depth.close_order(1233, 400, false)); // Erase BOOST_CHECK(cc.verify_ask_changed(true, true, false, false, false)); cc.reset(); const DepthLevel* first_ask = depth.asks(); BOOST_CHECK(verify_level(first_ask, 1234, 1, 500)); } BOOST_AUTO_TEST_CASE(TestAddCloseAddAsk) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1234, 300, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); depth.close_order(1234, 300, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1233, 200, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); const DepthLevel* ask = depth.asks(); BOOST_CHECK(verify_level(ask, 1233, 1, 200)); BOOST_CHECK(verify_level(ask, false, false, false)); } BOOST_AUTO_TEST_CASE(TestAddCloseAddHigherAsk) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1234, 300, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); depth.close_order(1234, 300, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1235, 200, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); const DepthLevel* ask = depth.asks(); BOOST_CHECK(verify_level(ask, 1235, 1, 200)); BOOST_CHECK(verify_level(ask, false, false, false)); } BOOST_AUTO_TEST_CASE(TestCloseAsksFreeLevels) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1234, 800, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1232, 100, false); BOOST_CHECK(cc.verify_ask_changed(true, true, false, false, false)); cc.reset(); depth.add_order(1236, 300, false); BOOST_CHECK(cc.verify_ask_changed(false, false, true, false, false)); cc.reset(); depth.add_order(1235, 200, false); BOOST_CHECK(cc.verify_ask_changed(false, false, true, true, false)); cc.reset(); depth.add_order(1234, 900, false); BOOST_CHECK(cc.verify_ask_changed(false, true, false, false, false)); cc.reset(); depth.add_order(1231, 700, false); BOOST_CHECK(cc.verify_ask_changed(true, true, true, true, true)); cc.reset(); depth.add_order(1235, 400, false); BOOST_CHECK(cc.verify_ask_changed(false, false, false, true, false)); cc.reset(); depth.add_order(1231, 500, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); depth.close_order(1234, 900, false); // does not erase BOOST_CHECK(cc.verify_ask_changed(false, false, true, false, false)); cc.reset(); depth.close_order(1232, 100, false); // erase BOOST_CHECK(cc.verify_ask_changed(false, true, true, true, true)); cc.reset(); depth.close_order(1236, 100, false); BOOST_CHECK(cc.verify_ask_changed(false, false, false, true, false)); cc.reset(); const DepthLevel* ask = depth.asks(); BOOST_CHECK(verify_level(ask, 1231, 2, 1200)); BOOST_CHECK(verify_level(ask, 1234, 1, 800)); BOOST_CHECK(verify_level(ask, 1235, 2, 600)); BOOST_CHECK(verify_level(ask, 0, 0, 0)); BOOST_CHECK(verify_level(ask, 0, 0, 0)); depth.add_order(1233, 350, false); depth.add_order(1236, 300, false); depth.add_order(1231, 700, false); ask = depth.asks(); // reset BOOST_CHECK(verify_level(ask, 1231, 3, 1900)); BOOST_CHECK(verify_level(ask, 1233, 1, 350)); BOOST_CHECK(verify_level(ask, 1234, 1, 800)); BOOST_CHECK(verify_level(ask, 1235, 2, 600)); BOOST_CHECK(verify_level(ask, 1236, 1, 300)); } BOOST_AUTO_TEST_CASE(TestIncreaseAsk) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1236, 300, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1235, 200, false); BOOST_CHECK(cc.verify_ask_changed(true, true, false, false, false)); cc.reset(); depth.add_order(1232, 100, false); BOOST_CHECK(cc.verify_ask_changed(true, true, true, false, false)); cc.reset(); depth.add_order(1235, 400, false); BOOST_CHECK(cc.verify_ask_changed(false, true, false, false, false)); cc.reset(); depth.change_qty_order(1232, 37, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); depth.change_qty_order(1232, 41, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); depth.change_qty_order(1235, 201, false); BOOST_CHECK(cc.verify_ask_changed(false, true, false, false, false)); cc.reset(); const DepthLevel* ask = depth.asks(); BOOST_CHECK(verify_level(ask, 1232, 1, 178)); BOOST_CHECK(verify_level(ask, 1235, 2, 801)); BOOST_CHECK(verify_level(ask, 1236, 1, 300)); } BOOST_AUTO_TEST_CASE(TestDecreaseAsk) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1236, 300, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1235, 200, false); BOOST_CHECK(cc.verify_ask_changed(true, true, false, false, false)); cc.reset(); depth.add_order(1232, 100, false); BOOST_CHECK(cc.verify_ask_changed(true, true, true, false, false)); cc.reset(); depth.add_order(1235, 400, false); BOOST_CHECK(cc.verify_ask_changed(false, true, false, false, false)); cc.reset(); depth.change_qty_order(1236, -37, false); BOOST_CHECK(cc.verify_ask_changed(false, false, true, false, false)); cc.reset(); depth.change_qty_order(1236, -41, false); BOOST_CHECK(cc.verify_ask_changed(false, false, true, false, false)); cc.reset(); depth.change_qty_order(1235, -201, false); BOOST_CHECK(cc.verify_ask_changed(false, true, false, false, false)); cc.reset(); const DepthLevel* ask = depth.asks(); BOOST_CHECK(verify_level(ask, 1232, 1, 100)); BOOST_CHECK(verify_level(ask, 1235, 2, 399)); BOOST_CHECK(verify_level(ask, 1236, 1, 222)); } BOOST_AUTO_TEST_CASE(TestIncreaseDecreaseAsk) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1236, 300, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1235, 200, false); BOOST_CHECK(cc.verify_ask_changed(true, true, false, false, false)); cc.reset(); depth.add_order(1232, 100, false); BOOST_CHECK(cc.verify_ask_changed(true, true, true, false, false)); cc.reset(); depth.add_order(1235, 400, false); BOOST_CHECK(cc.verify_ask_changed(false, true, false, false, false)); cc.reset(); depth.change_qty_order(1236, 37, false); BOOST_CHECK(cc.verify_ask_changed(false, false, true, false, false)); cc.reset(); depth.change_qty_order(1235, -41, false); BOOST_CHECK(cc.verify_ask_changed(false, true, false, false, false)); cc.reset(); depth.change_qty_order(1232, 51, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); depth.change_qty_order(1236, -41, false); BOOST_CHECK(cc.verify_ask_changed(false, false, true, false, false)); cc.reset(); depth.change_qty_order(1236, 201, false); BOOST_CHECK(cc.verify_ask_changed(false, false, true, false, false)); cc.reset(); const DepthLevel* ask = depth.asks(); BOOST_CHECK(verify_level(ask, 1232, 1, 151)); BOOST_CHECK(verify_level(ask, 1235, 2, 559)); BOOST_CHECK(verify_level(ask, 1236, 1, 497)); } BOOST_AUTO_TEST_CASE(TestReplaceBid) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1236, 300, true); BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1235, 200, true); BOOST_CHECK(cc.verify_bid_changed(false, true, false, false, false)); cc.reset(); depth.add_order(1232, 100, true); BOOST_CHECK(cc.verify_bid_changed(false, false, true, false, false)); cc.reset(); depth.add_order(1235, 400, true); BOOST_CHECK(cc.verify_bid_changed(false, true, false, false, false)); cc.reset(); // Verify Levels const DepthLevel* bid = depth.bids(); BOOST_CHECK(verify_level(bid, 1236, 1, 300)); BOOST_CHECK(verify_level(bid, 1235, 2, 600)); BOOST_CHECK(verify_level(bid, 1232, 1, 100)); // Replace bid depth.replace_order(1235, 1237, 200, 200, true); // Verify Levels bid = depth.bids(); BOOST_CHECK(verify_level(bid, 1237, 1, 200)); BOOST_CHECK(verify_level(bid, 1236, 1, 300)); BOOST_CHECK(verify_level(bid, 1235, 1, 400)); BOOST_CHECK(verify_level(bid, 1232, 1, 100)); BOOST_CHECK(cc.verify_bid_changed(true, true, true, true, false)); cc.reset(); } BOOST_AUTO_TEST_CASE(TestReplaceAsk) { SizedDepth depth; ChangedChecker cc(depth); depth.add_order(1236, 300, false); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); depth.add_order(1235, 200, false); BOOST_CHECK(cc.verify_ask_changed(true, true, false, false, false)); cc.reset(); depth.add_order(1232, 100, false); BOOST_CHECK(cc.verify_ask_changed(true, true, true, false, false)); cc.reset(); depth.add_order(1235, 400, false); BOOST_CHECK(cc.verify_ask_changed(false, true, false, false, false)); cc.reset(); // Verify Levels const DepthLevel* ask = depth.asks(); BOOST_CHECK(verify_level(ask, 1232, 1, 100)); BOOST_CHECK(verify_level(ask, 1235, 2, 600)); BOOST_CHECK(verify_level(ask, 1236, 1, 300)); // Replace ask depth.replace_order(1235, 1237, 200, 200, false); // Verify Levels ask = depth.asks(); BOOST_CHECK(verify_level(ask, 1232, 1, 100)); BOOST_CHECK(verify_level(ask, 1235, 1, 400)); BOOST_CHECK(verify_level(ask, 1236, 1, 300)); BOOST_CHECK(verify_level(ask, 1237, 1, 200)); BOOST_CHECK(cc.verify_ask_changed(false, true, false, true, false)); cc.reset(); } } // namespace ================================================ FILE: test/unit/ut_immediate_or_cancel.cpp ================================================ // Copyright (c) 2012 - 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #define BOOST_TEST_NO_MAIN LiquibookTest #include #include "ut_utils.h" namespace liquibook { using simple::SimpleOrder; typedef FillCheck SimpleFillCheck; OrderConditions IOC(oc_immediate_or_cancel); OrderConditions FOK(oc_all_or_none | oc_immediate_or_cancel); BOOST_AUTO_TEST_CASE(TestIocBidNoMatch) { SimpleOrderBook order_book; SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 100); SimpleOrder ask0(false, 1250, 100); SimpleOrder bid0(true, 1250, 100); SimpleOrder bid1(true, 1249, 100); SimpleOrder bid2(true, 1248, 100); // No match BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // No Match - will cancel order { SimpleFillCheck fc0(&bid0, 0, 0, IOC); SimpleFillCheck fc1(&bid1, 0, 0); SimpleFillCheck fc2(&bid2, 0, 0); //SimpleFillCheck fc3(&ask0, 0, 0); SimpleFillCheck fc4(&ask1, 0, 0); SimpleFillCheck fc5(&ask2, 0, 0); BOOST_CHECK(add_and_verify(order_book, &bid0, false, false, IOC)); } // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); } BOOST_AUTO_TEST_CASE(TestIocBidPartialMatch) { SimpleOrderBook order_book; SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 100); SimpleOrder ask0(false, 1250, 100); SimpleOrder bid0(true, 1250, 300); SimpleOrder bid1(true, 1249, 100); SimpleOrder bid2(true, 1248, 100); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Partial Match - will cancel order { SimpleFillCheck fc0(&bid0, 100, 125000, IOC); SimpleFillCheck fc1(&bid1, 0, 0); SimpleFillCheck fc2(&bid2, 0, 0); SimpleFillCheck fc3(&ask0, 100, 125000); SimpleFillCheck fc4(&ask1, 0, 0); SimpleFillCheck fc5(&ask2, 0, 0); BOOST_CHECK(add_and_verify(order_book, &bid0, true, false, IOC)); } // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); } BOOST_AUTO_TEST_CASE(TestIocBidFullMatch) { SimpleOrderBook order_book; SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 100); SimpleOrder ask0(false, 1250, 400); SimpleOrder bid0(true, 1250, 300); SimpleOrder bid1(true, 1249, 100); SimpleOrder bid2(true, 1248, 100); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1250, 1, 400)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Full Match - will complete order { SimpleFillCheck fc0(&bid0, 300, 1250 * 300, IOC); SimpleFillCheck fc1(&bid1, 0, 0); SimpleFillCheck fc2(&bid2, 0, 0); SimpleFillCheck fc3(&ask0, 300, 1250 * 300); SimpleFillCheck fc4(&ask1, 0, 0); SimpleFillCheck fc5(&ask2, 0, 0); BOOST_CHECK(add_and_verify(order_book, &bid0, true, true, IOC)); } // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); } BOOST_AUTO_TEST_CASE(TestIocBidMultiMatch) { SimpleOrderBook order_book; SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 100); SimpleOrder ask0(false, 1250, 400); SimpleOrder bid0(true, 1251, 500); SimpleOrder bid1(true, 1249, 100); SimpleOrder bid2(true, 1248, 100); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1250, 1, 400)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Full Match - will complete order { SimpleFillCheck fc0(&bid0, 500, 625100, IOC); SimpleFillCheck fc1(&bid1, 0, 0); SimpleFillCheck fc2(&bid2, 0, 0); SimpleFillCheck fc3(&ask0, 400, 1250 * 400); SimpleFillCheck fc4(&ask1, 100, 1251 * 100); SimpleFillCheck fc5(&ask2, 0, 0); BOOST_CHECK(add_and_verify(order_book, &bid0, true, true, IOC)); } // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); } BOOST_AUTO_TEST_CASE(TestFokBidNoMatch) { SimpleOrderBook order_book; SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 100); SimpleOrder ask0(false, 1250, 100); SimpleOrder bid0(true, 1250, 100); SimpleOrder bid1(true, 1249, 100); SimpleOrder bid2(true, 1248, 100); // No match BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // No Match - will cancel order { SimpleFillCheck fc0(&bid0, 0, 0, FOK); SimpleFillCheck fc1(&bid1, 0, 0); SimpleFillCheck fc2(&bid2, 0, 0); //SimpleFillCheck fc3(&ask0, 0, 0); SimpleFillCheck fc4(&ask1, 0, 0); SimpleFillCheck fc5(&ask2, 0, 0); BOOST_CHECK(add_and_verify(order_book, &bid0, false, false, FOK)); } // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); } BOOST_AUTO_TEST_CASE(TestFokBidPartialMatch) { SimpleOrderBook order_book; SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 100); SimpleOrder ask0(false, 1250, 100); SimpleOrder bid0(true, 1250, 300); SimpleOrder bid1(true, 1249, 100); SimpleOrder bid2(true, 1248, 100); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Partial Match - will not fill and will cancel order { SimpleFillCheck fc0(&bid0, 0, 0, FOK); SimpleFillCheck fc1(&bid1, 0, 0); SimpleFillCheck fc2(&bid2, 0, 0); SimpleFillCheck fc3(&ask0, 0, 0); SimpleFillCheck fc4(&ask1, 0, 0); SimpleFillCheck fc5(&ask2, 0, 0); BOOST_CHECK(add_and_verify(order_book, &bid0, false, false, FOK)); } // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); } BOOST_AUTO_TEST_CASE(TestFokBidFullMatch) { SimpleOrderBook order_book; SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 100); SimpleOrder ask0(false, 1250, 400); SimpleOrder bid0(true, 1250, 300); SimpleOrder bid1(true, 1249, 100); SimpleOrder bid2(true, 1248, 100); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1250, 1, 400)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Full Match - will complete order { SimpleFillCheck fc0(&bid0, 300, 1250 * 300, FOK); SimpleFillCheck fc1(&bid1, 0, 0); SimpleFillCheck fc2(&bid2, 0, 0); SimpleFillCheck fc3(&ask0, 300, 1250 * 300); SimpleFillCheck fc4(&ask1, 0, 0); SimpleFillCheck fc5(&ask2, 0, 0); BOOST_CHECK(add_and_verify(order_book, &bid0, true, true, FOK)); } // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); } BOOST_AUTO_TEST_CASE(TestFokBidMultiMatch) { SimpleOrderBook order_book; SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 100); SimpleOrder ask0(false, 1250, 400); SimpleOrder bid0(true, 1251, 500); SimpleOrder bid1(true, 1249, 100); SimpleOrder bid2(true, 1248, 100); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1250, 1, 400)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Full Match - will complete order { SimpleFillCheck fc0(&bid0, 500, 625100, FOK); SimpleFillCheck fc1(&bid1, 0, 0); SimpleFillCheck fc2(&bid2, 0, 0); SimpleFillCheck fc3(&ask0, 400, 1250 * 400); SimpleFillCheck fc4(&ask1, 100, 1251 * 100); SimpleFillCheck fc5(&ask2, 0, 0); BOOST_CHECK(add_and_verify(order_book, &bid0, true, true, FOK)); } // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); } BOOST_AUTO_TEST_CASE(TestIocAskNoMatch) { SimpleOrderBook order_book; SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 100); SimpleOrder ask0(false, 1250, 100); SimpleOrder bid0(true, 1250, 100); SimpleOrder bid1(true, 1249, 100); SimpleOrder bid2(true, 1248, 100); // No match BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // No Match - will cancel order { // SimpleFillCheck fc0(&bid0, 0, 0); SimpleFillCheck fc1(&bid1, 0, 0); SimpleFillCheck fc2(&bid2, 0, 0); SimpleFillCheck fc3(&ask0, 0, 0, IOC); SimpleFillCheck fc4(&ask1, 0, 0); SimpleFillCheck fc5(&ask2, 0, 0); BOOST_CHECK(add_and_verify(order_book, &ask0, false, false, IOC)); } // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); } BOOST_AUTO_TEST_CASE(TestIocAskPartialMatch) { SimpleOrderBook order_book; SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 100); SimpleOrder ask0(false, 1250, 300); SimpleOrder bid0(true, 1250, 100); SimpleOrder bid1(true, 1249, 100); SimpleOrder bid2(true, 1248, 100); // No match BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); // Verify sizes BOOST_CHECK_EQUAL(3, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Partial Match - will cancel order { SimpleFillCheck fc0(&bid0, 100, 125000); SimpleFillCheck fc1(&bid1, 0, 0); SimpleFillCheck fc2(&bid2, 0, 0); SimpleFillCheck fc3(&ask0, 100, 125000, IOC); SimpleFillCheck fc4(&ask1, 0, 0); SimpleFillCheck fc5(&ask2, 0, 0); BOOST_CHECK(add_and_verify(order_book, &ask0, true, false, IOC)); } // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); } BOOST_AUTO_TEST_CASE(TestIocAskFullMatch) { SimpleOrderBook order_book; SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 100); SimpleOrder ask0(false, 1250, 300); SimpleOrder bid0(true, 1250, 300); SimpleOrder bid1(true, 1249, 100); SimpleOrder bid2(true, 1248, 100); // No match BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); // Verify sizes BOOST_CHECK_EQUAL(3, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 1, 300)); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Full match { SimpleFillCheck fc0(&bid0, 300, 1250 * 300); SimpleFillCheck fc1(&bid1, 0, 0); SimpleFillCheck fc2(&bid2, 0, 0); SimpleFillCheck fc3(&ask0, 300, 1250 * 300, IOC); SimpleFillCheck fc4(&ask1, 0, 0); SimpleFillCheck fc5(&ask2, 0, 0); BOOST_CHECK(add_and_verify(order_book, &ask0, true, true, IOC)); } // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); } BOOST_AUTO_TEST_CASE(TestIocAskMultiMatch) { SimpleOrderBook order_book; SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 100); SimpleOrder ask0(false, 1249, 400); SimpleOrder bid0(true, 1250, 300); SimpleOrder bid1(true, 1249, 100); SimpleOrder bid2(true, 1248, 100); // No match BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); // Verify sizes BOOST_CHECK_EQUAL(3, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 1, 300)); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Full match { SimpleFillCheck fc0(&bid0, 300, 1250 * 300); SimpleFillCheck fc1(&bid1, 100, 1249 * 100); SimpleFillCheck fc2(&bid2, 0, 0); SimpleFillCheck fc3(&ask0, 400, 499900, IOC); SimpleFillCheck fc4(&ask1, 0, 0); SimpleFillCheck fc5(&ask2, 0, 0); BOOST_CHECK(add_and_verify(order_book, &ask0, true, true, IOC)); } // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); } BOOST_AUTO_TEST_CASE(TestFokAskNoMatch) { SimpleOrderBook order_book; SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 100); SimpleOrder ask0(false, 1250, 100); SimpleOrder bid0(true, 1250, 100); SimpleOrder bid1(true, 1249, 100); SimpleOrder bid2(true, 1248, 100); // No match BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // No Match - will cancel order { // SimpleFillCheck fc0(&bid0, 0, 0); SimpleFillCheck fc1(&bid1, 0, 0); SimpleFillCheck fc2(&bid2, 0, 0); SimpleFillCheck fc3(&ask0, 0, 0, FOK); SimpleFillCheck fc4(&ask1, 0, 0); SimpleFillCheck fc5(&ask2, 0, 0); BOOST_CHECK(add_and_verify(order_book, &ask0, false, false, FOK)); } // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); } BOOST_AUTO_TEST_CASE(TestFokAskPartialMatch) { SimpleOrderBook order_book; SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 100); SimpleOrder ask0(false, 1250, 300); SimpleOrder bid0(true, 1250, 100); SimpleOrder bid1(true, 1249, 100); SimpleOrder bid2(true, 1248, 100); // No match BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); // Verify sizes BOOST_CHECK_EQUAL(3, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Partial Match - will not fill and will cancel order { SimpleFillCheck fc0(&bid0, 0, 0); SimpleFillCheck fc1(&bid1, 0, 0); SimpleFillCheck fc2(&bid2, 0, 0); SimpleFillCheck fc3(&ask0, 0, 0, FOK); SimpleFillCheck fc4(&ask1, 0, 0); SimpleFillCheck fc5(&ask2, 0, 0); BOOST_CHECK(add_and_verify(order_book, &ask0, false, false, FOK)); } // Verify sizes BOOST_CHECK_EQUAL(3, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); } BOOST_AUTO_TEST_CASE(TestFokAskFullMatch) { SimpleOrderBook order_book; SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 100); SimpleOrder ask0(false, 1250, 300); SimpleOrder bid0(true, 1250, 300); SimpleOrder bid1(true, 1249, 100); SimpleOrder bid2(true, 1248, 100); // No match BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); // Verify sizes BOOST_CHECK_EQUAL(3, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 1, 300)); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Full Match { SimpleFillCheck fc0(&bid0, 300, 1250 * 300); SimpleFillCheck fc1(&bid1, 0, 0); SimpleFillCheck fc2(&bid2, 0, 0); SimpleFillCheck fc3(&ask0, 300, 1250 * 300, FOK); SimpleFillCheck fc4(&ask1, 0, 0); SimpleFillCheck fc5(&ask2, 0, 0); BOOST_CHECK(add_and_verify(order_book, &ask0, true, true, FOK)); } // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); } BOOST_AUTO_TEST_CASE(TestFokAskMultiMatch) { SimpleOrderBook order_book; SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 100); SimpleOrder ask0(false, 1249, 400); SimpleOrder bid0(true, 1250, 300); SimpleOrder bid1(true, 1249, 100); SimpleOrder bid2(true, 1248, 100); // No match BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); // Verify sizes BOOST_CHECK_EQUAL(3, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 1, 300)); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Full match { SimpleFillCheck fc0(&bid0, 300, 1250 * 300); SimpleFillCheck fc1(&bid1, 100, 1249 * 100); SimpleFillCheck fc2(&bid2, 0, 0); SimpleFillCheck fc3(&ask0, 400, 499900, FOK); SimpleFillCheck fc4(&ask1, 0, 0); SimpleFillCheck fc5(&ask2, 0, 0); BOOST_CHECK(add_and_verify(order_book, &ask0, true, true, FOK)); } // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1248, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); } } // Namespace ================================================ FILE: test/unit/ut_listeners.cpp ================================================ // Copyright (c) 2012 - 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #define BOOST_TEST_NO_MAIN LiquibookTest #include #include "ut_utils.h" #include "changed_checker.h" #include #include namespace liquibook { using book::OrderBook; using simple::SimpleOrder; typedef SimpleOrder* OrderPtr; typedef OrderBook TypedOrderBook; typedef DepthOrderBook TypedDepthOrderBook; typedef TypedDepthOrderBook::DepthTracker DepthTracker; class TradeCbListener : public TradeListener { public: virtual void on_trade(const TypedOrderBook* order_book, Quantity qty, Cost cost) { quantities_.push_back(qty); costs_.push_back(cost); } void reset() { quantities_.clear(); costs_.clear(); } std::vector quantities_; std::vector costs_; }; class OrderCbListener : public OrderListener { public: virtual void on_accept(const OrderPtr& order) { accepts_.push_back(order); } virtual void on_reject(const OrderPtr& order, const char* ) { rejects_.push_back(order); } virtual void on_fill(const OrderPtr& order, const OrderPtr& , // matched_order Quantity , // fill_qty Cost) // fill_cost { fills_.push_back(order); } virtual void on_cancel(const OrderPtr& order) { cancels_.push_back(order); } virtual void on_cancel_reject(const OrderPtr& order, const char* ) { cancel_rejects_.push_back(order); } virtual void on_replace(const OrderPtr& order, const int32_t& , // size_delta Price ) // new_price) { replaces_.push_back(order); } virtual void on_replace_reject(const OrderPtr& order, const char* ) { replace_rejects_.push_back(order); } void reset() { accepts_.clear(); rejects_.clear(); fills_.clear(); cancels_.clear(); cancel_rejects_.clear(); replaces_.clear(); replace_rejects_.clear(); } typedef std::vector OrderVector; OrderVector accepts_; OrderVector rejects_; OrderVector fills_; OrderVector cancels_; OrderVector cancel_rejects_; OrderVector replaces_; OrderVector replace_rejects_; }; BOOST_AUTO_TEST_CASE(TestOrderCallbacks) { SimpleOrder order0(false, 3250, 100); SimpleOrder order1(true, 3250, 800); SimpleOrder order2(false, 3230, 0); SimpleOrder order3(false, 3240, 200); SimpleOrder order4(true, 3250, 600); OrderCbListener listener; TypedOrderBook order_book; order_book.set_order_listener(&listener); // Add order, should be accepted order_book.add(&order0); BOOST_CHECK_EQUAL(1, listener.accepts_.size()); listener.reset(); // Add matching order, should be accepted, followed by a fill order_book.add(&order1); BOOST_CHECK_EQUAL(1, listener.accepts_.size()); BOOST_CHECK_EQUAL(1, listener.fills_.size()); listener.reset(); // Add invalid order, should be rejected order_book.add(&order2); BOOST_CHECK_EQUAL(1, listener.rejects_.size()); listener.reset(); // Cancel only valid order, should be cancelled order_book.cancel(&order1); BOOST_CHECK_EQUAL(1, listener.cancels_.size()); listener.reset(); // Cancel filled order, should be rejected order_book.cancel(&order0); BOOST_CHECK_EQUAL(1, listener.cancel_rejects_.size()); listener.reset(); // Add a new order and replace it, should be replaced order_book.add(&order3); order_book.replace(&order3, 0, 3250); BOOST_CHECK_EQUAL(1, listener.accepts_.size()); BOOST_CHECK_EQUAL(1, listener.replaces_.size()); listener.reset(); // Add matching order, should be accepted, followed by a fill order_book.add(&order4); BOOST_CHECK_EQUAL(1, listener.accepts_.size()); BOOST_CHECK_EQUAL(1, listener.fills_.size()); listener.reset(); // Replace matched order, with too large of a size decrease, replace // should be rejected order_book.replace(&order3, -500); BOOST_CHECK_EQUAL(0, listener.replaces_.size()); BOOST_CHECK_EQUAL(1, listener.replace_rejects_.size()); } class OrderBookCbListener : public OrderBookListener { public: virtual void on_order_book_change(const TypedOrderBook* book) { changes_.push_back(book); } void reset() { changes_.clear(); } typedef std::vector OrderBookVector; OrderBookVector changes_; }; BOOST_AUTO_TEST_CASE(TestOrderBookCallbacks) { SimpleOrder order0(false, 3250, 100); SimpleOrder order1(true, 3250, 800); SimpleOrder order2(false, 3230, 0); SimpleOrder order3(false, 3240, 200); SimpleOrder order4(true, 3250, 600); OrderBookCbListener listener; OrderBook order_book; order_book.set_order_book_listener(&listener); // Add order, should be accepted order_book.add(&order0); BOOST_CHECK_EQUAL(1, listener.changes_.size()); listener.reset(); // Add matching order, should be accepted, followed by a fill order_book.add(&order1); BOOST_CHECK_EQUAL(1, listener.changes_.size()); listener.reset(); // Add invalid order, should be rejected order_book.add(&order2); BOOST_CHECK_EQUAL(0, listener.changes_.size()); // NO CHANGE listener.reset(); // Cancel only valid order, should be cancelled order_book.cancel(&order1); BOOST_CHECK_EQUAL(1, listener.changes_.size()); listener.reset(); // Cancel filled order, should be rejected order_book.cancel(&order0); BOOST_CHECK_EQUAL(0, listener.changes_.size()); // NO CHANGE listener.reset(); // Add a new order and replace it, should be replaced order_book.add(&order3); order_book.replace(&order3, 0, 3250); BOOST_CHECK_EQUAL(2, listener.changes_.size()); listener.reset(); // Add matching order, should be accepted, followed by a fill order_book.add(&order4); BOOST_CHECK_EQUAL(1, listener.changes_.size()); listener.reset(); // Replace matched order, with too large of a size decrease, replace // should be rejected order_book.replace(&order3, -500); BOOST_CHECK_EQUAL(0, listener.changes_.size()); // NO CHANGE } class DepthCbListener : public TypedDepthOrderBook::TypedDepthListener { public: virtual void on_depth_change(const TypedDepthOrderBook* book, const DepthTracker* ) // depth { changes_.push_back(book); } void reset() { changes_.clear(); } typedef std::vector OrderBooks; OrderBooks changes_; }; BOOST_AUTO_TEST_CASE(TestDepthCallbacks) { SimpleOrder buy0(true, 3250, 100); SimpleOrder buy1(true, 3249, 800); SimpleOrder buy2(true, 3248, 300); SimpleOrder buy3(true, 3247, 200); SimpleOrder buy4(true, 3246, 600); SimpleOrder buy5(true, 3245, 300); SimpleOrder buy6(true, 3244, 100); SimpleOrder sell0(false, 3250, 300); SimpleOrder sell1(false, 3251, 200); SimpleOrder sell2(false, 3252, 200); SimpleOrder sell3(false, 3253, 400); SimpleOrder sell4(false, 3254, 300); SimpleOrder sell5(false, 3255, 100); SimpleOrder sell6(false, 3255, 100); DepthCbListener listener; TypedDepthOrderBook order_book; order_book.set_depth_listener(&listener); // Add buy orders, should be accepted order_book.add(&buy0); order_book.add(&buy1); order_book.add(&buy2); order_book.add(&buy3); order_book.add(&buy4); BOOST_CHECK_EQUAL(5, listener.changes_.size()); listener.reset(); // Add buy orders past end, should be accepted, but not affect depth order_book.add(&buy5); order_book.add(&buy6); BOOST_CHECK_EQUAL(0, listener.changes_.size()); listener.reset(); // Add sell orders, should be accepted and affect depth order_book.add(&sell5); order_book.add(&sell4); order_book.add(&sell3); order_book.add(&sell2); order_book.add(&sell1); order_book.add(&sell0); BOOST_CHECK_EQUAL(6, listener.changes_.size()); listener.reset(); // Add sell order past end, should be accepted, but not affect depth order_book.add(&sell6); BOOST_CHECK_EQUAL(0, listener.changes_.size()); listener.reset(); } class BboCbListener : public TypedDepthOrderBook::TypedBboListener { public: virtual void on_bbo_change(const TypedDepthOrderBook* book, const DepthTracker* ) // depth { changes_.push_back(book); } void reset() { changes_.clear(); } typedef std::vector OrderBooks; OrderBooks changes_; }; BOOST_AUTO_TEST_CASE(TestBboCallbacks) { SimpleOrder buy0(true, 3250, 100); SimpleOrder buy1(true, 3249, 800); SimpleOrder buy2(true, 3248, 300); SimpleOrder buy3(true, 3247, 200); SimpleOrder buy4(true, 3246, 600); SimpleOrder buy5(true, 3245, 300); SimpleOrder buy6(true, 3244, 100); SimpleOrder sell0(false, 3250, 300); SimpleOrder sell1(false, 3251, 200); SimpleOrder sell2(false, 3252, 200); SimpleOrder sell3(false, 3253, 400); SimpleOrder sell4(false, 3254, 300); SimpleOrder sell5(false, 3255, 100); SimpleOrder sell6(false, 3255, 100); BboCbListener listener; TypedDepthOrderBook order_book; order_book.set_bbo_listener(&listener); // Add buy orders, should be accepted order_book.add(&buy0); BOOST_CHECK_EQUAL(1, listener.changes_.size()); listener.reset(); order_book.add(&buy1); BOOST_CHECK_EQUAL(0, listener.changes_.size()); listener.reset(); order_book.add(&buy2); BOOST_CHECK_EQUAL(0, listener.changes_.size()); listener.reset(); order_book.add(&buy3); BOOST_CHECK_EQUAL(0, listener.changes_.size()); listener.reset(); order_book.add(&buy4); BOOST_CHECK_EQUAL(0, listener.changes_.size()); listener.reset(); // Add buy orders past end, should be accepted, but not affect depth order_book.add(&buy5); BOOST_CHECK_EQUAL(0, listener.changes_.size()); listener.reset(); order_book.add(&buy6); BOOST_CHECK_EQUAL(0, listener.changes_.size()); listener.reset(); // Add sell orders, should be accepted and affect bbo order_book.add(&sell2); BOOST_CHECK_EQUAL(1, listener.changes_.size()); listener.reset(); order_book.add(&sell1); BOOST_CHECK_EQUAL(1, listener.changes_.size()); listener.reset(); order_book.add(&sell0); BOOST_CHECK_EQUAL(1, listener.changes_.size()); listener.reset(); // Add sell orders worse than best bid, should not effect bbo order_book.add(&sell5); BOOST_CHECK_EQUAL(0, listener.changes_.size()); listener.reset(); order_book.add(&sell4); BOOST_CHECK_EQUAL(0, listener.changes_.size()); listener.reset(); order_book.add(&sell3); BOOST_CHECK_EQUAL(0, listener.changes_.size()); listener.reset(); // Add sell order past end, should be accepted, but not affect depth order_book.add(&sell6); BOOST_CHECK_EQUAL(0, listener.changes_.size()); listener.reset(); } BOOST_AUTO_TEST_CASE(TestTradeCallbacks) { SimpleOrder order0(false, 3250, 100); SimpleOrder order1(true, 3250, 800); SimpleOrder order2(false, 3230, 0); SimpleOrder order3(false, 3240, 200); SimpleOrder order4(true, 3250, 600); TradeCbListener listener; TypedOrderBook order_book; order_book.set_trade_listener(&listener); // Add order, should be accepted order_book.add(&order0); BOOST_CHECK_EQUAL(0, listener.quantities_.size()); listener.reset(); // Add matching order, should result in a trade order_book.add(&order1); BOOST_CHECK_EQUAL(1, listener.quantities_.size()); BOOST_CHECK_EQUAL(1, listener.costs_.size()); BOOST_CHECK_EQUAL(100, listener.quantities_[0]); BOOST_CHECK_EQUAL(100 * 3250, listener.costs_[0]); listener.reset(); // Add invalid order, should be rejected order_book.add(&order2); BOOST_CHECK_EQUAL(0, listener.quantities_.size()); listener.reset(); // Cancel only valid order, should be cancelled order_book.cancel(&order1); BOOST_CHECK_EQUAL(0, listener.quantities_.size()); listener.reset(); // Cancel filled order, should be rejected order_book.cancel(&order0); BOOST_CHECK_EQUAL(0, listener.quantities_.size()); listener.reset(); // Add a new order and replace it, should be replaced order_book.add(&order3); order_book.replace(&order3, 0, 3250); BOOST_CHECK_EQUAL(0, listener.quantities_.size()); listener.reset(); // Add matching order, should be accepted, followed by a fill order_book.add(&order4); BOOST_CHECK_EQUAL(1, listener.quantities_.size()); BOOST_CHECK_EQUAL(1, listener.costs_.size()); listener.reset(); // Replace matched order, with too large of a size decrease, replace // should be rejected order_book.replace(&order3, -500); BOOST_CHECK_EQUAL(0, listener.quantities_.size()); } } // namespace liquibook ================================================ FILE: test/unit/ut_main.cpp ================================================ // Copyright (c) 2017, Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #define BOOST_TEST_MODULE LiquibookTest #include ================================================ FILE: test/unit/ut_market_price.cpp ================================================ // Copyright (c) 2012 - 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #define BOOST_TEST_NO_MAIN LiquibookTest #include #include "ut_utils.h" #include "changed_checker.h" #include #include namespace liquibook { using book::DepthLevel; using book::OrderBook; using book::OrderTracker; using simple::SimpleOrder; namespace { const bool sideBuy = true; const bool sideSell = false; const Price prcMkt = 0; const Price prc0 = 9900; const Quantity q100 = 100; const bool expectMatch = true; const bool expectNoMatch = false; const bool expectComplete = true; const bool expectNoComplete = false; } typedef OrderTracker SimpleTracker; typedef test::ChangedChecker<5> ChangedChecker; typedef FillCheck SimpleFillCheck; BOOST_AUTO_TEST_CASE(TestNoMktToMktWithoutPreviousTrade) { SimpleOrderBook order_book; SimpleOrder order0(sideBuy, prcMkt, q100); SimpleOrder order1(sideSell, prcMkt, q100); // Check that no market-to-market trade happens without a prior trade BOOST_CHECK(add_and_verify(order_book, &order0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &order1, expectNoMatch)); SimpleFillCheck fc0(&order0, 0, 0); SimpleFillCheck fc1(&order1, 0, 0); BOOST_CHECK_EQUAL(0U, order_book.market_price()); } BOOST_AUTO_TEST_CASE(TestTradeSetsMarketPrice) { SimpleOrderBook order_book; SimpleOrder order0(sideBuy, prcMkt, q100); SimpleOrder order1(sideSell, prcMkt, q100); // Check that no market-to-market trade happens without a prior trade BOOST_CHECK(add_and_verify(order_book, &order0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &order1, expectNoMatch)); BOOST_CHECK_EQUAL(0U, order_book.market_price()); SimpleOrder order2(sideBuy, prc0, q100); // Scope for fill checks { SimpleFillCheck fc0(&order0, 0, 0); SimpleFillCheck fc1(&order1, q100, q100 * prc0); SimpleFillCheck fc2(&order2, q100, q100 * prc0); BOOST_CHECK(add_and_verify(order_book, &order2, expectMatch, expectComplete)); } BOOST_CHECK_EQUAL(prc0, order_book.market_price()); SimpleOrder order3(sideSell, prcMkt, q100); // Scope for fill checks { SimpleFillCheck fc0(&order0, q100, q100 * prc0); SimpleFillCheck fc3(&order3, q100, q100 * prc0); BOOST_CHECK(add_and_verify(order_book, &order3, expectMatch, expectComplete)); } BOOST_CHECK_EQUAL(prc0, order_book.market_price()); } BOOST_AUTO_TEST_CASE(TestExplicitlySettingMarketPriceAllowsMarketToMarketTrades) { SimpleOrderBook order_book; SimpleOrder order0(sideBuy, prcMkt, q100); SimpleOrder order1(sideSell, prcMkt, q100); // Check that no market-to-market trade happens without a prior trade BOOST_CHECK(add_and_verify(order_book, &order0, expectNoMatch)); BOOST_CHECK(add_and_verify(order_book, &order1, expectNoMatch)); // IF WE MAKE THE MANUAL SETTING OF MARKET PRICE RETROACTIVE // FIX THIS TEST TO REFLECT THAT // Scope for fill checks { SimpleFillCheck fc0(&order0, 0, 0); SimpleFillCheck fc1(&order1, 0, 0); order_book.set_market_price(prc0); } SimpleOrder order2(sideBuy, prcMkt, q100); SimpleOrder order3(sideSell, prcMkt, q100); // Scope for fill checks { SimpleFillCheck fc0(&order0, q100, q100 * prc0); SimpleFillCheck fc1(&order1, q100, q100 * prc0); SimpleFillCheck fc2(&order2, q100, q100 * prc0); SimpleFillCheck fc3(&order3, q100, q100 * prc0); BOOST_CHECK(add_and_verify(order_book, &order2, expectMatch, expectComplete)); BOOST_CHECK(add_and_verify(order_book, &order3, expectMatch, expectComplete)); } } } // namespace ================================================ FILE: test/unit/ut_order_book.cpp ================================================ // Copyright (c) 2012 - 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #define BOOST_TEST_NO_MAIN LiquibookTest #include #include "ut_utils.h" #include "changed_checker.h" #include #include namespace liquibook { using book::DepthLevel; using book::OrderBook; using book::OrderTracker; using simple::SimpleOrder; typedef OrderTracker SimpleTracker; typedef test::ChangedChecker<5> ChangedChecker; typedef FillCheck SimpleFillCheck; BOOST_AUTO_TEST_CASE(TestBidsMultimapSortCorrect) { SimpleOrderBook::Bids bids; SimpleOrder order0(true, 1250, 100); SimpleOrder order1(true, 1255, 100); SimpleOrder order2(true, 1240, 100); SimpleOrder order3(true, MARKET_ORDER_PRICE, 100); SimpleOrder order4(true, 1245, 100); // Insert out of price order bids.insert(std::make_pair(book::ComparablePrice(true, order0.price()), SimpleTracker(&order0))); bids.insert(std::make_pair(book::ComparablePrice(true, order1.price()), SimpleTracker(&order1))); bids.insert(std::make_pair(book::ComparablePrice(true, order2.price()), SimpleTracker(&order2))); bids.insert(std::make_pair(book::ComparablePrice(true, order3.price()), SimpleTracker(&order3))); bids.insert(std::make_pair(book::ComparablePrice(true, order4.price()), SimpleTracker(&order4))); // Should access in price order SimpleOrder* expected_order[] = { &order3, &order1, &order0, &order4, &order2 }; SimpleOrderBook::Bids::iterator bid; int index = 0; for (bid = bids.begin(); bid != bids.end(); ++bid, ++index) { BOOST_CHECK_EQUAL(expected_order[index]->price(), bid->first); BOOST_CHECK_EQUAL(expected_order[index], bid->second.ptr()); } // Should be able to search and find BOOST_CHECK((bids.upper_bound(book::ComparablePrice(true, 1245)))->second.ptr()->price() == 1240); BOOST_CHECK((bids.lower_bound(book::ComparablePrice(true, 1245)))->second.ptr()->price() == 1245); } BOOST_AUTO_TEST_CASE(TestAsksMultimapSortCorrect) { SimpleOrderBook::Asks asks; SimpleOrder order0(false, 3250, 100); SimpleOrder order1(false, 3235, 800); SimpleOrder order2(false, 3230, 200); SimpleOrder order3(false, 0, 200); SimpleOrder order4(false, 3245, 100); SimpleOrder order5(false, 3265, 200); // Insert out of price order asks.insert(std::make_pair(book::ComparablePrice(false, order0.price()), SimpleTracker(&order0))); asks.insert(std::make_pair(book::ComparablePrice(false, order1.price()), SimpleTracker(&order1))); asks.insert(std::make_pair(book::ComparablePrice(false, order2.price()), SimpleTracker(&order2))); asks.insert(std::make_pair(book::ComparablePrice(false, MARKET_ORDER_PRICE), SimpleTracker(&order3))); asks.insert(std::make_pair(book::ComparablePrice(false, order4.price()), SimpleTracker(&order4))); asks.insert(std::make_pair(book::ComparablePrice(false, order5.price()), SimpleTracker(&order5))); // Should access in price order SimpleOrder* expected_order[] = { &order3, &order2, &order1, &order4, &order0, &order5 }; SimpleOrderBook::Asks::iterator ask; int index = 0; for (ask = asks.begin(); ask != asks.end(); ++ask, ++index) { BOOST_CHECK_EQUAL(expected_order[index]->price(), ask->first); BOOST_CHECK_EQUAL(expected_order[index], ask->second.ptr()); } BOOST_CHECK((asks.upper_bound(book::ComparablePrice(false, 3235)))->second.ptr()->price() == 3245); BOOST_CHECK((asks.lower_bound(book::ComparablePrice(false, 3235)))->second.ptr()->price() == 3235); } BOOST_AUTO_TEST_CASE(TestAddCompleteBid) { SimpleOrderBook order_book; SimpleOrder ask1(false, 1252, 100); SimpleOrder ask0(false, 1251, 100); SimpleOrder bid1(true, 1251, 100); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Match - complete { SimpleFillCheck fc1(&bid1, 100, 125100); SimpleFillCheck fc2(&ask0, 100, 125100); BOOST_CHECK(add_and_verify(order_book, &bid1, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); } namespace { bool isBuy = true; bool isSell = false; bool expectMatch = true; bool expectNoMatch = false; bool expectComplete = true; bool expectNoComplete = false; } BOOST_AUTO_TEST_CASE(TestAddCompleteAsk) { SimpleOrderBook order_book; SimpleOrder ask0(isSell, 1251, 100); SimpleOrder ask1(isSell, 1250, 100); SimpleOrder bid0(isBuy, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); // Match - complete //Scope for fill checks { SimpleFillCheck fc1(&ask1, 100, 125000); SimpleFillCheck fc2(&bid0, 100, 125000); BOOST_CHECK(add_and_verify(order_book, &ask1, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); // Verify sizes BOOST_CHECK_EQUAL(0, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestAddMultiMatchBid) { SimpleOrderBook order_book; SimpleOrder ask1(false, 1252, 100); SimpleOrder ask0(false, 1251, 300); SimpleOrder ask2(false, 1251, 200); SimpleOrder bid1(true, 1251, 500); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 2, 500)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Match - complete { SimpleFillCheck fc1(&bid1, 500, 1251 * 500); SimpleFillCheck fc2(&ask2, 200, 1251 * 200); SimpleFillCheck fc3(&ask0, 300, 1251 * 300); BOOST_CHECK(add_and_verify(order_book, &bid1, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify remaining BOOST_CHECK_EQUAL(&ask1, order_book.asks().begin()->second.ptr()); } BOOST_AUTO_TEST_CASE(TestAddMultiMatchAsk) { SimpleOrderBook order_book; SimpleOrder ask1(false, 9252, 100); SimpleOrder ask0(false, 9251, 300); SimpleOrder ask2(false, 9251, 200); SimpleOrder ask3(false, 9250, 600); SimpleOrder bid0(true, 9250, 100); SimpleOrder bid1(true, 9250, 500); SimpleOrder bid2(true, 9248, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); // Verify sizes BOOST_CHECK_EQUAL(3, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(9250, 2, 600)); BOOST_CHECK(dc.verify_bid(9248, 1, 100)); BOOST_CHECK(dc.verify_ask(9251, 2, 500)); BOOST_CHECK(dc.verify_ask(9252, 1, 100)); // Match - complete { SimpleFillCheck fc1(&ask3, 600, 9250 * 600); SimpleFillCheck fc2(&bid0, 100, 9250 * 100); SimpleFillCheck fc3(&bid1, 500, 9250 * 500); BOOST_CHECK(add_and_verify(order_book, &ask3, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(9248, 1, 100)); BOOST_CHECK(dc.verify_ask(9251, 2, 500)); BOOST_CHECK(dc.verify_ask(9252, 1, 100)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify remaining BOOST_CHECK_EQUAL(&bid2, order_book.bids().begin()->second.ptr()); } BOOST_AUTO_TEST_CASE(TestAddPartialMatchBid) { SimpleOrderBook order_book; SimpleOrder ask0(false, 7253, 300); SimpleOrder ask1(false, 7252, 100); SimpleOrder ask2(false, 7251, 200); SimpleOrder bid1(true, 7251, 350); SimpleOrder bid0(true, 7250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(7250, 1, 100)); BOOST_CHECK(dc.verify_ask(7251, 1, 200)); BOOST_CHECK(dc.verify_ask(7252, 1, 100)); BOOST_CHECK(dc.verify_ask(7253, 1, 300)); // Match - partial { SimpleFillCheck fc1(&bid1, 200, 7251 * 200); SimpleFillCheck fc2(&ask2, 200, 7251 * 200); BOOST_CHECK(add_and_verify(order_book, &bid1, true, false)); } // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(7251, 1, 150)); BOOST_CHECK(dc.verify_bid(7250, 1, 100)); BOOST_CHECK(dc.verify_ask(7252, 1, 100)); BOOST_CHECK(dc.verify_ask(7253, 1, 300)); // Verify remaining BOOST_CHECK_EQUAL(&ask1, order_book.asks().begin()->second.ptr()); BOOST_CHECK_EQUAL(&bid1, order_book.bids().begin()->second.ptr()); } BOOST_AUTO_TEST_CASE(TestAddPartialMatchAsk) { SimpleOrderBook order_book; SimpleOrder ask0(false, 1253, 300); SimpleOrder ask1(false, 1251, 400); SimpleOrder bid1(true, 1251, 350); SimpleOrder bid0(true, 1250, 100); SimpleOrder bid2(true, 1250, 200); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); // Verify sizes BOOST_CHECK_EQUAL(3, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1251, 1, 350)); BOOST_CHECK(dc.verify_bid(1250, 2, 300)); BOOST_CHECK(dc.verify_ask(1253, 1, 300)); // Match - partial { SimpleFillCheck fc1(&ask1, 350, 1251 * 350); SimpleFillCheck fc2(&bid1, 350, 1251 * 350); BOOST_CHECK(add_and_verify(order_book, &ask1, true, false)); } // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 2, 300)); BOOST_CHECK(dc.verify_ask(1251, 1, 50)); BOOST_CHECK(dc.verify_ask(1253, 1, 300)); // Verify remaining BOOST_CHECK_EQUAL(&bid0, order_book.bids().begin()->second.ptr()); BOOST_CHECK_EQUAL(&ask1, order_book.asks().begin()->second.ptr()); } BOOST_AUTO_TEST_CASE(TestAddMultiPartialMatchBid) { SimpleOrderBook order_book; SimpleOrder ask1(false, 1252, 100); SimpleOrder ask2(false, 1251, 200); SimpleOrder ask0(false, 1251, 300); SimpleOrder bid1(true, 1251, 750); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 2, 500)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Match - partial { SimpleFillCheck fc1(&bid1, 500, 1251 * 500); SimpleFillCheck fc2(&ask0, 300, 1251 * 300); SimpleFillCheck fc3(&ask2, 200, 1251 * 200); BOOST_CHECK(add_and_verify(order_book, &bid1, true, false)); } // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 250)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Verify remaining BOOST_CHECK_EQUAL(&ask1, order_book.asks().begin()->second.ptr()); BOOST_CHECK_EQUAL(&bid1, order_book.bids().begin()->second.ptr()); } BOOST_AUTO_TEST_CASE(TestAddMultiPartialMatchAsk) { SimpleOrderBook order_book; SimpleOrder ask0(false, 1253, 300); SimpleOrder ask1(false, 1251, 700); SimpleOrder bid1(true, 1251, 370); SimpleOrder bid2(true, 1251, 200); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); // Verify sizes BOOST_CHECK_EQUAL(3, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1251, 2, 570)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1253, 1, 300)); // Match - partial { SimpleFillCheck fc1(&ask1, 570, 1251 * 570); SimpleFillCheck fc2(&bid1, 370, 1251 * 370); SimpleFillCheck fc3(&bid2, 200, 1251 * 200); BOOST_CHECK(add_and_verify(order_book, &ask1, true, false)); } // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 130)); BOOST_CHECK(dc.verify_ask(1253, 1, 300)); // Verify remaining BOOST_CHECK_EQUAL(&bid0, order_book.bids().begin()->second.ptr()); BOOST_CHECK_EQUAL(100, order_book.bids().begin()->second.open_qty()); BOOST_CHECK_EQUAL(&ask1, order_book.asks().begin()->second.ptr()); BOOST_CHECK_EQUAL(130, order_book.asks().begin()->second.open_qty()); } BOOST_AUTO_TEST_CASE(TestRepeatMatchBid) { SimpleOrderBook order_book; SimpleOrder ask3(false, 1251, 400); SimpleOrder ask2(false, 1251, 200); SimpleOrder ask1(false, 1251, 300); SimpleOrder ask0(false, 1251, 100); SimpleOrder bid1(true, 1251, 900); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1251, 1, 900)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); // Match - repeated { SimpleFillCheck fc1(&bid1, 100, 125100); SimpleFillCheck fc2(&ask0, 100, 125100); BOOST_CHECK(add_and_verify(order_book, &ask0, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 800)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); { SimpleFillCheck fc1(&bid1, 300, 1251 * 300); SimpleFillCheck fc2(&ask1, 300, 1251 * 300); BOOST_CHECK(add_and_verify(order_book, &ask1, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 500)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); { SimpleFillCheck fc1(&bid1, 200, 1251 * 200); SimpleFillCheck fc2(&ask2, 200, 1251 * 200); BOOST_CHECK(add_and_verify(order_book, &ask2, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 300)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); { SimpleFillCheck fc1(&bid1, 300, 1251 * 300); SimpleFillCheck fc2(&ask3, 300, 1251 * 300); BOOST_CHECK(add_and_verify(order_book, &ask3, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestRepeatMatchAsk) { SimpleOrderBook order_book; SimpleOrder ask0(false, 1252, 100); SimpleOrder ask1(false, 1251, 900); SimpleOrder bid0(true, 1251, 100); SimpleOrder bid1(true, 1251, 300); SimpleOrder bid2(true, 1251, 200); SimpleOrder bid3(true, 1251, 400); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_ask(1251, 1, 900)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); BOOST_CHECK_EQUAL(&ask1, order_book.asks().begin()->second.ptr()); // Match - repeated { SimpleFillCheck fc1(&ask1, 100, 125100); SimpleFillCheck fc2(&bid0, 100, 125100); BOOST_CHECK(add_and_verify(order_book, &bid0, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_ask(1251, 1, 800)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); { SimpleFillCheck fc1(&ask1, 300, 1251 * 300); SimpleFillCheck fc2(&bid1, 300, 1251 * 300); BOOST_CHECK(add_and_verify(order_book, &bid1, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_ask(1251, 1, 500)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); { SimpleFillCheck fc1(&ask1, 200, 1251 * 200); SimpleFillCheck fc2(&bid2, 200, 1251 * 200); BOOST_CHECK(add_and_verify(order_book, &bid2, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_ask(1251, 1, 300)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); { SimpleFillCheck fc1(&ask1, 300, 1251 * 300); SimpleFillCheck fc2(&bid3, 300, 1251 * 300); BOOST_CHECK(add_and_verify(order_book, &bid3, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestAddMarketOrderBid) { SimpleOrderBook order_book; SimpleOrder ask1(false, 1252, 100); SimpleOrder ask0(false, 1251, 100); SimpleOrder bid1(true, 0, 100); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Match - complete { SimpleFillCheck fc1(&bid1, 100, 125100); SimpleFillCheck fc2(&ask0, 100, 125100); BOOST_CHECK(add_and_verify(order_book, &bid1, true, true)); } // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); } BOOST_AUTO_TEST_CASE(TestAddMarketOrderAsk) { SimpleOrderBook order_book; SimpleOrder ask0(false, 1252, 100); SimpleOrder ask1(false, 0, 100); SimpleOrder bid1(true, 1251, 100); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1251, 1, 100)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Match - complete { //ASSERT_NO_THROW( SimpleFillCheck fc1(&bid1, 100, 125100); SimpleFillCheck fc2(&ask1, 100, 125100); BOOST_CHECK(add_and_verify(order_book, &ask1, true, true)); // ); } // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); } BOOST_AUTO_TEST_CASE(TestAddMarketOrderBidMultipleMatch) { SimpleOrderBook order_book; SimpleOrder ask1(false, 12520, 300); SimpleOrder ask0(false, 12510, 200); SimpleOrder bid1(true, 0, 500); SimpleOrder bid0(true, 12500, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(12500, 1, 100)); BOOST_CHECK(dc.verify_ask(12510, 1, 200)); BOOST_CHECK(dc.verify_ask(12520, 1, 300)); // Match - complete { SimpleFillCheck fc1(&bid1, 500, 12510 * 200 + 12520 * 300); SimpleFillCheck fc2(&ask0, 200, 12510 * 200); SimpleFillCheck fc3(&ask1, 300, 12520 * 300); BOOST_CHECK(add_and_verify(order_book, &bid1, true, true)); } // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(0, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(12500, 1, 100)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); } BOOST_AUTO_TEST_CASE(TestAddMarketOrderAskMultipleMatch) { SimpleOrderBook order_book; SimpleOrder ask0(false, 12520, 100); SimpleOrder ask1(false, 0, 600); SimpleOrder bid1(true, 12510, 200); SimpleOrder bid0(true, 12500, 400); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(12510, 1, 200)); BOOST_CHECK(dc.verify_bid(12500, 1, 400)); BOOST_CHECK(dc.verify_ask(12520, 1, 100)); // Match - complete { SimpleFillCheck fc1(&bid0, 400, 12500 * 400); SimpleFillCheck fc2(&bid1, 200, 12510 * 200); SimpleFillCheck fc3(&ask1, 600, 12500 * 400 + 12510 * 200); BOOST_CHECK(add_and_verify(order_book, &ask1, true, true)); } // Verify sizes BOOST_CHECK_EQUAL(0, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); BOOST_CHECK(dc.verify_ask(12520, 1, 100)); } BOOST_AUTO_TEST_CASE(TestMatchMarketOrderBid) { SimpleOrderBook order_book; SimpleOrder ask0(false, 1253, 100); SimpleOrder bid1(true, 0, 100); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(0, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); // Match - complete { SimpleFillCheck fc1(&bid1, 100, 125300); SimpleFillCheck fc2(&ask0, 100, 125300); BOOST_CHECK(add_and_verify(order_book, &ask0, true, true)); } // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(0, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); } BOOST_AUTO_TEST_CASE(TestMatchMarketOrderAsk) { SimpleOrderBook order_book; SimpleOrder ask0(false, 1252, 100); SimpleOrder ask1(false, 0, 100); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify sizes BOOST_CHECK_EQUAL(0, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); // Match - complete { SimpleFillCheck fc1(&bid0, 100, 125000); SimpleFillCheck fc2(&ask1, 100, 125000); BOOST_CHECK(add_and_verify(order_book, &bid0, true, true)); } // Verify sizes BOOST_CHECK_EQUAL(0, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); } BOOST_AUTO_TEST_CASE(TestMatchMultipleMarketOrderBid) { SimpleOrderBook order_book; SimpleOrder ask0(false, 1253, 400); SimpleOrder bid1(true, 0, 100); SimpleOrder bid2(true, 0, 200); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); // Verify sizes BOOST_CHECK_EQUAL(3, order_book.bids().size()); BOOST_CHECK_EQUAL(0, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); // Match - complete { SimpleFillCheck fc1(&bid1, 100, 1253 * 100); SimpleFillCheck fc2(&bid2, 200, 1253 * 200); SimpleFillCheck fc3(&ask0, 300, 1253 * 300); BOOST_CHECK(add_and_verify(order_book, &ask0, true, false)); } // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_ask(1253, 1, 100)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); } BOOST_AUTO_TEST_CASE(TestMatchMultipleMarketOrderAsk) { SimpleOrderBook order_book; SimpleOrder ask0(false, 1252, 100); SimpleOrder ask2(false, 0, 400); SimpleOrder ask1(false, 0, 100); SimpleOrder bid0(true, 1250, 300); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); // Verify sizes BOOST_CHECK_EQUAL(0, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); // Match - partiaL { SimpleFillCheck fc1(&bid0, 300, 1250 * 300); SimpleFillCheck fc2(&ask1, 100, 1250 * 100); SimpleFillCheck fc3(&ask2, 200, 1250 * 200); BOOST_CHECK(add_and_verify(order_book, &bid0, true, true)); } // Verify sizes BOOST_CHECK_EQUAL(0, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); } BOOST_AUTO_TEST_CASE(TestCancelBid) { SimpleOrderBook order_book; SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 100); SimpleOrder ask0(false, 1251, 100); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(3, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 2, 200)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Cancel bid BOOST_CHECK(cancel_and_verify(order_book, &bid0, simple::os_cancelled)); // Cancel correctness BOOST_CHECK(cancel_and_verify(order_book, &ask1, simple::os_cancelled)); BOOST_CHECK(cancel_and_verify(order_book, &ask1, simple::os_cancelled)); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(0, 0, 0)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Verify sizes BOOST_CHECK_EQUAL(0, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestCancelAskAndMatch) { SimpleOrderBook order_book; SimpleOrder ask1(false, 1252, 100); SimpleOrder ask0(false, 1251, 100); SimpleOrder bid2(true, 1252, 100); SimpleOrder bid0(true, 1250, 100); SimpleOrder bid1(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify sizes BOOST_CHECK_EQUAL(2, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 2, 200)); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); // Cancel bid BOOST_CHECK(cancel_and_verify(order_book, &ask0, simple::os_cancelled)); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 2, 200)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); // Match - partiaL { SimpleFillCheck fc1(&bid2, 100, 1252 * 100); SimpleFillCheck fc2(&ask1, 100, 1252 * 100); BOOST_CHECK(add_and_verify(order_book, &bid2, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 2, 200)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); // Cancel bid BOOST_CHECK(cancel_and_verify(order_book, &bid0, simple::os_cancelled)); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(0, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestCancelBidFail) { SimpleOrderBook order_book; SimpleOrder ask0(false, 1251, 100); SimpleOrder ask1(false, 1250, 100); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); // Match - complete { SimpleFillCheck fc1(&ask1, 100, 125000); SimpleFillCheck fc2(&bid0, 100, 125000); BOOST_CHECK(add_and_verify(order_book, &ask1, true, true)); } // Verify sizes BOOST_CHECK_EQUAL(0, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); // Cancel a filled order BOOST_CHECK(cancel_and_verify(order_book, &bid0, simple::os_complete)); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); } BOOST_AUTO_TEST_CASE(TestCancelAskFail) { SimpleOrderBook order_book; SimpleOrder ask1(false, 1252, 100); SimpleOrder ask0(false, 1251, 100); SimpleOrder bid1(true, 1251, 100); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_ask(1251, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); // Match - complete { SimpleFillCheck fc1(&bid1, 100, 125100); SimpleFillCheck fc2(&ask0, 100, 125100); BOOST_CHECK(add_and_verify(order_book, &bid1, true, true)); } // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); // Cancel a filled order BOOST_CHECK(cancel_and_verify(order_book, &ask0, simple::os_complete)); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); } BOOST_AUTO_TEST_CASE(TestCancelBidRestore) { SimpleOrderBook order_book; SimpleOrder ask10(false, 1258, 600); SimpleOrder ask9(false, 1257, 700); SimpleOrder ask8(false, 1256, 100); SimpleOrder ask7(false, 1256, 100); SimpleOrder ask6(false, 1255, 500); SimpleOrder ask5(false, 1255, 200); SimpleOrder ask4(false, 1254, 300); SimpleOrder ask3(false, 1252, 200); SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 400); SimpleOrder ask0(false, 1250, 500); SimpleOrder bid0(true, 1249, 100); SimpleOrder bid1(true, 1249, 200); SimpleOrder bid2(true, 1249, 200); SimpleOrder bid3(true, 1248, 400); SimpleOrder bid4(true, 1246, 600); SimpleOrder bid5(true, 1246, 500); SimpleOrder bid6(true, 1245, 200); SimpleOrder bid7(true, 1245, 100); SimpleOrder bid8(true, 1245, 200); SimpleOrder bid9(true, 1244, 700); SimpleOrder bid10(true, 1244, 300); SimpleOrder bid11(true, 1242, 300); SimpleOrder bid12(true, 1241, 400); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &ask3, false)); BOOST_CHECK(add_and_verify(order_book, &ask4, false)); BOOST_CHECK(add_and_verify(order_book, &ask5, false)); BOOST_CHECK(add_and_verify(order_book, &ask6, false)); BOOST_CHECK(add_and_verify(order_book, &ask7, false)); BOOST_CHECK(add_and_verify(order_book, &ask8, false)); BOOST_CHECK(add_and_verify(order_book, &ask9, false)); BOOST_CHECK(add_and_verify(order_book, &ask10, false)); BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); BOOST_CHECK(add_and_verify(order_book, &bid3, false)); BOOST_CHECK(add_and_verify(order_book, &bid4, false)); BOOST_CHECK(add_and_verify(order_book, &bid5, false)); BOOST_CHECK(add_and_verify(order_book, &bid6, false)); BOOST_CHECK(add_and_verify(order_book, &bid7, false)); BOOST_CHECK(add_and_verify(order_book, &bid8, false)); BOOST_CHECK(add_and_verify(order_book, &bid9, false)); BOOST_CHECK(add_and_verify(order_book, &bid10, false)); BOOST_CHECK(add_and_verify(order_book, &bid11, false)); BOOST_CHECK(add_and_verify(order_book, &bid12, false)); // Verify sizes BOOST_CHECK_EQUAL(13, order_book.bids().size()); BOOST_CHECK_EQUAL(11, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1249, 3, 500)); BOOST_CHECK(dc.verify_bid(1248, 1, 400)); BOOST_CHECK(dc.verify_bid(1246, 2, 1100)); BOOST_CHECK(dc.verify_bid(1245, 3, 500)); BOOST_CHECK(dc.verify_bid(1244, 2, 1000)); BOOST_CHECK(dc.verify_ask(1250, 1, 500)); BOOST_CHECK(dc.verify_ask(1251, 1, 400)); BOOST_CHECK(dc.verify_ask(1252, 2, 300)); BOOST_CHECK(dc.verify_ask(1254, 1, 300)); BOOST_CHECK(dc.verify_ask(1255, 2, 700)); // Cancel a bid level (erase) BOOST_CHECK(cancel_and_verify(order_book, &bid3, simple::os_cancelled)); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1249, 3, 500)); BOOST_CHECK(dc.verify_bid(1246, 2, 1100)); BOOST_CHECK(dc.verify_bid(1245, 3, 500)); BOOST_CHECK(dc.verify_bid(1244, 2, 1000)); BOOST_CHECK(dc.verify_bid(1242, 1, 300)); // Restored BOOST_CHECK(dc.verify_ask(1250, 1, 500)); BOOST_CHECK(dc.verify_ask(1251, 1, 400)); BOOST_CHECK(dc.verify_ask(1252, 2, 300)); BOOST_CHECK(dc.verify_ask(1254, 1, 300)); BOOST_CHECK(dc.verify_ask(1255, 2, 700)); // Cancel common bid levels (not erased) BOOST_CHECK(cancel_and_verify(order_book, &bid7, simple::os_cancelled)); BOOST_CHECK(cancel_and_verify(order_book, &bid4, simple::os_cancelled)); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1249, 3, 500)); BOOST_CHECK(dc.verify_bid(1246, 1, 500)); // Cxl 600 BOOST_CHECK(dc.verify_bid(1245, 2, 400)); // Cxl 100 BOOST_CHECK(dc.verify_bid(1244, 2, 1000)); BOOST_CHECK(dc.verify_bid(1242, 1, 300)); BOOST_CHECK(dc.verify_ask(1250, 1, 500)); BOOST_CHECK(dc.verify_ask(1251, 1, 400)); BOOST_CHECK(dc.verify_ask(1252, 2, 300)); BOOST_CHECK(dc.verify_ask(1254, 1, 300)); BOOST_CHECK(dc.verify_ask(1255, 2, 700)); // Cancel the best bid level (erased) BOOST_CHECK(cancel_and_verify(order_book, &bid1, simple::os_cancelled)); BOOST_CHECK(cancel_and_verify(order_book, &bid0, simple::os_cancelled)); BOOST_CHECK(cancel_and_verify(order_book, &bid2, simple::os_cancelled)); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1246, 1, 500)); BOOST_CHECK(dc.verify_bid(1245, 2, 400)); BOOST_CHECK(dc.verify_bid(1244, 2, 1000)); BOOST_CHECK(dc.verify_bid(1242, 1, 300)); BOOST_CHECK(dc.verify_bid(1241, 1, 400)); BOOST_CHECK(dc.verify_ask(1250, 1, 500)); BOOST_CHECK(dc.verify_ask(1251, 1, 400)); BOOST_CHECK(dc.verify_ask(1252, 2, 300)); BOOST_CHECK(dc.verify_ask(1254, 1, 300)); BOOST_CHECK(dc.verify_ask(1255, 2, 700)); } BOOST_AUTO_TEST_CASE(TestCancelAskRestore) { SimpleOrderBook order_book; SimpleOrder ask10(false, 1258, 600); SimpleOrder ask9(false, 1257, 700); SimpleOrder ask8(false, 1256, 100); SimpleOrder ask7(false, 1256, 100); SimpleOrder ask6(false, 1255, 500); SimpleOrder ask5(false, 1255, 200); SimpleOrder ask4(false, 1254, 300); SimpleOrder ask3(false, 1252, 200); SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 400); SimpleOrder ask0(false, 1250, 500); SimpleOrder bid0(true, 1249, 100); SimpleOrder bid1(true, 1249, 200); SimpleOrder bid2(true, 1249, 200); SimpleOrder bid3(true, 1248, 400); SimpleOrder bid4(true, 1246, 600); SimpleOrder bid5(true, 1246, 500); SimpleOrder bid6(true, 1245, 200); SimpleOrder bid7(true, 1245, 100); SimpleOrder bid8(true, 1245, 200); SimpleOrder bid9(true, 1244, 700); SimpleOrder bid10(true, 1244, 300); SimpleOrder bid11(true, 1242, 300); SimpleOrder bid12(true, 1241, 400); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &ask3, false)); BOOST_CHECK(add_and_verify(order_book, &ask4, false)); BOOST_CHECK(add_and_verify(order_book, &ask5, false)); BOOST_CHECK(add_and_verify(order_book, &ask6, false)); BOOST_CHECK(add_and_verify(order_book, &ask7, false)); BOOST_CHECK(add_and_verify(order_book, &ask8, false)); BOOST_CHECK(add_and_verify(order_book, &ask9, false)); BOOST_CHECK(add_and_verify(order_book, &ask10, false)); BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); BOOST_CHECK(add_and_verify(order_book, &bid3, false)); BOOST_CHECK(add_and_verify(order_book, &bid4, false)); BOOST_CHECK(add_and_verify(order_book, &bid5, false)); BOOST_CHECK(add_and_verify(order_book, &bid6, false)); BOOST_CHECK(add_and_verify(order_book, &bid7, false)); BOOST_CHECK(add_and_verify(order_book, &bid8, false)); BOOST_CHECK(add_and_verify(order_book, &bid9, false)); BOOST_CHECK(add_and_verify(order_book, &bid10, false)); BOOST_CHECK(add_and_verify(order_book, &bid11, false)); BOOST_CHECK(add_and_verify(order_book, &bid12, false)); // Verify sizes BOOST_CHECK_EQUAL(13, order_book.bids().size()); BOOST_CHECK_EQUAL(11, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1249, 3, 500)); BOOST_CHECK(dc.verify_bid(1248, 1, 400)); BOOST_CHECK(dc.verify_bid(1246, 2, 1100)); BOOST_CHECK(dc.verify_bid(1245, 3, 500)); BOOST_CHECK(dc.verify_bid(1244, 2, 1000)); BOOST_CHECK(dc.verify_ask(1250, 1, 500)); BOOST_CHECK(dc.verify_ask(1251, 1, 400)); BOOST_CHECK(dc.verify_ask(1252, 2, 300)); BOOST_CHECK(dc.verify_ask(1254, 1, 300)); BOOST_CHECK(dc.verify_ask(1255, 2, 700)); // Cancel an ask level (erase) BOOST_CHECK(cancel_and_verify(order_book, &ask1, simple::os_cancelled)); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1249, 3, 500)); BOOST_CHECK(dc.verify_bid(1248, 1, 400)); BOOST_CHECK(dc.verify_bid(1246, 2, 1100)); BOOST_CHECK(dc.verify_bid(1245, 3, 500)); BOOST_CHECK(dc.verify_bid(1244, 2, 1000)); BOOST_CHECK(dc.verify_ask(1250, 1, 500)); BOOST_CHECK(dc.verify_ask(1252, 2, 300)); BOOST_CHECK(dc.verify_ask(1254, 1, 300)); BOOST_CHECK(dc.verify_ask(1255, 2, 700)); BOOST_CHECK(dc.verify_ask(1256, 2, 200)); // Restored // Cancel common ask levels (not erased) BOOST_CHECK(cancel_and_verify(order_book, &ask2, simple::os_cancelled)); BOOST_CHECK(cancel_and_verify(order_book, &ask6, simple::os_cancelled)); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1249, 3, 500)); BOOST_CHECK(dc.verify_bid(1248, 1, 400)); BOOST_CHECK(dc.verify_bid(1246, 2, 1100)); BOOST_CHECK(dc.verify_bid(1245, 3, 500)); BOOST_CHECK(dc.verify_bid(1244, 2, 1000)); BOOST_CHECK(dc.verify_ask(1250, 1, 500)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); // Cxl 100 BOOST_CHECK(dc.verify_ask(1254, 1, 300)); BOOST_CHECK(dc.verify_ask(1255, 1, 200)); // Cxl 500 BOOST_CHECK(dc.verify_ask(1256, 2, 200)); // Cancel the best ask level (erased) BOOST_CHECK(cancel_and_verify(order_book, &ask0, simple::os_cancelled)); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1249, 3, 500)); BOOST_CHECK(dc.verify_bid(1248, 1, 400)); BOOST_CHECK(dc.verify_bid(1246, 2, 1100)); BOOST_CHECK(dc.verify_bid(1245, 3, 500)); BOOST_CHECK(dc.verify_bid(1244, 2, 1000)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); BOOST_CHECK(dc.verify_ask(1254, 1, 300)); BOOST_CHECK(dc.verify_ask(1255, 1, 200)); BOOST_CHECK(dc.verify_ask(1256, 2, 200)); BOOST_CHECK(dc.verify_ask(1257, 1, 700)); // Restored } BOOST_AUTO_TEST_CASE(TestFillCompleteBidRestoreDepth) { SimpleOrderBook order_book; SimpleOrder ask10(false, 1258, 600); SimpleOrder ask9(false, 1257, 700); SimpleOrder ask8(false, 1256, 100); SimpleOrder ask7(false, 1256, 100); SimpleOrder ask6(false, 1255, 500); SimpleOrder ask5(false, 1255, 200); SimpleOrder ask4(false, 1254, 300); SimpleOrder ask3(false, 1252, 200); SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 400); SimpleOrder ask0(false, 1250, 500); SimpleOrder bid0(true, 1249, 100); SimpleOrder bid1(true, 1249, 200); SimpleOrder bid2(true, 1249, 200); SimpleOrder bid3(true, 1248, 400); SimpleOrder bid4(true, 1246, 600); SimpleOrder bid5(true, 1246, 500); SimpleOrder bid6(true, 1245, 200); SimpleOrder bid7(true, 1245, 100); SimpleOrder bid8(true, 1245, 200); SimpleOrder bid9(true, 1244, 700); SimpleOrder bid10(true, 1244, 300); SimpleOrder bid11(true, 1242, 300); SimpleOrder bid12(true, 1241, 400); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &ask3, false)); BOOST_CHECK(add_and_verify(order_book, &ask4, false)); BOOST_CHECK(add_and_verify(order_book, &ask5, false)); BOOST_CHECK(add_and_verify(order_book, &ask6, false)); BOOST_CHECK(add_and_verify(order_book, &ask7, false)); BOOST_CHECK(add_and_verify(order_book, &ask8, false)); BOOST_CHECK(add_and_verify(order_book, &ask9, false)); BOOST_CHECK(add_and_verify(order_book, &ask10, false)); BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); BOOST_CHECK(add_and_verify(order_book, &bid3, false)); BOOST_CHECK(add_and_verify(order_book, &bid4, false)); BOOST_CHECK(add_and_verify(order_book, &bid5, false)); BOOST_CHECK(add_and_verify(order_book, &bid6, false)); BOOST_CHECK(add_and_verify(order_book, &bid7, false)); BOOST_CHECK(add_and_verify(order_book, &bid8, false)); BOOST_CHECK(add_and_verify(order_book, &bid9, false)); BOOST_CHECK(add_and_verify(order_book, &bid10, false)); BOOST_CHECK(add_and_verify(order_book, &bid11, false)); BOOST_CHECK(add_and_verify(order_book, &bid12, false)); // Verify sizes BOOST_CHECK_EQUAL(13, order_book.bids().size()); BOOST_CHECK_EQUAL(11, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1249, 3, 500)); BOOST_CHECK(dc.verify_bid(1248, 1, 400)); BOOST_CHECK(dc.verify_bid(1246, 2, 1100)); BOOST_CHECK(dc.verify_bid(1245, 3, 500)); BOOST_CHECK(dc.verify_bid(1244, 2, 1000)); BOOST_CHECK(dc.verify_ask(1250, 1, 500)); BOOST_CHECK(dc.verify_ask(1251, 1, 400)); BOOST_CHECK(dc.verify_ask(1252, 2, 300)); BOOST_CHECK(dc.verify_ask(1254, 1, 300)); BOOST_CHECK(dc.verify_ask(1255, 2, 700)); // Fill the top bid level (erase) and add an ask level (insert) SimpleOrder cross_ask(false, 1249, 800); { SimpleFillCheck fc1(&bid0, 100, 1249 * 100); SimpleFillCheck fc2(&bid1, 200, 1249 * 200); SimpleFillCheck fc3(&bid2, 200, 1249 * 200); SimpleFillCheck fc4(&cross_ask, 500, 1249 * 500); BOOST_CHECK(add_and_verify(order_book, &cross_ask, true, false)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1248, 1, 400)); BOOST_CHECK(dc.verify_bid(1246, 2, 1100)); BOOST_CHECK(dc.verify_bid(1245, 3, 500)); BOOST_CHECK(dc.verify_bid(1244, 2, 1000)); BOOST_CHECK(dc.verify_bid(1242, 1, 300)); // Restored BOOST_CHECK(dc.verify_ask(1249, 1, 300)); // Inserted BOOST_CHECK(dc.verify_ask(1250, 1, 500)); BOOST_CHECK(dc.verify_ask(1251, 1, 400)); BOOST_CHECK(dc.verify_ask(1252, 2, 300)); BOOST_CHECK(dc.verify_ask(1254, 1, 300)); // Fill the top bid level (erase) but do not add an ask level (no insert) SimpleOrder cross_ask2(false, 1248, 400); { SimpleFillCheck fc1(&bid3, 400, 1248 * 400); SimpleFillCheck fc4(&cross_ask2, 400, 1248 * 400); BOOST_CHECK(add_and_verify(order_book, &cross_ask2, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1246, 2, 1100)); BOOST_CHECK(dc.verify_bid(1245, 3, 500)); BOOST_CHECK(dc.verify_bid(1244, 2, 1000)); BOOST_CHECK(dc.verify_bid(1242, 1, 300)); BOOST_CHECK(dc.verify_bid(1241, 1, 400)); // Restored BOOST_CHECK(dc.verify_ask(1249, 1, 300)); BOOST_CHECK(dc.verify_ask(1250, 1, 500)); BOOST_CHECK(dc.verify_ask(1251, 1, 400)); BOOST_CHECK(dc.verify_ask(1252, 2, 300)); BOOST_CHECK(dc.verify_ask(1254, 1, 300)); // Fill the top bid level (erase) and add ask level (insert), // but nothing to restore SimpleOrder cross_ask3(false, 1246, 2400); { SimpleFillCheck fc1(&bid4, 600, 1246 * 600); SimpleFillCheck fc2(&bid5, 500, 1246 * 500); SimpleFillCheck fc3(&cross_ask3, 1100, 1246 * 1100); BOOST_CHECK(add_and_verify(order_book, &cross_ask3, true, false)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1245, 3, 500)); BOOST_CHECK(dc.verify_bid(1244, 2, 1000)); BOOST_CHECK(dc.verify_bid(1242, 1, 300)); BOOST_CHECK(dc.verify_bid(1241, 1, 400)); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); // Nothing to restore BOOST_CHECK(dc.verify_ask(1246, 1, 1300)); BOOST_CHECK(dc.verify_ask(1249, 1, 300)); BOOST_CHECK(dc.verify_ask(1250, 1, 500)); BOOST_CHECK(dc.verify_ask(1251, 1, 400)); BOOST_CHECK(dc.verify_ask(1252, 2, 300)); // Partial fill the top bid level (reduce) SimpleOrder cross_ask4(false, 1245, 250); { SimpleFillCheck fc1(&bid6, 200, 1245 * 200); SimpleFillCheck fc2(&bid7, 50, 1245 * 50); SimpleFillCheck fc3(&cross_ask4, 250, 1245 * 250); BOOST_CHECK(add_and_verify(order_book, &cross_ask4, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1245, 2, 250)); // 1 filled, 1 reduced BOOST_CHECK(dc.verify_bid(1244, 2, 1000)); BOOST_CHECK(dc.verify_bid(1242, 1, 300)); BOOST_CHECK(dc.verify_bid(1241, 1, 400)); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); BOOST_CHECK(dc.verify_ask(1246, 1, 1300)); BOOST_CHECK(dc.verify_ask(1249, 1, 300)); BOOST_CHECK(dc.verify_ask(1250, 1, 500)); BOOST_CHECK(dc.verify_ask(1251, 1, 400)); BOOST_CHECK(dc.verify_ask(1252, 2, 300)); } BOOST_AUTO_TEST_CASE(TestFillCompleteAskRestoreDepth) { SimpleOrderBook order_book; SimpleOrder ask10(false, 1258, 600); SimpleOrder ask9(false, 1257, 700); SimpleOrder ask8(false, 1256, 100); SimpleOrder ask7(false, 1256, 100); SimpleOrder ask6(false, 1255, 500); SimpleOrder ask5(false, 1255, 200); SimpleOrder ask4(false, 1254, 300); SimpleOrder ask3(false, 1252, 200); SimpleOrder ask2(false, 1252, 100); SimpleOrder ask1(false, 1251, 400); SimpleOrder ask0(false, 1250, 500); SimpleOrder bid0(true, 1249, 100); SimpleOrder bid1(true, 1249, 200); SimpleOrder bid2(true, 1249, 200); SimpleOrder bid3(true, 1248, 400); SimpleOrder bid4(true, 1246, 600); SimpleOrder bid5(true, 1246, 500); SimpleOrder bid6(true, 1245, 200); SimpleOrder bid7(true, 1245, 100); SimpleOrder bid8(true, 1245, 200); SimpleOrder bid9(true, 1244, 700); SimpleOrder bid10(true, 1244, 300); SimpleOrder bid11(true, 1242, 300); SimpleOrder bid12(true, 1241, 400); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &ask3, false)); BOOST_CHECK(add_and_verify(order_book, &ask4, false)); BOOST_CHECK(add_and_verify(order_book, &ask5, false)); BOOST_CHECK(add_and_verify(order_book, &ask6, false)); BOOST_CHECK(add_and_verify(order_book, &ask7, false)); BOOST_CHECK(add_and_verify(order_book, &ask8, false)); BOOST_CHECK(add_and_verify(order_book, &ask9, false)); BOOST_CHECK(add_and_verify(order_book, &ask10, false)); BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); BOOST_CHECK(add_and_verify(order_book, &bid3, false)); BOOST_CHECK(add_and_verify(order_book, &bid4, false)); BOOST_CHECK(add_and_verify(order_book, &bid5, false)); BOOST_CHECK(add_and_verify(order_book, &bid6, false)); BOOST_CHECK(add_and_verify(order_book, &bid7, false)); BOOST_CHECK(add_and_verify(order_book, &bid8, false)); BOOST_CHECK(add_and_verify(order_book, &bid9, false)); BOOST_CHECK(add_and_verify(order_book, &bid10, false)); BOOST_CHECK(add_and_verify(order_book, &bid11, false)); BOOST_CHECK(add_and_verify(order_book, &bid12, false)); // Verify sizes BOOST_CHECK_EQUAL(13, order_book.bids().size()); BOOST_CHECK_EQUAL(11, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1249, 3, 500)); BOOST_CHECK(dc.verify_bid(1248, 1, 400)); BOOST_CHECK(dc.verify_bid(1246, 2, 1100)); BOOST_CHECK(dc.verify_bid(1245, 3, 500)); BOOST_CHECK(dc.verify_bid(1244, 2, 1000)); BOOST_CHECK(dc.verify_ask(1250, 1, 500)); BOOST_CHECK(dc.verify_ask(1251, 1, 400)); BOOST_CHECK(dc.verify_ask(1252, 2, 300)); BOOST_CHECK(dc.verify_ask(1254, 1, 300)); BOOST_CHECK(dc.verify_ask(1255, 2, 700)); // Fill the top ask level (erase) and add a bid level (insert) SimpleOrder cross_bid(true, 1250, 800); { SimpleFillCheck fc1(&ask0, 500, 1250 * 500); SimpleFillCheck fc4(&cross_bid, 500, 1250 * 500); BOOST_CHECK(add_and_verify(order_book, &cross_bid, true, false)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 300)); BOOST_CHECK(dc.verify_bid(1249, 3, 500)); BOOST_CHECK(dc.verify_bid(1248, 1, 400)); BOOST_CHECK(dc.verify_bid(1246, 2, 1100)); BOOST_CHECK(dc.verify_bid(1245, 3, 500)); BOOST_CHECK(dc.verify_ask(1251, 1, 400)); BOOST_CHECK(dc.verify_ask(1252, 2, 300)); BOOST_CHECK(dc.verify_ask(1254, 1, 300)); BOOST_CHECK(dc.verify_ask(1255, 2, 700)); BOOST_CHECK(dc.verify_ask(1256, 2, 200)); // Restored // Fill the top ask level (erase) but do not add an bid level (no insert) SimpleOrder cross_bid2(true, 1251, 400); { SimpleFillCheck fc1(&ask1, 400, 1251 * 400); SimpleFillCheck fc4(&cross_bid2, 400, 1251 * 400); BOOST_CHECK(add_and_verify(order_book, &cross_bid2, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 300)); BOOST_CHECK(dc.verify_bid(1249, 3, 500)); BOOST_CHECK(dc.verify_bid(1248, 1, 400)); BOOST_CHECK(dc.verify_bid(1246, 2, 1100)); BOOST_CHECK(dc.verify_bid(1245, 3, 500)); BOOST_CHECK(dc.verify_ask(1252, 2, 300)); BOOST_CHECK(dc.verify_ask(1254, 1, 300)); BOOST_CHECK(dc.verify_ask(1255, 2, 700)); BOOST_CHECK(dc.verify_ask(1256, 2, 200)); BOOST_CHECK(dc.verify_ask(1257, 1, 700)); // Restored // Fill the top ask level (erase) and add bid level (insert), // but nothing to restore SimpleOrder cross_bid3(true, 1252, 2400); { SimpleFillCheck fc1(&ask2, 100, 1252 * 100); SimpleFillCheck fc2(&ask3, 200, 1252 * 200); SimpleFillCheck fc3(&cross_bid3, 300, 1252 * 300); BOOST_CHECK(add_and_verify(order_book, &cross_bid3, true, false)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1252, 1, 2100)); // Insert BOOST_CHECK(dc.verify_bid(1250, 1, 300)); BOOST_CHECK(dc.verify_bid(1249, 3, 500)); BOOST_CHECK(dc.verify_bid(1248, 1, 400)); BOOST_CHECK(dc.verify_bid(1246, 2, 1100)); BOOST_CHECK(dc.verify_ask(1254, 1, 300)); BOOST_CHECK(dc.verify_ask(1255, 2, 700)); BOOST_CHECK(dc.verify_ask(1256, 2, 200)); BOOST_CHECK(dc.verify_ask(1257, 1, 700)); BOOST_CHECK(dc.verify_ask(1258, 1, 600)); // Restored // Fill the top ask level (erase) but nothing to restore SimpleOrder cross_bid4(true, 1254, 300); { SimpleFillCheck fc2(&ask4, 300, 1254 * 300); SimpleFillCheck fc3(&cross_bid4, 300, 1254 * 300); BOOST_CHECK(add_and_verify(order_book, &cross_bid4, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1252, 1, 2100)); BOOST_CHECK(dc.verify_bid(1250, 1, 300)); BOOST_CHECK(dc.verify_bid(1249, 3, 500)); BOOST_CHECK(dc.verify_bid(1248, 1, 400)); BOOST_CHECK(dc.verify_bid(1246, 2, 1100)); BOOST_CHECK(dc.verify_ask(1255, 2, 700)); BOOST_CHECK(dc.verify_ask(1256, 2, 200)); BOOST_CHECK(dc.verify_ask(1257, 1, 700)); BOOST_CHECK(dc.verify_ask(1258, 1, 600)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); // Nothing to restore // Partial fill the top ask level (reduce) SimpleOrder cross_bid5(true, 1255, 550); { SimpleFillCheck fc1(&ask5, 200, 1255 * 200); SimpleFillCheck fc2(&ask6, 350, 1255 * 350); SimpleFillCheck fc3(&cross_bid5, 550, 1255 * 550); BOOST_CHECK(add_and_verify(order_book, &cross_bid5, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1252, 1, 2100)); BOOST_CHECK(dc.verify_bid(1250, 1, 300)); BOOST_CHECK(dc.verify_bid(1249, 3, 500)); BOOST_CHECK(dc.verify_bid(1248, 1, 400)); BOOST_CHECK(dc.verify_bid(1246, 2, 1100)); BOOST_CHECK(dc.verify_ask(1255, 1, 150)); // 1 filled, 1 reduced BOOST_CHECK(dc.verify_ask(1256, 2, 200)); BOOST_CHECK(dc.verify_ask(1257, 1, 700)); BOOST_CHECK(dc.verify_ask(1258, 1, 600)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); } BOOST_AUTO_TEST_CASE(TestReplaceSizeIncrease) { SimpleOrderBook order_book; ChangedChecker cc(order_book.depth()); SimpleOrder ask0(false, 1252, 300); SimpleOrder ask1(false, 1251, 200); SimpleOrder bid0(true, 1250, 100); SimpleOrder bid1(true, 1249, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 200)); BOOST_CHECK(dc.verify_ask(1252, 1, 300)); // Verify changed stamps BOOST_CHECK(cc.verify_bid_changed(true, true, false, false, false)); BOOST_CHECK(cc.verify_ask_changed(true, true, false, false, false)); cc.reset(); // Replace size BOOST_CHECK(replace_and_verify(order_book, &bid0, 25)); BOOST_CHECK(replace_and_verify(order_book, &ask0, 50)); // Verify changed stamps BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); BOOST_CHECK(cc.verify_ask_changed(false, true, false, false, false)); cc.reset(); // Verify orders BOOST_CHECK_EQUAL(125, bid0.order_qty()); BOOST_CHECK_EQUAL(350, ask0.order_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 125)); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 200)); BOOST_CHECK(dc.verify_ask(1252, 1, 350)); } BOOST_AUTO_TEST_CASE(TestReplaceSizeDecrease) { SimpleOrderBook order_book; ChangedChecker cc(order_book.depth()); SimpleOrder ask1(false, 1252, 200); SimpleOrder ask0(false, 1252, 300); SimpleOrder bid1(true, 1251, 100); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1251, 1, 100)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 2, 500)); // Verify changed stamps BOOST_CHECK(cc.verify_bid_changed(true, true,false, false, false)); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); // Replace size BOOST_CHECK(replace_and_verify(order_book, &bid0, -60)); BOOST_CHECK(replace_and_verify(order_book, &ask0, -150)); // Verify orders BOOST_CHECK_EQUAL(40, bid0.order_qty()); BOOST_CHECK_EQUAL(150, ask0.order_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 100)); BOOST_CHECK(dc.verify_bid(1250, 1, 40)); BOOST_CHECK(dc.verify_ask(1252, 2, 350)); // Verify changed stamps BOOST_CHECK(cc.verify_bid_changed(false, true, false, false, false)); BOOST_CHECK(cc.verify_ask_changed(true,false, false, false, false)); cc.reset(); } BOOST_AUTO_TEST_CASE(TestReplaceSizeDecreaseCancel) { SimpleOrderBook order_book; ChangedChecker cc(order_book.depth()); SimpleOrder ask1(false, 1252, 200); SimpleOrder ask0(false, 1252, 300); SimpleOrder bid1(true, 1251, 400); SimpleOrder bid0(true, 1250, 100); SimpleOrder bid2(true, 1249, 700); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_ask(1252, 2, 500)); BOOST_CHECK(dc.verify_bid(1251, 1, 400)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_bid(1249, 1, 700)); // Verify changed stamps BOOST_CHECK(cc.verify_bid_changed(true, true, true, false, false)); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); // Partial Fill existing book SimpleOrder cross_bid(true, 1252, 125); SimpleOrder cross_ask(false, 1251, 100); // Bid matching best ask { SimpleFillCheck fc1(&cross_bid, 125, 1252 * 125); SimpleFillCheck fc2(&ask0, 125, 1252 * 125); BOOST_CHECK(add_and_verify(order_book, &cross_bid, true, true)); } BOOST_CHECK(cc.verify_bid_changed(false, false, false, false, false)); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); cc.reset(); // Ask matching best bid { SimpleFillCheck fc1(&cross_ask, 100, 1251 * 100); SimpleFillCheck fc2(&bid1, 100, 1251 * 100); BOOST_CHECK(add_and_verify(order_book, &cross_ask, true, true)); } BOOST_CHECK(cc.verify_bid_changed(true, false, false, false, false)); BOOST_CHECK(cc.verify_ask_changed(false, false, false, false, false)); // Verify quantity BOOST_CHECK_EQUAL(175, ask0.open_qty()); BOOST_CHECK_EQUAL(300, bid1.open_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 300)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_bid(1249, 1, 700)); BOOST_CHECK(dc.verify_ask(1252, 2, 375)); // Replace size - cancel BOOST_CHECK(replace_and_verify( order_book, &ask0, -175, PRICE_UNCHANGED, simple::os_cancelled)); // Verify orders BOOST_CHECK_EQUAL(125, ask0.order_qty()); BOOST_CHECK_EQUAL(0, ask0.open_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 300)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_bid(1249, 1, 700)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); // Replace size - reduce level BOOST_CHECK(replace_and_verify( order_book, &bid1, -100, PRICE_UNCHANGED, simple::os_accepted)); // Verify orders BOOST_CHECK_EQUAL(300, bid1.order_qty()); BOOST_CHECK_EQUAL(200, bid1.open_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 200)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_bid(1249, 1, 700)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); // Replace size - cancel and erase level BOOST_CHECK(replace_and_verify( order_book, &bid1, -200, PRICE_UNCHANGED, simple::os_cancelled)); // Verify orders BOOST_CHECK_EQUAL(100, bid1.order_qty()); BOOST_CHECK_EQUAL(0, bid1.open_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_bid(1249, 1, 700)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); } BOOST_AUTO_TEST_CASE(TestReplaceSizeDecreaseTooMuch) { SimpleOrderBook order_book; SimpleOrder ask1(false, 1252, 200); SimpleOrder ask0(false, 1252, 300); SimpleOrder bid1(true, 1251, 100); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1251, 1, 100)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 2, 500)); SimpleOrder cross_bid(true, 1252, 200); // Partial fill existing order { SimpleFillCheck fc1(&cross_bid, 200, 1252 * 200); SimpleFillCheck fc2(&ask0, 200, 1252 * 200); BOOST_CHECK(add_and_verify(order_book, &cross_bid, true, true)); } // Verify open quantity BOOST_CHECK_EQUAL(100, ask0.open_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 100)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 2, 300)); // Replace size - not enough left BOOST_CHECK( ! order_book.replace(&ask0, -150, PRICE_UNCHANGED)); // Verify change BOOST_CHECK_EQUAL(simple::os_cancelled, ask0.state()); BOOST_CHECK_EQUAL(200U, ask0.order_qty()); BOOST_CHECK_EQUAL(0U, ask0.open_qty()); // Verify depth unchanged dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 100)); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); } BOOST_AUTO_TEST_CASE(TestReplaceSizeIncreaseDecrease) { SimpleOrderBook order_book; SimpleOrder ask1(false, 1252, 200); SimpleOrder ask0(false, 1251, 300); SimpleOrder bid1(true, 1251, 100); SimpleOrder bid0(true, 1250, 100); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1250, 1, 100)); BOOST_CHECK(dc.verify_ask(1251, 1, 300)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); // Replace size BOOST_CHECK(replace_and_verify(order_book, &ask0, 50)); BOOST_CHECK(replace_and_verify(order_book, &bid0, 25)); BOOST_CHECK(replace_and_verify(order_book, &ask0, -100)); BOOST_CHECK(replace_and_verify(order_book, &bid0, 25)); BOOST_CHECK(replace_and_verify(order_book, &ask0, 300)); BOOST_CHECK(replace_and_verify(order_book, &bid0, -75)); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 75)); BOOST_CHECK(dc.verify_ask(1251, 1, 550)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); } BOOST_AUTO_TEST_CASE(TestReplaceBidPriceChange) { SimpleOrderBook order_book; ChangedChecker cc(order_book.depth()); SimpleOrder ask0(false, 1253, 300); SimpleOrder ask1(false, 1252, 200); SimpleOrder bid1(true, 1251, 140); SimpleOrder bid0(true, 1250, 120); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1251, 1, 140)); BOOST_CHECK(dc.verify_bid(1250, 1, 120)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); BOOST_CHECK(dc.verify_ask(1253, 1, 300)); // Verify changed stamps BOOST_CHECK(cc.verify_bid_changed(true, true, false, false, false)); BOOST_CHECK(cc.verify_ask_changed(true, true, false, false, false)); cc.reset(); // Replace price increase 1250 -> 1251 BOOST_CHECK(replace_and_verify(order_book, &bid0, SIZE_UNCHANGED, 1251)); // Verify price change in book SimpleOrderBook::Bids::const_iterator bid = order_book.bids().begin(); BOOST_CHECK_EQUAL(1251, bid->first); BOOST_CHECK_EQUAL(&bid1, bid->second.ptr()); BOOST_CHECK_EQUAL(1251, (++bid)->first); BOOST_CHECK_EQUAL(&bid0, bid->second.ptr()); BOOST_CHECK(order_book.bids().end() == ++bid); // Verify order BOOST_CHECK_EQUAL(1251, bid0.price()); BOOST_CHECK_EQUAL(120, bid0.order_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 2, 260)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); BOOST_CHECK(dc.verify_ask(1253, 1, 300)); // Verify changed stamps BOOST_CHECK(cc.verify_bid_changed(true, true, false, false, false)); BOOST_CHECK(cc.verify_ask_changed(false, false, false, false, false)); cc.reset(); // Replace price decrease 1251 -> 1250 BOOST_CHECK(replace_and_verify(order_book, &bid1, SIZE_UNCHANGED, 1250)); // Verify price change in book bid = order_book.bids().begin(); BOOST_CHECK_EQUAL(1251, bid->first); BOOST_CHECK_EQUAL(&bid0, bid->second.ptr()); BOOST_CHECK_EQUAL(1250, (++bid)->first); BOOST_CHECK_EQUAL(&bid1, bid->second.ptr()); BOOST_CHECK(order_book.bids().end() == ++bid); // Verify order BOOST_CHECK_EQUAL(1250, bid1.price()); BOOST_CHECK_EQUAL(140, bid1.order_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 120)); BOOST_CHECK(dc.verify_bid(1250, 1, 140)); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); BOOST_CHECK(dc.verify_ask(1253, 1, 300)); // Verify changed stamps BOOST_CHECK(cc.verify_bid_changed(true, true, false, false, false)); BOOST_CHECK(cc.verify_ask_changed(false, false, false, false, false)); } BOOST_AUTO_TEST_CASE(TestReplaceAskPriceChange) { SimpleOrderBook order_book; ChangedChecker cc(order_book.depth()); SimpleOrder ask0(false, 1253, 300); SimpleOrder ask1(false, 1252, 200); SimpleOrder bid1(true, 1251, 140); SimpleOrder bid0(true, 1250, 120); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify changed stamps BOOST_CHECK(cc.verify_bid_changed(true, true, false, false, false)); BOOST_CHECK(cc.verify_ask_changed(true, true, false, false, false)); cc.reset(); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1251, 1, 140)); BOOST_CHECK(dc.verify_bid(1250, 1, 120)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); BOOST_CHECK(dc.verify_ask(1253, 1, 300)); // Replace price increase 1252 -> 1253 BOOST_CHECK(replace_and_verify(order_book, &ask1, SIZE_UNCHANGED, 1253)); // Verify changed stamps BOOST_CHECK(cc.verify_bid_changed(false, false, false, false, false)); BOOST_CHECK(cc.verify_ask_changed(true, true, false, false, false)); cc.reset(); // Verify price change in book SimpleOrderBook::Asks::const_iterator ask = order_book.asks().begin(); BOOST_CHECK_EQUAL(1253, ask->first); BOOST_CHECK_EQUAL(&ask0, ask->second.ptr()); BOOST_CHECK_EQUAL(1253, (++ask)->first); BOOST_CHECK_EQUAL(&ask1, ask->second.ptr()); BOOST_CHECK(order_book.asks().end() == ++ask); // Verify order BOOST_CHECK_EQUAL(1253, ask1.price()); BOOST_CHECK_EQUAL(200, ask1.order_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 140)); BOOST_CHECK(dc.verify_bid(1250, 1, 120)); BOOST_CHECK(dc.verify_ask(1253, 2, 500)); // Replace price decrease 1253 -> 1252 BOOST_CHECK(replace_and_verify(order_book, &ask0, SIZE_UNCHANGED, 1252)); // Verify price change in book ask = order_book.asks().begin(); BOOST_CHECK_EQUAL(1252, ask->first); BOOST_CHECK_EQUAL(&ask0, ask->second.ptr()); BOOST_CHECK_EQUAL(1253, (++ask)->first); BOOST_CHECK_EQUAL(&ask1, ask->second.ptr()); BOOST_CHECK(order_book.asks().end() == ++ask); // Verify order BOOST_CHECK_EQUAL(1252, ask0.price()); BOOST_CHECK_EQUAL(300, ask0.order_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 140)); BOOST_CHECK(dc.verify_bid(1250, 1, 120)); BOOST_CHECK(dc.verify_ask(1252, 1, 300)); BOOST_CHECK(dc.verify_ask(1253, 1, 200)); BOOST_CHECK(dc.verify_ask( 0, 0, 0)); } BOOST_AUTO_TEST_CASE(TestReplaceBidPriceChangeErase) { SimpleOrderBook order_book; SimpleOrder ask0(false, 1253, 300); SimpleOrder ask1(false, 1252, 200); SimpleOrder bid1(true, 1251, 140); SimpleOrder bid0(true, 1250, 120); SimpleOrder bid2(true, 1249, 100); SimpleOrder bid3(true, 1248, 200); SimpleOrder bid4(true, 1247, 400); SimpleOrder bid5(true, 1246, 800); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); BOOST_CHECK(add_and_verify(order_book, &bid3, false)); BOOST_CHECK(add_and_verify(order_book, &bid4, false)); BOOST_CHECK(add_and_verify(order_book, &bid5, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1251, 1, 140)); BOOST_CHECK(dc.verify_bid(1250, 1, 120)); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 200)); BOOST_CHECK(dc.verify_bid(1247, 1, 400)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); BOOST_CHECK(dc.verify_ask(1253, 1, 300)); // Replace price increase 1250 -> 1251 BOOST_CHECK(replace_and_verify(order_book, &bid0, SIZE_UNCHANGED, 1251)); // Verify price change in book SimpleOrderBook::Bids::const_iterator bid = order_book.bids().begin(); BOOST_CHECK_EQUAL(1251, bid->first); BOOST_CHECK_EQUAL(&bid1, bid->second.ptr()); BOOST_CHECK_EQUAL(1251, (++bid)->first); BOOST_CHECK_EQUAL(&bid0, bid->second.ptr()); BOOST_CHECK_EQUAL(1249, (++bid)->first); BOOST_CHECK_EQUAL(&bid2, bid->second.ptr()); // Verify order BOOST_CHECK_EQUAL(1251, bid0.price()); BOOST_CHECK_EQUAL(120, bid0.order_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 2, 260)); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 200)); BOOST_CHECK(dc.verify_bid(1247, 1, 400)); BOOST_CHECK(dc.verify_bid(1246, 1, 800)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); BOOST_CHECK(dc.verify_ask(1253, 1, 300)); // Replace price decrease 1251 -> 1250 BOOST_CHECK(replace_and_verify(order_book, &bid1, SIZE_UNCHANGED, 1250)); // Verify price change in book bid = order_book.bids().begin(); BOOST_CHECK_EQUAL(1251, bid->first); BOOST_CHECK_EQUAL(&bid0, bid->second.ptr()); BOOST_CHECK_EQUAL(1250, (++bid)->first); BOOST_CHECK_EQUAL(&bid1, bid->second.ptr()); BOOST_CHECK_EQUAL(1249, (++bid)->first); BOOST_CHECK_EQUAL(&bid2, bid->second.ptr()); // Verify order BOOST_CHECK_EQUAL(1250, bid1.price()); BOOST_CHECK_EQUAL(140, bid1.order_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 120)); BOOST_CHECK(dc.verify_bid(1250, 1, 140)); BOOST_CHECK(dc.verify_bid(1249, 1, 100)); BOOST_CHECK(dc.verify_bid(1248, 1, 200)); BOOST_CHECK(dc.verify_bid(1247, 1, 400)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); BOOST_CHECK(dc.verify_ask(1253, 1, 300)); } BOOST_AUTO_TEST_CASE(TestReplaceAskPriceChangeErase) { SimpleOrderBook order_book; SimpleOrder ask5(false, 1258, 304); SimpleOrder ask4(false, 1256, 330); SimpleOrder ask3(false, 1255, 302); SimpleOrder ask2(false, 1254, 310); SimpleOrder ask0(false, 1253, 300); SimpleOrder ask1(false, 1252, 200); SimpleOrder bid1(true, 1251, 140); SimpleOrder bid0(true, 1250, 120); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &ask3, false)); BOOST_CHECK(add_and_verify(order_book, &ask4, false)); BOOST_CHECK(add_and_verify(order_book, &ask5, false)); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1251, 1, 140)); BOOST_CHECK(dc.verify_bid(1250, 1, 120)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); BOOST_CHECK(dc.verify_ask(1253, 1, 300)); BOOST_CHECK(dc.verify_ask(1254, 1, 310)); BOOST_CHECK(dc.verify_ask(1255, 1, 302)); BOOST_CHECK(dc.verify_ask(1256, 1, 330)); // Replace price increase 1252 -> 1253 BOOST_CHECK(replace_and_verify(order_book, &ask1, SIZE_UNCHANGED, 1253)); // Verify price change in book SimpleOrderBook::Asks::const_iterator ask = order_book.asks().begin(); BOOST_CHECK_EQUAL(1253, ask->first); BOOST_CHECK_EQUAL(&ask0, ask->second.ptr()); BOOST_CHECK_EQUAL(1253, (++ask)->first); BOOST_CHECK_EQUAL(&ask1, ask->second.ptr()); BOOST_CHECK_EQUAL(1254, (++ask)->first); BOOST_CHECK_EQUAL(&ask2, ask->second.ptr()); // Verify order BOOST_CHECK_EQUAL(1253, ask1.price()); BOOST_CHECK_EQUAL(200, ask1.order_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 140)); BOOST_CHECK(dc.verify_bid(1250, 1, 120)); BOOST_CHECK(dc.verify_ask(1253, 2, 500)); BOOST_CHECK(dc.verify_ask(1254, 1, 310)); BOOST_CHECK(dc.verify_ask(1255, 1, 302)); BOOST_CHECK(dc.verify_ask(1256, 1, 330)); BOOST_CHECK(dc.verify_ask(1258, 1, 304)); // Replace price decrease 1253 -> 1252 BOOST_CHECK(replace_and_verify(order_book, &ask0, SIZE_UNCHANGED, 1252)); // Verify price change in book ask = order_book.asks().begin(); BOOST_CHECK_EQUAL(1252, ask->first); BOOST_CHECK_EQUAL(&ask0, ask->second.ptr()); BOOST_CHECK_EQUAL(1253, (++ask)->first); BOOST_CHECK_EQUAL(&ask1, ask->second.ptr()); BOOST_CHECK_EQUAL(1254, (++ask)->first); BOOST_CHECK_EQUAL(&ask2, ask->second.ptr()); // Verify order BOOST_CHECK_EQUAL(1252, ask0.price()); BOOST_CHECK_EQUAL(300, ask0.order_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 140)); BOOST_CHECK(dc.verify_bid(1250, 1, 120)); BOOST_CHECK(dc.verify_ask(1252, 1, 300)); BOOST_CHECK(dc.verify_ask(1253, 1, 200)); BOOST_CHECK(dc.verify_ask(1254, 1, 310)); BOOST_CHECK(dc.verify_ask(1255, 1, 302)); BOOST_CHECK(dc.verify_ask(1256, 1, 330)); } // A potential problem // When restroing a level into the depth, the orders (and thus the restored // level already reflect the post-fill quantity, but the fill callback has // yet to be processed. As such, a multilevel fill can have fills at the // restoration price double-counted // but the BOOST_AUTO_TEST_CASE(TestBidMultiLevelFillRestore) { SimpleOrderBook order_book; SimpleOrder ask1(false, 0, 1300); SimpleOrder ask0(false, 1252, 100); SimpleOrder bid0(true, 1251, 200); SimpleOrder bid1(true, 1250, 200); SimpleOrder bid2(true, 1250, 200); SimpleOrder bid3(true, 1248, 200); SimpleOrder bid4(true, 1247, 200); SimpleOrder bid5(true, 1246, 200); SimpleOrder bid6(true, 1245, 200); // Partial match SimpleOrder bid7(true, 1244, 200); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &bid2, false)); BOOST_CHECK(add_and_verify(order_book, &bid3, false)); BOOST_CHECK(add_and_verify(order_book, &bid4, false)); BOOST_CHECK(add_and_verify(order_book, &bid5, false)); BOOST_CHECK(add_and_verify(order_book, &bid6, false)); BOOST_CHECK(add_and_verify(order_book, &bid7, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); // Verify sizes BOOST_CHECK_EQUAL(8, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); BOOST_CHECK(dc.verify_bid(1251, 1, 200)); BOOST_CHECK(dc.verify_bid(1250, 2, 400)); BOOST_CHECK(dc.verify_bid(1248, 1, 200)); BOOST_CHECK(dc.verify_bid(1247, 1, 200)); BOOST_CHECK(dc.verify_bid(1246, 1, 200)); // Match - complete { SimpleFillCheck fc0(&bid0, 200, 250200); SimpleFillCheck fc1(&bid1, 200, 250000); SimpleFillCheck fc2(&bid2, 200, 250000); SimpleFillCheck fc3(&bid3, 200, 249600); SimpleFillCheck fc4(&bid4, 200, 249400); SimpleFillCheck fc5(&bid5, 200, 249200); SimpleFillCheck fc6(&bid6, 100, 124500); SimpleFillCheck fc7(&ask1, 1300, 1622900); BOOST_CHECK(add_and_verify(order_book, &ask1, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_ask(1252, 1, 100)); BOOST_CHECK(dc.verify_bid(1245, 1, 100)); BOOST_CHECK(dc.verify_bid(1244, 1, 200)); } BOOST_AUTO_TEST_CASE(TestAskMultiLevelFillRestore) { SimpleOrderBook order_book; SimpleOrder ask0(false, 1251, 200); // Partial match SimpleOrder ask1(false, 1250, 200); SimpleOrder ask2(false, 1250, 300); SimpleOrder ask3(false, 1248, 200); SimpleOrder ask4(false, 1247, 200); SimpleOrder ask5(false, 1245, 200); SimpleOrder ask6(false, 1245, 200); SimpleOrder ask7(false, 1244, 200); SimpleOrder bid1(true, 0, 1550); SimpleOrder bid0(true, 1242, 100); // No match BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); BOOST_CHECK(add_and_verify(order_book, &ask2, false)); BOOST_CHECK(add_and_verify(order_book, &ask3, false)); BOOST_CHECK(add_and_verify(order_book, &ask4, false)); BOOST_CHECK(add_and_verify(order_book, &ask5, false)); BOOST_CHECK(add_and_verify(order_book, &ask6, false)); BOOST_CHECK(add_and_verify(order_book, &ask7, false)); BOOST_CHECK(add_and_verify(order_book, &bid0, false)); // Verify sizes BOOST_CHECK_EQUAL(8, order_book.asks().size()); BOOST_CHECK_EQUAL(1, order_book.bids().size()); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_ask(1244, 1, 200)); BOOST_CHECK(dc.verify_ask(1245, 2, 400)); BOOST_CHECK(dc.verify_ask(1247, 1, 200)); BOOST_CHECK(dc.verify_ask(1248, 1, 200)); BOOST_CHECK(dc.verify_ask(1250, 2, 500)); BOOST_CHECK(dc.verify_bid(1242, 1, 100)); // Match - complete { SimpleFillCheck fc7(&ask7, 200, 248800); SimpleFillCheck fc6(&ask6, 200, 249000); SimpleFillCheck fc5(&ask5, 200, 249000); SimpleFillCheck fc4(&ask4, 200, 249400); SimpleFillCheck fc3(&ask3, 200, 249600); SimpleFillCheck fc2(&ask2, 300, 375000); SimpleFillCheck fc1(&ask1, 200, 250000); SimpleFillCheck fc0(&ask0, 50, 62550); SimpleFillCheck fc8(&bid1, 1550, 1933350); BOOST_CHECK(add_and_verify(order_book, &bid1, true, true)); } // Verify depth dc.reset(); BOOST_CHECK(dc.verify_ask(1251, 1, 150)); BOOST_CHECK(dc.verify_bid(1242, 1, 100)); } BOOST_AUTO_TEST_CASE(TestReplaceBidMatch) { SimpleOrderBook order_book; ChangedChecker cc(order_book.depth()); SimpleOrder ask1(false, 1254, 200); SimpleOrder ask0(false, 1253, 300); SimpleOrder bid1(true, 1251, 140); SimpleOrder bid0(true, 1250, 120); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1251, 1, 140)); BOOST_CHECK(dc.verify_bid(1250, 1, 120)); BOOST_CHECK(dc.verify_ask(1253, 1, 300)); BOOST_CHECK(dc.verify_ask(1254, 1, 200)); // Verify changed stamps BOOST_CHECK(cc.verify_bid_changed(true, true, false, false, false)); BOOST_CHECK(cc.verify_ask_changed(true, true, false, false, false)); cc.reset(); // Replace price increase new best 1250 -> 1252 BOOST_CHECK(replace_and_verify(order_book, &bid0, SIZE_UNCHANGED, 1252)); // Verify price change in book SimpleOrderBook::Bids::const_iterator bid = order_book.bids().begin(); BOOST_CHECK_EQUAL(1252, bid->first); BOOST_CHECK_EQUAL(&bid0, bid->second.ptr()); BOOST_CHECK_EQUAL(1251, (++bid)->first); BOOST_CHECK_EQUAL(&bid1, bid->second.ptr()); BOOST_CHECK(order_book.bids().end() == ++bid); // Verify order BOOST_CHECK_EQUAL(1252, bid0.price()); BOOST_CHECK_EQUAL(120, bid0.order_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1252, 1, 120)); BOOST_CHECK(dc.verify_bid(1251, 1, 140)); BOOST_CHECK(dc.verify_ask(1253, 1, 300)); BOOST_CHECK(dc.verify_ask(1254, 1, 200)); // Verify changed stamps BOOST_CHECK(cc.verify_bid_changed(true, true, true, false, false)); BOOST_CHECK(cc.verify_ask_changed(false, false, false, false, false)); cc.reset(); // Match - complete { SimpleFillCheck fc0(&ask0, 140, 140 * 1253); SimpleFillCheck fc1(&bid1, 140, 140 * 1253); // Replace price increase match 1251 -> 1253 BOOST_CHECK(replace_and_verify(order_book, &bid1, SIZE_UNCHANGED, 1253, simple::os_complete, 140)); } // Verify price change in book bid = order_book.bids().begin(); BOOST_CHECK_EQUAL(1252, bid->first); BOOST_CHECK_EQUAL(&bid0, bid->second.ptr()); BOOST_CHECK(order_book.bids().end() == ++bid); // Verify order BOOST_CHECK_EQUAL(1253, bid1.price()); BOOST_CHECK_EQUAL(140, bid1.order_qty()); BOOST_CHECK_EQUAL(0, bid1.open_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1252, 1, 120)); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); BOOST_CHECK(dc.verify_ask(1253, 1, 160)); BOOST_CHECK(dc.verify_ask(1254, 1, 200)); // Verify changed stamps BOOST_CHECK(cc.verify_bid_changed(true, true, true, false, false)); BOOST_CHECK(cc.verify_ask_changed(true, false, false, false, false)); } BOOST_AUTO_TEST_CASE(TestReplaceAskMatch) { SimpleOrderBook order_book; ChangedChecker cc(order_book.depth()); SimpleOrder ask1(false, 1254, 200); SimpleOrder ask0(false, 1253, 300); SimpleOrder bid1(true, 1251, 140); SimpleOrder bid0(true, 1250, 120); // No match BOOST_CHECK(add_and_verify(order_book, &bid0, false)); BOOST_CHECK(add_and_verify(order_book, &bid1, false)); BOOST_CHECK(add_and_verify(order_book, &ask0, false)); BOOST_CHECK(add_and_verify(order_book, &ask1, false)); // Verify depth DepthCheck dc(order_book.depth()); BOOST_CHECK(dc.verify_bid(1251, 1, 140)); BOOST_CHECK(dc.verify_bid(1250, 1, 120)); BOOST_CHECK(dc.verify_ask(1253, 1, 300)); BOOST_CHECK(dc.verify_ask(1254, 1, 200)); // Verify changed stamps BOOST_CHECK(cc.verify_bid_changed(true, true, false, false, false)); BOOST_CHECK(cc.verify_ask_changed(true, true, false, false, false)); cc.reset(); // Replace price decrease new best 1254 -> 1252 BOOST_CHECK(replace_and_verify(order_book, &ask1, SIZE_UNCHANGED, 1252)); // Verify price change in book SimpleOrderBook::Asks::const_iterator ask = order_book.asks().begin(); BOOST_CHECK_EQUAL(1252, ask->first); BOOST_CHECK_EQUAL(&ask1, ask->second.ptr()); BOOST_CHECK_EQUAL(1253, (++ask)->first); BOOST_CHECK_EQUAL(&ask0, ask->second.ptr()); BOOST_CHECK(order_book.asks().end() == ++ask); // Verify order BOOST_CHECK_EQUAL(1252, ask1.price()); BOOST_CHECK_EQUAL(200, ask1.order_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1251, 1, 140)); BOOST_CHECK(dc.verify_bid(1250, 1, 120)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); BOOST_CHECK(dc.verify_ask(1253, 1, 300)); // Verify changed stamps BOOST_CHECK(cc.verify_bid_changed(false, false, false, false, false)); BOOST_CHECK(cc.verify_ask_changed(true, true, true, false, false)); cc.reset(); // Match - complete { SimpleFillCheck fc0(&ask0, 140, 140 * 1251); SimpleFillCheck fc1(&bid1, 140, 140 * 1251); // Replace price decrease match 1253 -> 1251 BOOST_CHECK(replace_and_verify(order_book, &ask0, SIZE_UNCHANGED, 1251, simple::os_accepted, 140)); } // Verify price change in book ask = order_book.asks().begin(); BOOST_CHECK_EQUAL(1251, ask->first); BOOST_CHECK_EQUAL(&ask0, ask->second.ptr()); BOOST_CHECK_EQUAL(1252, (++ask)->first); BOOST_CHECK_EQUAL(&ask1, ask->second.ptr()); BOOST_CHECK(order_book.asks().end() == ++ask); // Verify order BOOST_CHECK_EQUAL(1251, ask0.price()); BOOST_CHECK_EQUAL(300, ask0.order_qty()); BOOST_CHECK_EQUAL(160, ask0.open_qty()); // Verify depth dc.reset(); BOOST_CHECK(dc.verify_bid(1250, 1, 120)); BOOST_CHECK(dc.verify_bid( 0, 0, 0)); BOOST_CHECK(dc.verify_ask(1251, 1, 160)); BOOST_CHECK(dc.verify_ask(1252, 1, 200)); // Verify changed stamps BOOST_CHECK(cc.verify_bid_changed(true, true, false, false, false)); BOOST_CHECK(cc.verify_ask_changed(true, true, true, false, false)); } } // namespace ================================================ FILE: test/unit/ut_order_book_shared_ptr.cpp ================================================ // Copyright (c) 2012 - 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #define BOOST_TEST_NO_MAIN LiquibookTest #include #include "ut_utils.h" #include "changed_checker.h" #include #include #include #include namespace liquibook { using book::DepthLevel; using book::OrderBook; using book::OrderTracker; using simple::SimpleOrder; typedef std::shared_ptr SimpleOrderPtr; class SharedPtrOrderBook : public OrderBook { virtual void perform_callback(OrderBook::TypedCallback& cb) { switch(cb.type) { case TypedCallback::cb_order_accept: cb.order->accept(); break; case TypedCallback::cb_order_fill: { Cost fill_cost = cb.price * cb.quantity; cb.order->fill(cb.quantity, fill_cost, 0); cb.matched_order->fill(cb.quantity, fill_cost, 0); break; } case TypedCallback::cb_order_cancel: cb.order->cancel(); break; case TypedCallback::cb_order_replace: cb.order->replace(cb.delta, cb.price); break; default: // Nothing break; } } }; typedef FillCheck SharedFillCheck; BOOST_AUTO_TEST_CASE(TestSharedPointerBuild) { SharedPtrOrderBook order_book; SimpleOrderPtr ask1(new SimpleOrder(false, 1252, 100)); SimpleOrderPtr ask0(new SimpleOrder(false, 1251, 100)); SimpleOrderPtr bid1(new SimpleOrder(true, 1251, 100)); SimpleOrderPtr bid0(new SimpleOrder(true, 1250, 100)); // No match BOOST_CHECK(add_and_verify(order_book, bid0, false)); BOOST_CHECK(add_and_verify(order_book, ask0, false)); BOOST_CHECK(add_and_verify(order_book, ask1, false)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Match - complete { SharedFillCheck fc1(bid1, 100, 125100); SharedFillCheck fc2(ask0, 100, 125100); BOOST_CHECK(add_and_verify(order_book, bid1, true, true)); } // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(1, order_book.asks().size()); } BOOST_AUTO_TEST_CASE(TestSharedCancelBid) { SharedPtrOrderBook order_book; SimpleOrderPtr ask1(new SimpleOrder(false, 1252, 100)); SimpleOrderPtr ask0(new SimpleOrder(false, 1251, 100)); SimpleOrderPtr bid0(new SimpleOrder(true, 1250, 100)); // No match BOOST_CHECK(add_and_verify(order_book, bid0, false)); BOOST_CHECK(add_and_verify(order_book, ask0, false)); BOOST_CHECK(add_and_verify(order_book, ask1, false)); // Verify sizes BOOST_CHECK_EQUAL(1, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); // Cancel bid BOOST_CHECK(cancel_and_verify(order_book, bid0, simple::os_cancelled)); // Verify sizes BOOST_CHECK_EQUAL(0, order_book.bids().size()); BOOST_CHECK_EQUAL(2, order_book.asks().size()); } } // namespace ================================================ FILE: test/unit/ut_stop_orders.cpp ================================================ // Copyright (c) 2012 - 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #define BOOST_TEST_NO_MAIN LiquibookTest #include #include "ut_utils.h" #include "changed_checker.h" #include #include namespace liquibook { using book::DepthLevel; using book::OrderBook; using book::OrderTracker; using simple::SimpleOrder; namespace { const bool sideBuy = true; const bool sideSell = false; const Price prcMkt = 0; const Price prc53 = 53; const Price prc54 = 54; const Price prc55 = 55; const Price prc56 = 56; const Price prc57 = 57; const Quantity q100 = 100; const Quantity q1000 = 1000; const bool expectMatch = true; const bool expectNoMatch = false; const bool expectComplete = true; const bool expectNoComplete = false; } typedef OrderTracker SimpleTracker; typedef test::ChangedChecker<5> ChangedChecker; typedef FillCheck SimpleFillCheck; BOOST_AUTO_TEST_CASE(TestStopOrdersOffMarketNoTrade) { SimpleOrderBook book; SimpleOrder order0(sideBuy, prc55, q100); SimpleOrder order1(sideSell, prcMkt, q100); // Enter order to generate a trade establishing market price BOOST_CHECK(add_and_verify(book, &order0, expectNoMatch)); BOOST_CHECK(add_and_verify(book, &order1, expectMatch, expectComplete)); BOOST_CHECK_EQUAL(prc55, book.market_price()); SimpleOrder order2(sideBuy, prcMkt, q100, prc56); SimpleOrder order3(sideSell, prcMkt, q100, prc54); BOOST_CHECK(add_and_verify(book, &order2, expectNoMatch)); BOOST_CHECK(add_and_verify(book, &order3, expectNoMatch)); // Orders were accepted, but not traded BOOST_CHECK_EQUAL(simple::os_accepted, order2.state()); BOOST_CHECK_EQUAL(simple::os_accepted, order3.state()); } BOOST_AUTO_TEST_CASE(TestStopMarketOrdersOnMarketTradeImmediately) { SimpleOrderBook book; SimpleOrder order0(sideBuy, prc55, q100); SimpleOrder order1(sideSell, prcMkt, q100); // Enter order to generate a trade establishing market price BOOST_CHECK(add_and_verify(book, &order0, expectNoMatch)); BOOST_CHECK(add_and_verify(book, &order1, expectMatch, expectComplete)); BOOST_CHECK_EQUAL(prc55, book.market_price()); SimpleOrder order2(sideBuy, prcMkt, q100, prc55); SimpleOrder order3(sideSell, prcMkt, q100, prc55); BOOST_CHECK(add_and_verify(book, &order2, expectNoMatch)); BOOST_CHECK(add_and_verify(book, &order3, expectMatch, expectComplete)); } BOOST_AUTO_TEST_CASE(TestStopMarketOrdersTradeWhenStopPriceReached) { SimpleOrderBook book; SimpleOrder order0(sideBuy, prc53, q100); SimpleOrder order1(sideSell, prc57, q100); book.set_market_price(prc55); // Enter seed orders and be sure they don't trade with each other. BOOST_CHECK(add_and_verify(book, &order0, expectNoMatch)); BOOST_CHECK(add_and_verify(book, &order1, expectNoMatch)); // enter stop orders. Be sure they don't trade yet SimpleOrder order2(sideBuy, prcMkt, q100, prc56); SimpleOrder order3(sideSell, prcMkt, q100, prc54); BOOST_CHECK(add_and_verify(book, &order2, expectNoMatch)); BOOST_CHECK(add_and_verify(book, &order3, expectNoMatch)); SimpleOrder order4(sideBuy, prc56, q1000, 0, book::oc_all_or_none); SimpleOrder order5(sideSell, prc56, q1000, 0, book::oc_all_or_none); // Scope for fill checks { SimpleFillCheck fc0(&order0, 0, 0); SimpleFillCheck fc1(&order1, q100, q100 * prc57); SimpleFillCheck fc2(&order2, q100, q100 * prc57); // Trade at 56 which should trigger order2 which should trade with order 1 at order 1's price BOOST_CHECK(add_and_verify(book, &order4, expectNoMatch, expectNoComplete, book::oc_all_or_none)); BOOST_CHECK(add_and_verify(book, &order5, expectMatch, expectComplete, book::oc_all_or_none)); } BOOST_CHECK_EQUAL(prc57, book.market_price()); SimpleOrder order6(sideBuy, prc54, q1000, 0, book::oc_all_or_none); SimpleOrder order7(sideSell, prc54, q1000, 0, book::oc_all_or_none); // Scope for fill checks { SimpleFillCheck fc0(&order0, q100, q100 * prc53); SimpleFillCheck fc3(&order3, q100, q100 * prc53); // Trade at 54 which should trigger order3 which should trade with order 0 at order 0's price BOOST_CHECK(add_and_verify(book, &order6, expectNoMatch, expectNoComplete, book::oc_all_or_none)); BOOST_CHECK(add_and_verify(book, &order7, expectMatch, expectComplete, book::oc_all_or_none)); } BOOST_CHECK_EQUAL(prc53, book.market_price()); } BOOST_AUTO_TEST_CASE(TestStopOrdersCancelBeforeTrigger) { SimpleOrderBook book; book.set_market_price(prc55); SimpleOrder bid(sideBuy, prcMkt, q100, prc56); SimpleOrder ask(sideSell, prcMkt, q100, prc54); BOOST_CHECK(add_and_verify(book, &bid, expectNoMatch)); BOOST_CHECK(add_and_verify(book, &ask, expectNoMatch)); // Orders were accepted, but not traded BOOST_CHECK_EQUAL(simple::os_accepted, bid.state()); BOOST_CHECK_EQUAL(simple::os_accepted, ask.state()); // Cancel bid and ask BOOST_CHECK(cancel_and_verify(book, &bid, simple::os_cancelled)); BOOST_CHECK(cancel_and_verify(book, &ask, simple::os_cancelled)); } } // namespace ================================================ FILE: test/unit/ut_utils.h ================================================ // Copyright (c) 2012 -- 2017 Object Computing, Inc. // All rights reserved. // See the file license.txt for licensing information. #pragma once #include "depth_check.h" #include #include #include using namespace liquibook::book; namespace liquibook { typedef simple::SimpleOrderBook<5> SimpleOrderBook; typedef simple::SimpleOrderBook<5>::DepthTracker SimpleDepth; template bool add_and_verify(OrderBook& order_book, const OrderPtr& order, const bool match_expected, const bool complete_expected = false, OrderConditions conditions = 0) { const bool matched = order_book.add(order, conditions); if (matched == match_expected) { if (complete_expected) { // State should be complete return simple::os_complete == order->state(); } else if (conditions & oc_immediate_or_cancel) { // State should be cancelled return simple::os_cancelled == order->state(); } else { // State should be accepted return simple::os_accepted == order->state(); } } else { return false; } } template bool cancel_and_verify(OrderBook& order_book, const OrderPtr& order, simple::OrderState expected_state) { order_book.cancel(order); return expected_state == order->state(); } template bool replace_and_verify(OrderBook& order_book, const OrderPtr& order, int32_t size_change, Price new_price = PRICE_UNCHANGED, simple::OrderState expected_state = simple::os_accepted, Quantity match_qty = 0) { // Calculate Quantity expected_order_qty = order->order_qty() + size_change; Quantity expected_open_qty = order->open_qty() + size_change - match_qty; Price expected_price = (new_price == PRICE_UNCHANGED) ? order->price() : new_price; // Perform order_book.replace(order, size_change, new_price); // Verify bool correct = true; if (expected_state != order->state()) { correct = false; std::cout << "State " << order->state() << std::endl; } if (expected_order_qty != order->order_qty()) { correct = false; std::cout << "Order Qty " << order->order_qty() << std::endl; } if (expected_open_qty != order->open_qty()) { correct = false; std::cout << "Open Qty " << order->open_qty() << std::endl; } if (expected_price != order->price()) { correct = false; std::cout << "Price " << order->price() << std::endl; } return correct; } template class FillCheck { public: FillCheck(OrderPtr order, Quantity filled_qty, Cost filled_cost, OrderConditions conditions = 0) : order_(order), expected_filled_qty_(order->filled_qty() + filled_qty), expected_open_qty_(order->order_qty() - expected_filled_qty_), expected_filled_cost_(order->filled_cost() + (filled_cost)), conditions_(conditions) { } ~FillCheck() { verify_filled(); } private: OrderPtr order_; Quantity expected_filled_qty_; Quantity expected_open_qty_; Cost expected_filled_cost_; OrderConditions conditions_; void verify_filled() { if (expected_filled_qty_ != order_->filled_qty()) { std::cout << "filled_qty " << order_->filled_qty() << " expected " << expected_filled_qty_ << std::endl; throw std::runtime_error("Unexpected filled quantity"); } if (expected_open_qty_ != order_->open_qty()) { std::cout << "open_qty " << order_->open_qty() << " expected " << expected_open_qty_ << std::endl; throw std::runtime_error("Unexpected open quantity"); } if (expected_filled_cost_ != order_->filled_cost()) { std::cout << "filled_cost " << order_->filled_cost() << " expected " << expected_filled_cost_ << std::endl; throw std::runtime_error("Unexpected filled cost"); } // If the order was filled, and is not complete if (order_->state() != simple::os_complete && !expected_open_qty_) { std::cout << "state " << order_->state() << " expected " << simple::os_complete << std::endl; throw std::runtime_error("Unexpected state with no open quantity"); // Else If the order was not filled } else if (expected_open_qty_) { bool IOC = ((conditions_ & oc_immediate_or_cancel) != 0); if (order_->state() != simple::os_accepted && !IOC) { std::cout << "state " << order_->state() << " expected " << simple::os_accepted << std::endl; throw std::runtime_error("Unexpected state with open quantity"); } if (order_->state() != simple::os_cancelled && IOC) { std::cout << "state " << order_->state() << " expected " << simple::os_cancelled << std::endl; throw std::runtime_error("Unexpected state for IOC with open quantity"); } } } }; } // namespace ================================================ FILE: ut_run.bat ================================================ bin\test\liquibook_unit_test.exe ================================================ FILE: web/css/liquibook.css ================================================ .highlight { color: red; } ================================================ FILE: web/easy.html ================================================ Liquibook

Liquibook is Easy. Kindergarten Easy

To be usable, Liquibook must be easy to integrate. You need to:

  • Create a class that implements the Order Interface
  • Instantiate OrderBook and use interface
  • Handle callbacks by overriding OrderBook::perform_callback()
  • Either call OrderBook::perform_callbacks() in the calling thread, or signal a background thread to do it.

How do I...?

  • Use my Order class - Just bind your pointer to order type to the OrderBook template.
  • class MyOrderBook : public OrderBook<MyOrder*> { ... }
  • Use shared pointers - just pass a shared pointer type as the OrderBook template argument.
  • typedef boost::shared_ptr MyOrderPtr;
    class MyOrderBook : public OrderBook<MyOrderPtr> { ... }
  • Track aggregate depth levels - instantiate Depth in your child OrderBook, and manipulate it in the callback handling, like impl::SimpleOrderBook does.
  • depth_.add_order(...);
    depth_.fill_order(...);
    depth_.close_order(...);
    depth_.replace_order(...);
    depth_.ignore_fill_qty(...);
    
  • Track more than 5 depth levels - Just instantiate Depth with the number you want.
  • typedef book::Depth<10> MyDepth
  • Track BBO only - Just instantiate Depth with one level.
  • typedef book::Depth<10> MyBBO
================================================ FILE: web/fast.html ================================================ Liquibook

Liquibook is fast. Blazing Fast

Liquibook was designed to meet the needs of the most demanding customers.

Therefore it:

  • Is written in C++
  • Has no floating point comparisons
  • Performs minimal copying of data from your order objects

How Fast?

The included performance tests show Liquibook handling over 2 million orders per second.

What Does the Performance Test Actually Do?

The included performance test allocates a number of orders, and then runs for a given duration, measuring the time to handle them. If the orders run out before the test completes, the test is rerun with double the number of orders until an iteration completes.

When complete, it checks the number of orders processed before the end of the test, and reports that, as well as the number of resulting matches (trades).

The test runs against three different order books:

  • Raw order book matching engine
  • Order book matching engine with BBO tracking
  • Order book matching engine with 5 levels of aggregation (market depth)

The quoted times is for the slowest of these, with 5 levels of aggregation. You can see the performance history on GitHub where 2 second tests are recorded.

================================================ FILE: web/flexible.html ================================================ Liquibook

Liquibook is flexible. Acrobat Flexible

Liquibook is desingned to meet a wide array of use cases.

This means you get to choose:

  • Your Order class
  • Your pointer preference (smart or standard)
  • Your threading model
  • Your identifiers
  • Your downstream handling
  • Your add-ons

How is it so Flexible?

Your Order class is used, it must only implement a trivial interface. If you don't have one, a sample one is included.

The OrderBook class is a template, which accepts a pointer type as a parameter. You can use any pointer type which dereferences using the -> operator.

Liquibook is not an application, but a library, usable within your application. As such, it has no synchronization, nor background threads. You choose whether to have mutliple threads and how to synchronize them.

Liquibook does not use order IDs, user IDs, account IDs, or symbols, just Order pointers. Any mapping from order ID to pointer is done outside of Liquibook.

Liquibook further assumes all orders given to an order book are for the order book's symbol, and mapping from symbol to order book is done outside of Liquibook.

Liquibook does none of the downstream handling, such as feed publishing, resulting from a trade or a quote. After all, that is feed-specific. Instead, liquibook queues up callbacks, which you handle in your code.

Liquibook includes optional add-on classes, for depth/BBO tracking, that can easily be integrated in the handling of callbacks. For an example see SimpleOrderBook in the impl directory.

================================================ FILE: web/fluid.html ================================================ Liquibook

Liquibook

C++ Limit Order Book Matching Engine

Liquibook is an open source limit order book mathing engine, written in C++. It focues on speed and flexibility, and is ready to integrate into your projects.

Learn more »

Fast

Supports upwards of 2 million order insertions per second. Performs no floating point comparisons.

View details »

Modular

Includes optional add-ons for order aggregation by price (market depth) and BBO tracking.

View details »

Minimal

Copies minimal number of fields.

View details »

Flexible

Works with your design - does not impose threading decisions, shared pointers, or an order class.

View details »

Easy

Requires your order class implement only 4 methods.

View details »

Open

Completely non-viral, open source license to meet your needs.

View details »


© Object Computing, Inc. 2013

================================================ FILE: web/get-started.html ================================================ Liquibook

Liquibook is Fast

Liquibook was designed to meet the needs of the most demanding customers. This means:

  • Written in C++
  • No floating point comparisons
  • Minimal copying of data from your order objects

How Fast?

The included performance tests show Liquibook handling over 2 million orders per second.

What Does the Performance Test Actually Do?

The included performance test allocates a number of orders, and then runs for a given duration, measuring the time to handle them. If the orders run out before the test completes, the test is rerun with double the number of orders until an iteration completes.

When complete, it checks the number of orders processed before the end of the test, and reports that, as well as the number of resulting matches (trades).

The test runs against three different order books:

  • Raw order book matching engine
  • Order book matching engine with BBO tracking
  • Order book matching engine with 5 levels of aggregation (market depth)

The quoted times is for the slowest of these, with 5 levels of aggregation. You can see the performance history on GitHub.

================================================ FILE: web/index.html ================================================ Liquibook Matching Engine

Liquibook

Open source C++ limit order book matching engine

Liquibook is a fast and flexible matching engine. It works with your design choices, and works with any protocol.

Get started today

Fast

Supports upwards of 2 million order insertions per second. Performs no floating point comparisons.

More »

Flexible

Works with your design - does not impose a threading model, shared pointers, or a specific order class.

More »

Easy

Requires your order class implement only 4 methods. Modular design allows you to easily add BBO or market depth support.

More »


================================================ FILE: web/sub-template.html ================================================ Liquibook

Liquibook is Fast

Liquibook was designed to meet the needs of the most demanding customers. This means:

  • Written in C++
  • No floating point comparisons
  • Minimal copying of data from your order objects

How Fast?

The included performance tests show Liquibook handling over 2 million orders per second.

What Does the Performance Test Actually Do?

The included performance test allocates a number of orders, and then runs for a given duration, measuring the time to handle them. If the orders run out before the test completes, the test is rerun with double the number of orders until an iteration completes.

When complete, it checks the number of orders processed before the end of the test, and reports that, as well as the number of resulting matches (trades).

The test runs against three different order books:

  • Raw order book matching engine
  • Order book matching engine with BBO tracking
  • Order book matching engine with 5 levels of aggregation (market depth)

The quoted times is for the slowest of these, with 5 levels of aggregation. You can see the performance history on GitHub.

================================================ FILE: winenv.bat ================================================ @REM Copyright (c) 2017 Object Computing, Inc. @REM All rights reserved. @REM See the file license.txt for licensing information. REM Setting up LiquiBook environment @REM LiquiBook depends on MPC V 3.6 or later. (http://www.ociweb.com/products/mpc) @REM LiquiBook depends on BOOST V 1.43.0 or later. (http://www.boost.org/) @REM Assumes VC10(Visual Studio 2010), VC12(Visual Studio 2013), or VC14 (Visual Studio 2015) @REM installed in the default location (see VC_ROOT below) and VC..COMNTOOLS is set. @REM Customize this file by setting variables to suit your environment @REM Also you should customize the LiquiBook.features file to enable particular features on your system. @REM --------START: DELETE THESE LINES AFTER CUSTOMIZING ---- @ECHO Please make a copy of %~f0 and customize it according to the instructions in the file. @ECHO You should also customize %~dp0\Liquibook.features @goto end @REM --------END: DELETE THESE LINES AFTER CUSTOMIZING ---- @echo off REM ===================================================================================== REM EDIT THE FOLLOWING LINES OR SET THESE VALUES IN YOUR ENVIRONMENT BEFORE RUNNING SETUP if "a" == "a%MPC_ROOT%" set MPC_ROOT=c:\MPC\MPC_4_1_22 if "a" == "a%BOOST_VERSION%" set BOOST_VERSION=boost_1_61_0 if "a" == "a%BOOST_ROOT%" set BOOST_ROOT=c:\boost\%BOOST_VERSION% @REM TODO: For the pub/sub example program set QUICKFAST_ROOT @rem to the actual quickfast directory and @rem define XEERCESROOT if "a" == "a%QUICKFAST_ROOT%" set QUICKFAST_ROOT=noQuickFAST if "a" == "a%XERCESROOT%" set XERCESROOT=C:\Progs\xerces\xerces-c-3.1.1-x86_64-windows-vc-10.0 REM END OF VALUES TO BE SET REM ===================================================================================== REM Microsoft moved 32 bit apps to a new program files directory on 64 bit systems set PROGRAM_FILES_X86=Program Files if exist "C:\Program Files (x86)" set PROGRAM_FILES_X86=Program Files (x86) REM Verify setup by checking for expected files/directories set SETUP_CHECKING=MPC_ROOT=%MPC_ROOT% if not exist "%MPC_ROOT%\mpc.pl" goto setup_is_bad set SETUP_CHECKING=BOOST_ROOT=%BOOST_ROOT% if not exist "%BOOST_ROOT%\boost" goto setup_is_bad REM if you are not running unit tests, delete the REM on the next line REM goto noBoost set SETUP_CHECKING=BOOST_ROOT lib=%BOOST_ROOT%\lib if not exist "%BOOST_ROOT%\lib" goto setup_is_bad :noBoost REM If you are not building the pub/sub app remove the REM on the next line REM goto noPubSub set SETUP_CHECKING=QUICKFAST_ROOT contains QuickFASTApplication.mpb if not exist %QUICKFAST_ROOT%\QuickFASTApplication.mpb goto setup_is_bad set SETUP_CHECKING=XERCESROOT=%XERCESROOT% if not exist "%XERCESROOT%\lib" goto setup_is_bad :noPubSub REM Find visual studio. REM You can short-circuit this by setting VCVER before running this REM However this also skips the check to see if VC is installed in the expected place rem if VCVER is already set, skip the discovery if not "a" == "a%VCVER%" goto setup_is_ok set SETUP_CHECKING=Setup checking visual studio common tools set VCVER=15 set SETUP_CHECKING=VS150COMNTOOLS=%VS150COMNTOOLS% set VS_COMMON_TOOLS=%VS150COMNTOOLS% set VC_ROOT=C:\%PROGRAM_FILES_X86%\Microsoft Visual Studio 15.0\VC\bin if exist "%VS_COMMON_TOOLS%VSVARS32.BAT" goto setup_is_ok set VCVER=14 set SETUP_CHECKING=VS140COMNTOOLS=%VS140COMNTOOLS% set VS_COMMON_TOOLS=%VS140COMNTOOLS% set VC_ROOT=C:\%PROGRAM_FILES_X86%\Microsoft Visual Studio 14.0\VC\bin if exist "%VS_COMMON_TOOLS%VSVARS32.BAT" goto setup_is_ok set VCVER=13 set SETUP_CHECKING=VS130COMNTOOLS=%VS130COMNTOOLS% set VS_COMMON_TOOLS=%VS130COMNTOOLS% set VC_ROOT=C:\%PROGRAM_FILES_X86%\Microsoft Visual Studio 13.0\VC\bin if exist "%VS_COMMON_TOOLS%VSVARS32.BAT" goto setup_is_ok set VCVER=12 set SETUP_CHECKING=VS120COMNTOOLS=%VS120COMNTOOLS% set VS_COMMON_TOOLS=%VS130COMNTOOLS% set VC_ROOT=C:\%PROGRAM_FILES_X86%\Microsoft Visual Studio 12.0\VC\bin if exist "%VS_COMMON_TOOLS%VSVARS32.BAT" goto setup_is_ok set VCVER=11 set SETUP_CHECKING=VS110COMNTOOLS=%VS110COMNTOOLS% set VS_COMMON_TOOLS=%VS110COMNTOOLS% set VC_ROOT=C:\%PROGRAM_FILES_X86%\Microsoft Visual Studio 11.0\VC\bin if exist "%VS_COMMON_TOOLS%VSVARS32.BAT" goto setup_is_ok set VCVER=10 set SETUP_CHECKING=VS100COMNTOOLS=%VS100COMNTOOLS% set VS_COMMON_TOOLS=%VS100COMNTOOLS% set VC_ROOT=C:\%PROGRAM_FILES_X86%\Microsoft Visual Studio 10.0\VC\bin if exist "%VS_COMMON_TOOLS%VSVARS32.BAT" goto setup_is_ok set VCVER=9 set SETUP_CHECKING=VS90COMNTOOLS=%VS90COMNTOOLS% set VS_COMMON_TOOLS=%VS90COMNTOOLS% set VC_ROOT=C:\%PROGRAM_FILES_X86%\Microsoft Visual Studio 9.0\VC\bin if exist "%VS_COMMON_TOOLS%VSVARS32.BAT" goto setup_is_ok set VCVER=8 set SETUP_CHECKING=VS80COMNTOOLS=%VS80COMNTOOLS% set VS_COMMON_TOOLS=%VS80COMNTOOLS% set VC_ROOT=C:\%PROGRAM_FILES_X86%\Microsoft Visual Studio 8.0\VC\bin if exist "%VS_COMMON_TOOLS%VSVARS32.BAT" goto setup_is_ok REM goto setup_is_bad (you are here) REM Find visual studio. rem if VCVER is already set, skip the discovery if not "a" == "a%VCVER%" goto setup_is_ok REM goto setup_is_bad (you are here) :setup_is_bad ECHO Setup check failed: %SETUP_CHECKING% ECHO Edit the setup.cmd file or change environment variables goto end :setup_is_ok set SETUP_CHECKING= set LIQUIBOOK_ROOT=%~dp0 call "%VS_COMMON_TOOLS%VSVARS32.BAT" >nul REM: This avoids growing PATH and INCLUDE every time setup is run if "a" == "a%BASE_PATH%" set BASE_PATH=%PATH% if "a" == "a%BASE_INCLUDE%" set BASE_INCLUDE=%INCLUDE% set RELEASE_PATH=%LiquiBook_ROOT%\bin;%LiquiBook_ROOT%\Output\Release;%MPC_ROOT%;%BOOST_ROOT%\lib;%LiquiBook_ROOT%\lib;%BASE_PATH% set DEBUG_PATH=%LiquiBook_ROOT%\bin;%LiquiBook_ROOT%\Output\Debug;%MPC_ROOT%;%BOOST_ROOT%\lib;%LiquiBook_ROOT%\lib;%BASE_PATH% if "a" == "a%QUICKFAST_ROOT%" goto noQuickFAST set RELEASE_PATH=%RELEASE_PATH%;%QUICKFAST_ROOT%\Output\Release set DEBUG_PATH=%DEBUG_PATH%;%QUICKFAST_ROOT%\Output\Debug :noQuickFAST if "a" == "a%XERCESROOT%" goto noXERCES set RELEASE_PATH=%RELEASE_PATH%;%XERCESROOT%\bin set DEBUG_PATH=%DEBUG_PATH%;%XERCESROOT%\bin :noXERCES set PATH=%DEBUG_PATH% set INCLUDE=%BOOST_ROOT%\include;%BASE_INCLUDE% title LiquiBook :end ================================================ FILE: winenv_clear.bat ================================================ @REM Copyright (c) 2009, 2010 Object Computing, Inc. @REM All rights reserved. @REM See the file license.txt for licensing information. REM This undefines the variables set by Setup to make it easy to rerun REM setup after making changes. It should not be needed unless you are REM editing the setup.cmd file. set MPC_ROOT= set XERCESROOT= set XERCES_LIBNAME= set BOOST_VERSION= set BOOST_ROOT= set QUICKFAST_ROOT=