Aussie AI
Chapter 6. Orders
-
Book Excerpt from "C++ Ultra-Low Latency: Multithreading and Low-Level Optimizations"
-
by David Spuler, Ph.D.
Chapter 6. Orders
Orders
Orders are the main low-level data for both an exchange and a trading engine. There are different types of orders:
- Limit orders (buy or sell at a set price)
- Stop orders (buy/sell when a price is reached)
- Market orders (accept the current price)
- Pegged orders
There are two “sides” for every trade, and they have separate orders: buy orders and sell orders. The prices of bid orders are called “bids” and for sell orders the term is “asks.” Or you can just call them “buys” and “sells” if you prefer.
Processing of orders differs for exchanges versus trading engines, but there are many overlaps. An exchange processes a large volume of incoming orders submitted by its trading clients. A trading engine can both submit its own orders, and also tracks other orders via the exchange’s market data feed.
When an order is submitted to an exchange, there are two possibilities:
- Immediate execution
- No matches yet
An immediately executing order is called an “aggressive order” and the exchange should execute it against other waiting orders. If there are no matches, the order goes into the “order book” to wait for possible later execution, in which case it is called a “passive order” while waiting.
The most common scenario is that an exchange has a number of passive orders for each stock or other financial instrument. The prices of buys are lower than the sell prices. All of the buy orders will have prices lower than the lowest sell price, because otherwise a trade would occur. Hence, an important measure is the highest buy price (“max-buy” or “max-bid”) and the lowest selling price (“min-sell” or “min-ask”). The difference between the max-buy and min-sell is the “spread” or “bid-ask spread.”
Market Data Feeds
For order processing by a trading engine, the exchange provides a data feed about all of the buy and sell orders that are currently active on the exchange for each financial instrument. the types of incoming market data feed messages includes:
- Add new order (buy or sell)
- Modify order
- Cancel order
- Trade execution
- Other administrative messages
There are a variety of low-level assumptions about the structure of the incoming data from a market data feed:
- New orders (“adds”) should have a unique id that we haven’t seen before.
- Modify or cancel orders should have an id that was previously added.
- Modify orders can only do certain types of updates (e.g., price changes are usually disallowed and instead the order must cancel and re-submit at the new price level).
Note that these assumptions can fail, and quite often, because a market data feed is usually implemented as a UDP multicast protocol. UDP is an unreliable protocol, where network messages can be lost or delayed, but it’s faster than TCP.
To maintain accuracy, there’s a separate data feed that’s slower and more reliable, usually over TCP. There’s a whole phase of “snapshot synchronization” logic for the entire order book, which uses this separate data feed, and it’s very different from the basic UDP market data feed of incoming transactions.
Order Objects
Orders are usually represented as a simple structure. They are a frequently-used object, and the last thing we want is to have function calls for creating, copying, or moving these objects. Bitwise copying is fine! Here’s an example order of a verbose order structure:
struct Order {
unsigned long timestamp;
int id;
int price; // or: double price;
int qty;
bool is_buy; // Side: Buy or sell order?
};
Note that we will often exclude the “side” data field because we’re often processing buy and sell orders
separately.
For example, an order book usually maintains the bids and asks
at each price level as a separate data structure.
Hence, context tells us whether it’s a buy or sell order object,
and it’s more efficient not to store an explicit field.
Even a small bool field adds more than a single byte because of alignment issues.
We could use inheritance of the different types of order structures,
by having an order base class, and derived buy order and sell order classes.
However, that’s not the typical low-latency coding style,
because to take advantage of such a hierarchy, we’d need to use virtual functions,
which are
elegant but somewhat inefficient.
Note that there’s only minor differences between a “struct” and a “class” in C++.
A structure is really like a class, except the data member default to public,
and a few other obscure difference.
Hence, you can define member functions for a structure to be class-like, or you can make a class have only public data members, like a structure. Just take care with:
- Default constructors
- Copy and move constructors
- Destructors
- Assignment operators (copy/move)
You probably just want the defaults. Also, you don’t want your order objects to contain some other non-trivial data member objects, or you get the same performance problems in those subobjects.
In some data structures, you might want to store a cut-down version of an order. Your internal code probably doesn’t need all of the fields that come in from the exchange data feed messages. For example, in a queue based on “order-of-insertion” you don’t need to store the timestamp. Hence, it’s common to have multiple different types of order objects, with fewer data members and a more compact size.
Integer Prices
Trading engines usually represent price as integers in real-world code. Some of the useful terminology in this regard is:
- Tick — the smallest possible pricing differential (depends on the stock or other instrument).
- Pip — the smallest difference in Forex pricing (foreign currencies).
- Basis point (bp) — the different differential in interest rates (usually a hundredth of a percent or 0.01%).
The value of the tick for a stock or futures contract depends on the individual stock, financial instrument, or asset. The tick for an asset might be $0.01 (i.e., a cent) or a small amount like $0.0001 for low-priced assets, or a relatively large amount such as $0.25 (quarter) for a high-priced asset.
A normal 32-bit “int” or “uint32_t” is usually adequate to represent a price.
Note that the integer representation of a price indicates a multiple of the tick or pip,
rather than the actual dollar price.
Note that size_t is usually a 64-bit unsigned integer, rather than 32-bit,
although it depends on the C++ implementation.
Prices cannot usually be negative (except for Swiss interest rates).
Since UINT_MAX in <climits> is approximately 4.7 billion,
the tick values stored as a 32-bit integer have these properties:
- $0.01 — price up to $47 million in 32-bits.
- $0.25 — price up to $1.175 billion.
- $$0.0001 — price to $470,000 in 32-bits.
To avoid issues with 32-bits, trading engines often use 64-bit integers for all prices,
thereby avoiding the risk of integer overflow
and also allowing for negative pricing.
As required, 64-bits can be used for larger price values than those listed above,
or for more granular pricing
with a smaller tick value.
In such cases, use uint64_t or unsigned long.
On the other hand, 32-bit integer arithmetic is marginally faster than the same operations on 64-bit integers. This also uses double the space, which affects cache lines and other cache locality, so there’s a performance trade-off in terms of both space and time.
The term “tick” is used for all types of financial instruments, but there are other terms. The tick is called a “pip” for Forex currency transactions, which is usually 0.0001 or 0.01, depending on the currency pair. The term “basis point” refers to the smallest difference in interest rates, and is a fraction of a percent (a hundredth of a percent).
The reasons for the use of integers in price representations include:
- Avoids rounding errors in floating-point computations.
- Equality tests or comparisons of
floatordoublecan suffer rounding errors. - Integer arithmetic is faster than floating-point.
Integer quantities. Quantity is also a positive integer, and is usually stored as an unsigned integer. This representation can handle a quantity up to 4.7 billion of an asset at the given price.
Negative quantities are usually invalid, and underflow of an unsigned type would wrap around to a large unsigned integer, resulting in an error. Zero quantity can be used as a representation of a used-up order, or some other marker of a “to-be-deleted-later” order, since a real order cannot have zero quantity.
Timestamp integers.
Timestamps are also an integer,
usually represented as an unsigned long
to allow representation of the large numbers.
Exchanges will tag an order with a timestamp,
and the origination of timestamps in trading systems is
usually a hardware-based timestamp.
Incoming messages will often be tagged with a hardware timestamp
in the NIC of the system.
Consistency Checks on Orders
Incoming order messages can sometimes be lost, delayed, or corrupted. Some of the self-consistency checks to consider include:
- Positive integers for price, quantity, ID, and timestamp.
- Huge integers might be underflow (e.g.,
UINT_MAXis also-1converted). - Timestamps should not be less than the previous one (but it happens with UDP).
At a higher-level, consistency should also apply to the order book data structure. Some consistency checks include:
- ID should be new for add orders.
- ID should already exist for modify or cancel orders.
- Trade executions and subsequent order updates should be consistent.
- If max-buy and min-sell cross, there should be a trade message incoming.
Another common error is that zero quantity (or price) might be used to indicate a finalized trade, a “to-be-deleted-later” efficiency optimization, or an otherwise invalid order. In multithreaded code, that order might be seen before it’s removed. Hence, you need to check for this invalid case everywhere, and I do mean everywhere. Otherwise, you’ll be re-processing zero-quantity orders over and over, because a zero-quantity buy or sell order can always be filled.
|
• Online: Table of Contents • PDF: Free PDF book download • Buy: C++ Ultra-Low Latency |
|
C++ Ultra-Low Latency: Multithreading and Low-Level Optimizations:
Get your copy from Amazon: C++ Ultra-Low Latency |