Smart Order Router Algorithm
Smart Order Router (SOR) supports Market and Limit orders. For a given order quantity, SOR builds an execution plan that considers the following factors from each exchange:
- Available liquidity (starting from the top of the book)
- Commissions charged by an exchange (maker/taker commissions, trade volume discounts)
- Available account balance on each exchange
- Recent reject statistics for each exchange
- Order price precision, order quantity precision, minimal order size, etc. per-exchange security metadata
Algorithm Logic
The Smart Order Router algorithm follows a set of rules when looking for trading liquidity on different venues for the most profitable execution of an incoming order.
First, the SOR validates the incoming order. The incoming order could be either a limit or market order. If validation fails, the order is canceled. If validation is passed, the SOR builds an execution plan. For each venue in the eligible list, the plan defines the quantity and price of the DMA order wired to that venue. For more information about eligible exchanges, read next section: How SOR determines which exchanges are eligible or not eligible.
To build an execution plan, the SOR works with source liquidity aggregated across all venues from the eligible list. The venue list sent with an incoming order could override the default list.
In general, SOR planning includes two phases: aggressive and passive. For aggressive execution, the source liquidity is re-sorted by applying exchange specific fees to all quotes/orders from the particular venue.
The quotes from different venues providing equal price (commission adjusted) are further re-sorted using other priority factors such as available liquidity.
In the second pass, the SOR splits residual order quantity across all eligible venues by passive price. The “optimal” plan is built by considering execution fees/rebates for passive execution and current market liquidity/turnover estimates for each venue.
SOR Execution Plan
The execution plan is built based on the current market across all venues. The SOR algorithm uses a consolidated order book that contains exchanges as eligible for trading only. At the beginning, the SOR builds an aggressive execution plan based on price on each level on the opposite book side. For example, if a price on a level is executable for order price, then it is considered aggressive. Otherwise, it is considered passive.
Aggressive execution plan
The SOR process each commission-adjusted price level individually. If there is only one exchange presented on specified price level, then SOR uses all available liquidity restricted by remaining quantity. If the price level contains only one exchange, then all available liquidity restricted by the remaining quantity is used. If there are two or more exchanges in the same price level, the SOR uses the order of the largest available liquidity.
Passive execution plan
A passive execution plan is applied for a residual amount that was not allocated during the building of the aggressive execution plan. The plan is built based on information about passive turnover on each exchange. For example, the SOR assumes that the probability of execution is higher on an exchange where passive turnover is much higher. The SOR allocates a residual amount, in a proportion to the passive fills statistics, that is stored for the last 5 seconds.
Please note that the Passive phase can be disabled if an order has an AggressiveOnly
flag set.
What SOR does if child order is rejected
If a child order that was sent from SOR is rejected, SOR marks the exchange as not eligible and rebuilds the execution plan. After that, the SOR send new order(s) according to the new execution plan.
How SOR determines which exchanges are eligible or not eligible
By default, all exchanges are marked as eligible. The list of eligible exchanges can be overridden by specifying it in the FIX tag 7002 with the order. See more information about the tag EligibleExchanges(7002)
later in this document.
Also, an exchange can be marked as not eligible in the exchange constraints specified in the SOR config. If an exchange does not support type or time in force (TIF) of a SOR order, it is considered not eligible by SOR.
For example, if the exchange GEMINI did not support GOOD_TILL_CANCEL (GTC) TIF, when handling the GTC order, SOR would skip the GEMINI exchange in the execution plan.
The last factor determining exchange eligibility is rejection statistics collected by SOR for an exchange. An exchange is marked as not eligible for the current order if SOR previously received rejections for orders sent to this exchange.
If all exchanges are marked as not eligible, the SOR order is cancelled.
Order Constraints
SOR also takes into account venue specific constraints, such as minimal order increment, minimal order size, and balances. Minimal order increment and minimal order size are defined for each vendor in Deltix Security Metadata. These two parameters are simple: order quantity should be rounded to the minimal order increment and cannot be less than the minimal order size.
Deltix Trading Connectors publish information about available balance for each currency using an account balance API (where available).
When routing a part of an order to a specific venue, SOR verifies that allocated size is within the available balance. This verification is based on the order parameters. For instance, order currency depends on the direction of the order.
Consider a BTC/USD order:
For a BUY order, we are selling USD and buying BTC. Thus, we need to make sure that we have enough USD balance to cover the entire order (or limit the quantity of the order to the amount that we have funds for).
For a SELL order, we are buying USD and selling BTC. Thus, we need to make sure that we have enough BTC to cover the order.
Working with balances
As mentioned above, Deltix Trading Connectors provide balances for each execution venue. By default, if SOR can’t find information about an actual balance for an order currency on the destination exchange, it assumes that there is no balance for the specified currency, i.e., current balance is zero.
Strictly speaking, a special service called Balance Publisher provides balance information per currency/account/exchange via a dedicated TimeBase stream. Exchanges usually have slightly different API when it comes to providing account balance information. The purpose of the Balance Publisher is to normalize this information in the form of the following data structure and provide it to the SOR Algorithm and other Deltix consumers.
Balance message schema:
CLASS "deltix.timebase.api.messages.balance.BalanceMessage" 'Balance Message' (
"account" 'Account' VARCHAR COMMENT 'Can be NULL for exchanges that do not provide per-account balances',
"amount" 'Amount' FLOAT DECIMAL64 ,
"available" 'Available' FLOAT DECIMAL64 ,
"balancePublisherName" 'Balance Publisher Name' VARCHAR COMMENT 'Source of balance information (balance publisher instance). ',
"borrowed" 'Borrowed' FLOAT DECIMAL64,
"customValues" 'Custom Values' ARRAY(OBJECT("deltix.timebase.api.messages.balance.CustomValue")) COMMENT 'UNDOCUMENTED',
"depositing" 'Depositing' FLOAT DECIMAL64,
"exchangeId" 'Exchange Id' VARCHAR ALPHANUMERIC (10) COMMENT 'Order Exchange or ECN (for example "COINBASE" or "XBTO").
This field stores textual exchange code as INT64 using ALPHANUMERIC(10) encoding.
Use ExchangeCodec helper to encode/decode text to numeric.Previously called "Vendor".',
"frozen" 'Frozen' FLOAT DECIMAL64,
"loaned" 'Loaned' FLOAT DECIMAL64,
"withdrawing" 'Withdrawing' FLOAT DECIMAL64
)
Customers are allowed to trade with exchanges bypassing SOR, (for example, placing manual orders via exchange GUI), thus consuming available account balance. The Balance Publisher polls for the actual amount of available balance periodically. However, since most exchanges have API request rate limits, the frequency of balance polling must be weighed against the ability to place or amend frequent trade orders. From this angle, the balance checking performed by SOR is approximate. When racing with other flows that consume balance, it is possible for SOR to emit an order that is rejected by an exchange due to a lack of account balance.
The balance poll period is 10 seconds by default and is not configurable.
By default, the SOR always check balances for execution, but this behavior can be disabled by setting the balances stream parameter to null:
SOR : ${template.algorithm.default} {
factory = "deltix.ember.algorithm.pack.sor.SORAlgorithmFactory"
settings {
balancesStream = null //“balances”
}
}
Balance checking can also be disabled for a specific exchange.
Available balance and future commission
If you are trading close to your balance limit, there may be a situation when an exchange rejects your attempt to consume the entire remaining balance with order size because there was no balance left to pay the commission. To avoid this, keep a small amount of balance available to consume the entire remaining balance with your order.
The following two per-exchange parameters help deal with such situations:
- buyOrderBalanceMarginRatio - An optional value used to estimate the additional quote currency balance that needs to be reserved for BUY orders on an exchange. This value is set to 0.01 (1%) by default and should be set to a value not smaller than the exchange commission ratio in cases when a commission is added to a BUY order's quantity by exchange.
- For example, for a BUY 10 BTCUSD @ 20000 SOR order and default margin ratio 0.01 on COINBASE, and an account balance of 40000 USD, SOR limits the execution plan entry for COINBASE to 40000/20000 * (1 - 0.01) = 1.98 BTCUSD in order to leave a margin of $400 on the account.
- sellOrderBalanceMarginRatio - An optional value used to estimate the additional base currency balance that needs to be reserved for SELL orders on an exchange. This value is set to 0 by default, and should be set to a value not smaller than the exchange commission ratio in cases when a commission is added by the exchange to a SELL order quantity by exchange.
- For example, for a SELL 10 BTCUSD @ 20000 SOR order and sellOrderBalanceMarginRatio set to 0.01 in config for COINBASE, and an account balance of 2 BTC, SOR limits the execution plan entry for COINBASE to 2 BTC * (1 - 0.01) = 1.98 BTCUSD in order to leave a 0.02 BTC margin on the account.
As you can see, SOR doesn’t use official exchange taker fees when estimating the actual balance. For example, right now, the COINBASE low trading tier commission is 0.25%. However, with this feature, SOR uses 1% instead. This allows SOR to stay on the safe side.
How to define list of prioritized exchanges
The SOR algorithm has optional input parameters that define a list of prioritized exchanges. If the SOR has two price levels with an equal price, then an exchange from the prioritized list takes precedence in an execution plan. This list can be defined in the configuration file ember.conf, as shown in the example below:
SOR : ${template.algorithm.default} {
factory = "deltix.ember.algorithm.pack.sor.SORAlgorithmFactory"
settings {
prioritizedExchanges = "GDAX,GEMINI"
}
}
Exchange Constraints
SOR configuration has a special optional section that defines a list of exchange constraints. For example, this section defines what order types are supported by each exchange. Or more precisely - what order types should be used by SOR on each exchange (it could be a subset of supported order types).
Typically, the exchange constraints configuration stanza is large. It makes sense to define it as a separate HOCON variable and reference it in the SOR definition, as shown below:
sorExchangeConstraints =
[
{
exchangeName = "COINBASE",
…
orderTypeDefinitions =
[
{orderType = MARKET, timeInForces = [GOOD_TILL_CANCEL, IMMEDIATE_OR_CANCEL, FILL_OR_KILL]}
{orderType = LIMIT, timeInForces = [GOOD_TILL_CANCEL, IMMEDIATE_OR_CANCEL, FILL_OR_KILL]}
…
]
}
…
]
algorithms {
SOR: ${template.algorithm.default} {
…
settings {
exchangeConstraints = ${sorExchangeConstraints}
}
}
}
In addition to supported order types, the following optional parameters can be defined for each exchange:
Parameter Name | Description |
---|---|
local | When set to "true", instructs SOR to skip Deltix Central Security Master API queries for the exchange. SOR will only use local security metadata stream for per-instrument information (minimum order size, order price and quantity precision, etc.). |
deltixVendorId | Can define an alternative exchange name (vendor ID) to be used for Deltix Central Security Master API queries. |
skipBalanceCheck | When set to “true”, SOR does not perform “available balance” check for the exchange. |
aggressiveOrdersOnly | When set to “true”, SOR does not route “passive” orders to the exchange (will only send aggressive orders). |
See a complete example of a SOR constraint in the Appendix.
Supported Order Types and Time-In-Force conditions
- Whether a balance check should be performed
- Whether an exchange should receive only aggressive (but not passive) orders
Market Order execution
A market order is handled in the following steps.
Step 1: Aggressive Phase
In the aggressive phase, the SOR sends out orders that, at the current time, should be executable.
The current order book prices are adjusted to include all applicable fees. For example:
Ask Price Quantity Exchange Fee Adjusted Ask 100 10 Coinbase .25% 100.25 100 5 Kraken 0 100 101 1 Coinbase .25% 101.2525 102 3 Bitstamp .15% 102.153 The order book is then resorted according to the Adjusted price.
Ask Price Quantity Exchange Fee Adjusted Ask 100 5 Kraken 0 100 100 10 Coinbase .25% 100.25 101 1 Coinbase .25% 101.2525 102 3 Bitstamp .15% 102.153 The SOR then applies constraints to filter the available exchanges. These constraints include available balance, order time in force, etc.
The final list of available exchanges is complete and the child order(s) are sent.
Step 2: Passive Phase
If, after the aggressive phase, there is still a remaining quantity, then this remaining amount is sent out with additional orders in the passive phase.
The SOR determines the passive turnover for each exchange over the last 5 seconds. For example:
Exchange Turnover Coinbase 10 Kraken 8 Bitstamp 3 The exchanges are filtered using the same order constraints from aggressive phase #3.
The remaining quantity is then allocated among the remaining exchanges according to the proportion of turnover. For example, remaining quantity of 100:
Exchange Turnover Order Size Coinbase 10 47.619 Kraken 8 38.095 Bitstamp 3 14.286
Market Order execution example
Let's imagine that the LTCUSD OrderBook looks like this (ask side only):
Ask Price | Ask Quantity | Ask Exchange |
---|---|---|
57.52 | 12 | BITFINEX |
57.51 | 0.3 | GDAX |
57.51 | 11 | BITSTAMP |
57.50 | 10 | BITSTAMP |
57.49 | 0.1 | GDAX |
57.49 | 1 | GEMINI |
57.49 | 0.2 | BITFINEX |
Imagine that we are executing an order BUY ‘LTCUSD’ 15 (TIF = GTC)
.
Order execution is done as follows:
Build an adjusted book
Ask Price Ask Quantity Ask Exchange Fee Adjusted Price 57.51 0.3 GDAX 0.0025 57.65378 57.49 0.1 GDAX 0.0025 57.63373 57.51 11 BITSTAMP 0.0015 57.59627 57.5 10 BITSTAMP 0.0015 57.58625 57.52 12 BITFINEX 0.001 57.57752 57.49 0.2 BITFINEX 0.001 57.54749 57.49 1 GEMINI 0.0005 57.51875 Filter out exchanges based on other constraints such as balances, etc. Let us assume there is no change at this step.
Based on the adjusted book, we would send MARKET child orders as follows:
- 1.0 is sent to the GEMINI exchange
- 12.2 is sent to the BITFINEX exchange
- 1.8 is sent to BITSTAMP
Limit Order execution
A Limit order is handled in these steps:
Step 1: Aggressive Phase
In the aggressive phase, the SOR sends out orders that, at the current time, should be executable.
The current order book prices are adjusted to include all applicable fees. For example:
Ask Price Quantity Exchange Fee Adjusted Ask 100 10 Coinbase .25% 100.25 100 5 Kraken 0 100 101 1 Coinbase .25% 101.2525 102 3 Bitstamp .15% 102.153 The order book is then resorted according to the adjusted price.
Ask Price Quantity Exchange Fee Adjusted Ask 100 5 Kraken 0 100 100 10 Coinbase .25% 100.25 101 1 Coinbase .25% 101.2525 102 3 Bitstamp .15% 102.153 The available exchanges are then restricted based on the order limit price. The limit price is checked against the original unadjusted price. For example, if our order is a
Buy 100
thenCoinbase @ 101
andBitstamp @ 102
are excluded.Additional constraints are applied to further restrict the available exchanges. These constraints include available balance, order time in force, etc.
The final list of available exchanges is complete and then the child order or orders are sent.
Step 2: Passive Phase
ONLY IF ENABLED
If, after the aggressive phase, there is still a remaining quantity, then this remaining amount is sent out with additional orders in the passive phase.
The SOR determines the passive turnover for each exchange over the last 5 seconds. For example:
Exchange Turnover Coinbase 10 Kraken 8 Bitstamp 3 The exchanges are filtered using the same order constraints from aggressive phase #4.
The remaining quantity is then allocated amongst the remaining exchanges according to the proportion of turnover. For example, if we have a remaining quantity of 100:
Exchange Turnover Order Size Coinbase 10 47.619 Kraken 8 38.095 Bitstamp 3 14.286
Limit Order execution example 1
Let's imagine a LTCUSD OrderBook looks like this (ask side only):
Ask Price | Ask Quantity | Ask Exchange |
---|---|---|
57.52 | 12 | BITFINEX |
57.51 | 0.3 | GDAX |
57.51 | 11 | BITSTAMP |
57.50 | 10 | BITSTAMP |
57.49 | 0.1 | GDAX |
57.49 | 1 | GEMINI |
57.49 | 0.2 | BITFINEX |
A new order is received: BUY ‘LTCUSD’ 300 @ 57.5 (TIF = GTC)
.
Based on exchange information, the SOR marks the GEMINI and BITFINEX exchanges as not eligible for trading because these exchanges do not support GTC orders.
Step 1 - Build an execution plan for aggressive execution: 0.1 goes to GDAX and 10 goes to BITSTAMP.
Step 2 – Build an execution plan for passive execution: at the moment turnover GDAX: 7; BITSTAMP: 0. This means that all residual amount should go to the GDAX.
Finally, the SOR sends two orders:
BUY 'LTCUSD' 290 @ 57.5 to GDAX
BUY 'LTCUSD' 10 @ 57.5 to BITSTAMP
Limit Order execution example 2
LTCUSD OrderBook (ask side only):
Ask Price | Ask Quantity | Ask Exchange |
---|---|---|
57.52 | 12 | BITFINEX |
57.51 | 0.3 | GDAX |
57.51 | 11 | BITSTAMP |
57.50 | 10 | BITSTAMP |
57.49 | 0.1 | GDAX |
57.49 | 1 | GEMINI |
57.49 | 0.2 | BITFINEX |
A new order received: BUY ‘LTCUSD’ 300 @ 57.48 (TIF = GTC)
.
Based on exchange information, the SOR marks the GEMINI and BITFINEX exchanges as not eligible for trading because these exchanges do not support GTC orders.
Step 1 - Build execution plan for aggressive execution. Skip this step because LMT price less than best ask.
Step 2 – Build Execution Plan for passive execution: at the moment turnover GDAX: 7; BITSTAMP: 8. The SOR splits the residual amount in proportion to the turnover statistics for each exchange.
Finally, the SOR sends two orders:
BUY 'LTCUSD' 140 @ 57.5 to GDAX
BUY 'LTCUSD' 160 @ 57.5 to BITSTAMP
Limit Order execution example 3
This example shows that the LIMIT price may be a decisive factor.
Let's say a user wants to BUY 1 BTC/USD and the market looks like this:
Ask Price | Ask Quantity | Ask Exchange |
---|---|---|
8219.99 | 1 | COINBASE |
8221.30 | 1.3 | KRAKEN |
Let’s assume COINBASE charges 0.25% commission and let’s imagine KRAKEN commission is 0%.
As the first step, SOR builds an Adjusted Order book that reflects prices with commission:
Ask Price* | Ask Quantity | Ask Exchange | Original Price |
---|---|---|---|
8221.30 | 1.3 | KRAKEN | 8221.30 |
8240.54 | 1 | COINBASE | 8219.99 |
SOR Logic:
- SOR takes best ask from the adjusted order book, which is Kraken.
Unfortunately, the original price of 8221.30 exceeds our limit price. SOR has to skip this entry. - SOR takes the next best ask from the adjusted order book. All conditions seem good. We send
BUY 1 @8219.99 to COINBASE
.
Standard SOR Order parameters
Tag | Field name | Type | Req'd | Comments |
---|---|---|---|---|
1 | Account | String | N | Trading Order account. All child orders will have the same trading account. |
55 | Symbol | String | Y | This tag is required. Human-readable instrument name as it is defined in Deltix Security Metadata. |
54 | Side | Char | Y | '1'=Buy '2'=Sell '5'=Sell Short '6'=Sell Short Exempt |
50 | SenderSubID | String | Y | Trader ID |
44 | Price | Price | C | Order price (required for LIMIT orders) |
38 | OrderQty | Qty | Y | Order quantity |
76 | ExecBroker | String | Y | "SOR" (Deployment key of algo strategy) |
40 | OrdType | Char | Y | Can be one of the following: '1' = Market Order '2' = Limit Order, requires tag Price (44) 'X' = Custom order (may or may not include Price (44)). Most Deltix algo orders use order type 'X' but SOR is special. In some cases, it serves as a facade for DMA venues. As such it supports basic order types. |
Custom SOR Order Parameters
Immutable Order Parameters
The following SOR parameters can be set at the point of order entry:
- Instrument Name
- Order Type (Limit/Market)
- Order TIF (IOC/GTC)
- Account
- Trader
- Eligible venues, accounts, trading connectors
Mutable Parameters
The following parameters can be modified during the life of the order:
- Order Price
- Order Qty
Multiple accounts and connectors for an exchange
In some cases, SOR may have to use more than one account configured for an exchange. In some other cases multiple connectors may be defined (e.g. Spot vs Margin Trading APIs for BINANCE).
Starting from Ember 1.7.6, SOR supports an extended format for the EligibleExchanges(7002)
field. This field is a comma-separated list of exchanges that the order may be routed to. In extended form, each exchange may have an optional suffix component that specifies what trading connector, balance publisher ID, and account to use. More formally, the format of each comma-separated entry is:
<Exchange> [“:” [<Connector>] [“:” [<SourceBP> “:” <Account>]]]
Where:
Exchange
– Exchange identifier, in ALPHANUMERIC(10) format. For example, “COINBASE”Connector
– Trading Connector identifier, as it appears in Ember configuration. For example “COINBASE1”.SourceBP
– Identifies balance publisher that provides information about account balance of given exchangeAccount
– Trading account to be used on given exchange
For example:
- *
COINBASE:COINBASE3:nick:spot
- *
COINBASE::nick:spot
- *
COINBASE
Example of a full attribute:
allowedExchnages(7002)=COINBASE:COINBASE3:nick:spot,BINANCE,KRAKEN::nick:spot
ConnectorId
designates the destination for child orders issued by SOR to an exchange with ExchangeId
.
sourceBPID
and account
identify the account that the balance is used for by SOR for the balance check. When these attributes are not specified but the balance check is enabled, SOR uses the account with the largest balance for which it received balance messages.
The sourceBPID
element of the suffix corresponds to the balance publisher name that you typically define in a Market Maker Balances wizard.
Deployment Options
Sequential Execution
When this mode is set, the algorithm runs in a "sequential" mode: child orders are executed one by one. This mode may be helpful in some cases when the link to execution venues is slow or does not support concurrent order submissions. Default is false (SOR submits orders in parallel).
Default Destination
When defined, this parameter ensures that all child orders are routed to the specific destination. For example, SIMULATOR.
Security Metadata Attributes
The following instrument attributes can be defined in the TimeBase security stream:
- minOrderSize
- orderSizePrecision
- takerCommission
- makerCommission
Appendix. SOR Constraints example
A practical example of SOR constraints from the ember.conf configuration file:
sorExchangeConstraints =
[
{
exchangeName = "COINBASE",
aggressiveOrdersOnly = true,
orderTypeDefinitions =
[
{orderType = MARKET, timeInForces = [GOOD_TILL_CANCEL, IMMEDIATE_OR_CANCEL, FILL_OR_KILL]}
{orderType = LIMIT, timeInForces = [GOOD_TILL_CANCEL, IMMEDIATE_OR_CANCEL, FILL_OR_KILL]}
{orderType = STOP, timeInForces = [GOOD_TILL_CANCEL, IMMEDIATE_OR_CANCEL, FILL_OR_KILL]}
]
}
{
exchangeName = "KRAKEN",
//deltixVendorId = "KRAKEN",
orderTypeDefinitions =
[ // Actually KRAKEN does not support the notion of time-in-force except GTD for limit orders (market orders do
{orderType = MARKET, timeInForces = [GOOD_TILL_CANCEL]}
{orderType = LIMIT, timeInForces = [GOOD_TILL_DATE, GOOD_TILL_CANCEL]}
]
}
{
exchangeName = "GEMINI",
aggressiveOrdersOnly = true,
orderTypeDefinitions =
[
{orderType = LIMIT, timeInForces = [DAY, IMMEDIATE_OR_CANCEL]}
]
}
{
exchangeName = "BINANCE",
aggressiveOrdersOnly = true,
orderTypeDefinitions =
[
{orderType = MARKET, timeInForces = [GOOD_TILL_CANCEL, IMMEDIATE_OR_CANCEL, FILL_OR_KILL]}
{orderType = LIMIT, timeInForces = [GOOD_TILL_CANCEL, IMMEDIATE_OR_CANCEL, FILL_OR_KILL]}
{orderType = STOP, timeInForces = [GOOD_TILL_CANCEL, IMMEDIATE_OR_CANCEL, FILL_OR_KILL]}
]
}
{
exchangeName = "OMEGA1DARK"
local = true
aggressiveOrdersOnly = true,
orderTypeDefinitions =
[
{orderType = MARKET, timeInForces = [IMMEDIATE_OR_CANCEL]}
{orderType = LIMIT, timeInForces = [GOOD_TILL_DATE, GOOD_TILL_CANCEL]}
]
}
Configuration
Here is the complete list of supported SOR attributes that can be specified in algorithm settings:
Parameter Name | Description |
---|---|
initialActiveOrdersCacheSize | Optional, int. Initial active orders cache size. |
initialClientsCapacity | Optional, int. Initial client capacity. |
maxInactiveOrdersCacheSize | Optional, int. Maximum inactive orders cache size. |
orderCacheCapacity | Optional, int. Order cache capacity. |
recyclingDisabled | Optional, boolean, default value is true. Disables recycling of order instance. |
acceptL1Data | Optional, boolean, defaults to false. When enabled, SOR will expect level 1 market data instead of level 2. |
balancesStream | Optional, string, defaults to null. When specified, it will enable available balance checks based on balance messages in this TimeBase stream. |
copyChildEventIdentity | Optional, boolean, defaults to false. When enabled, external order ID and event ID from child order trade events will be included in the trade report events issued by SOR for the parent order. Event ID in this case will have this format: <child_trade_event_source_ID>:<child_trade_event_ID> |
customAttributeKeysToCopy | Optional, list of custom attribute keys, empty by default. Attributes in this list will be copied from each new order request and included with all the events returned to client for this order. |
defaultOrderDestination | Optional, string, null by default. Can be used to route orders to this destination with order exchange ID set according to the execution plan. |
exchangeConstraints | Optional, list of LP exchanges constraints. Allows to specify eligible exchanges and their order constraints: supported order types and time in force values. See example above. |
prioritizedExchanges | Optional, string with comma separated list of exchanges. Allows to specify exchanges that should have priority when generating execution plan. |
smartOrderTransformation | Optional, boolean, defaults to false. When enabled, it will allow SOR to change order type and issue LIMIT child orders for the parent MARKET order. |
stateMarketDataTimeout | Optional, duration, defaults to 5 minutes (5m). If the time since the last market data message received from exchange exceeds this timeout, exchange market data will be considered stale and will not be used by SOR. |
Per-exchange reject codes
Sample order rejection message:
Can't build execution plan. (DLTXMM:5,BITMEX:5,UATX:5,BINANCE:64,B_NEST_S:5,BINANCEFUT:5,BITSTAMP:5,BITFINEX:4,KRAKEN:5,COINBASE:5,LMAXDG:5,LMAXMAKER:5,LMAXMKR:5,QUOINE:5,ALPHAPOINT:5,GEMINI:5,HITBTC:5,POLONIEX:5,BITTREX:5,HUOBI:4,OKEX:4,DERIBIT:5,BEAXY:5,SEEDCX:5,FTX:5,ASCENDEX:5,KUCOIN:4,COINLIST:5)
SOR uses bitmasks when reporting per-exchange reject codes.
Bit | Short Code | Description |
---|---|---|
0 | NoMarketData | Missing Market Data for this exchange |
1 | StaleMarketData | Stale Market Data for this exchange. Market data is considered stale when the time since the last market data message received exceeds configured stateMarketDataTimeout |
2 | ExcludedByUser | Exchange excluded by user |
3 | UndefinedSymbol | Algorithm can't find instrument metadata for this exchange |
4 | RecentOrderRejection | We recently received unexplained an order reject from this exchange (it is blacklisted) |
5 | OrderTypeUnsupportedByExchange | OrderType or TimeInForce constraint are not compatible with this exchange |
6 | NotEnoughBalance | We do not have enough balance to trade on exchange |
7 | MinOrderSize | Minimum order size restriction from this exchange affected execution |
For example: BITMEX:5
means that for BITMEX, the exchange binary error mask is 00000101
(bits 0 and 2): NoMarketData
and ExcludedByUser
.
Similarly BINANCE:64
would mean that for a BINANCE error, the mask is 01000000
(bit 6): NotEnoughBalance
.