Skip to main content

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.

note

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.
note

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 NameDescription
localWhen 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.).
deltixVendorIdCan define an alternative exchange name (vendor ID) to be used for Deltix Central Security Master API queries.
skipBalanceCheckWhen set to “true”, SOR does not perform “available balance” check for the exchange.
aggressiveOrdersOnlyWhen 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.

  1. The current order book prices are adjusted to include all applicable fees. For example:

    Ask PriceQuantityExchangeFeeAdjusted Ask
    10010Coinbase.25%100.25
    1005Kraken0100
    1011Coinbase.25%101.2525
    1023Bitstamp.15%102.153
  2. The order book is then resorted according to the Adjusted price.

    Ask PriceQuantityExchangeFeeAdjusted Ask
    1005Kraken0100
    10010Coinbase.25%100.25
    1011Coinbase.25%101.2525
    1023Bitstamp.15%102.153
  3. The SOR then applies constraints to filter the available exchanges. These constraints include available balance, order time in force, etc.

  4. 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.

  1. The SOR determines the passive turnover for each exchange over the last 5 seconds. For example:

    ExchangeTurnover
    Coinbase10
    Kraken8
    Bitstamp3
  2. The exchanges are filtered using the same order constraints from aggressive phase #3.

  3. The remaining quantity is then allocated among the remaining exchanges according to the proportion of turnover. For example, remaining quantity of 100:

    ExchangeTurnoverOrder Size
    Coinbase1047.619
    Kraken838.095
    Bitstamp314.286

Market Order execution example

Let's imagine that the LTCUSD OrderBook looks like this (ask side only):

Ask PriceAsk QuantityAsk Exchange
57.5212BITFINEX
57.510.3GDAX
57.5111BITSTAMP
57.5010BITSTAMP
57.490.1GDAX
57.491GEMINI
57.490.2BITFINEX

Imagine that we are executing an order BUY ‘LTCUSD’ 15 (TIF = GTC).

Order execution is done as follows:

  1. Build an adjusted book

    Ask PriceAsk QuantityAsk ExchangeFeeAdjusted Price
    57.510.3GDAX0.002557.65378
    57.490.1GDAX0.002557.63373
    57.5111BITSTAMP0.001557.59627
    57.510BITSTAMP0.001557.58625
    57.5212BITFINEX0.00157.57752
    57.490.2BITFINEX0.00157.54749
    57.491GEMINI0.000557.51875
  2. Filter out exchanges based on other constraints such as balances, etc.  Let us assume there is no change at this step.

  3. 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.

  1. The current order book prices are adjusted to include all applicable fees. For example:

    Ask PriceQuantityExchangeFeeAdjusted Ask
    10010Coinbase.25%100.25
    1005Kraken0100
    1011Coinbase.25%101.2525
    1023Bitstamp.15%102.153
  2. The order book is then resorted according to the adjusted price.

    Ask PriceQuantityExchangeFeeAdjusted Ask
    1005Kraken0100
    10010Coinbase.25%100.25
    1011Coinbase.25%101.2525
    1023Bitstamp.15%102.153
  3. 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 then Coinbase @ 101 and Bitstamp @ 102 are excluded.

  4. Additional constraints are applied to further restrict the available exchanges.  These constraints include available balance, order time in force, etc.

  5. 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.

  1. The SOR determines the passive turnover for each exchange over the last 5 seconds. For example:

    ExchangeTurnover
    Coinbase10
    Kraken8
    Bitstamp3
  2. The exchanges are filtered using the same order constraints from aggressive phase #4.

  3. 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:

    ExchangeTurnoverOrder Size
    Coinbase1047.619
    Kraken838.095
    Bitstamp314.286

Limit Order execution example 1

Let's imagine a LTCUSD OrderBook looks like this (ask side only):

Ask PriceAsk QuantityAsk Exchange
57.5212BITFINEX
57.510.3GDAX
57.5111BITSTAMP
57.5010BITSTAMP
57.490.1GDAX
57.491GEMINI
57.490.2BITFINEX

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.

  1. Step 1 - Build an execution plan for aggressive execution: 0.1 goes to GDAX and 10 goes to BITSTAMP.

  2. 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.

  3. 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 PriceAsk QuantityAsk Exchange
57.5212BITFINEX
57.510.3GDAX
57.5111BITSTAMP
57.5010BITSTAMP
57.490.1GDAX
57.491GEMINI
57.490.2BITFINEX

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.

  1. Step 1 - Build execution plan for aggressive execution. Skip this step because LMT price less than best ask.

  2. 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.

  3. 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 PriceAsk QuantityAsk Exchange
8219.991COINBASE
8221.301.3KRAKEN

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 QuantityAsk ExchangeOriginal Price
8221.301.3KRAKEN8221.30
8240.541COINBASE8219.99

SOR Logic:

  1. 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.
  2. 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

TagField nameTypeReq'dComments
1AccountStringNTrading Order account. All child orders will have the same trading account.
55SymbolStringYThis tag is required. Human-readable instrument name as it is defined in Deltix Security Metadata.
54SideCharY'1'=Buy
'2'=Sell
'5'=Sell Short
'6'=Sell Short Exempt
50SenderSubIDStringYTrader ID
44PricePriceCOrder price (required for LIMIT orders)
38OrderQtyQtyYOrder quantity
76ExecBrokerStringY"SOR" (Deployment key of algo strategy)
40OrdTypeCharYCan 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

TagField nameTypeReq'dComments
7002 EligibleExchangesStringNIf provided can contain comma-separated list of destination exchanges that this order can be routed to. If skipped, then all exchanges are eligible. Each exchange name must match Deltix exchange code
Example: COINBASE,GEMINI,BINANCE.

Starting from Ember 1.7.6 SOR algorithm supports extended format of this attribute that allows to specify exchange account or trade connector to be used by SOR for child orders. In this new extended format each item in the comma separated list can contain additional optional fields separated by column in this format: exchange[:[connector][:accountSource[:accountName]]].
Example: COINBASE::nick:spot, BINANCE:BINANCE-A.
7003AggressiveOnlyBooleanNWhen this flag is enabled (set to ‘Y’) SOR Execution Plan will not perform Passive phase. See SOR documentation for details. Default is ‘N’ (disabled) and hence algorithm will perform both Aggressive and Passive phases.

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 exchange
  • Account – 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
note

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.

Universe Configurator connector screen

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 NameDescription
initialActiveOrdersCacheSizeOptional, int. Initial active orders cache size.
initialClientsCapacityOptional, int. Initial client capacity.
maxInactiveOrdersCacheSizeOptional, int. Maximum inactive orders cache size.
orderCacheCapacityOptional, int. Order cache capacity.
recyclingDisabledOptional, boolean, default value is true. Disables recycling of order instance.
acceptL1DataOptional, boolean, defaults to false. When enabled, SOR will expect level 1 market data instead of level 2.
balancesStreamOptional, string, defaults to null. When specified, it will enable available balance checks based on balance messages in this TimeBase stream.
copyChildEventIdentityOptional, 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>
customAttributeKeysToCopyOptional, 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.
defaultOrderDestinationOptional, string, null by default. Can be used to route orders to this destination with order exchange ID set according to the execution plan.
exchangeConstraintsOptional, 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.
prioritizedExchangesOptional, string with comma separated list of exchanges. Allows to specify exchanges that should have priority when generating execution plan.
smartOrderTransformationOptional, boolean, defaults to false. When enabled, it will allow SOR to change order type and issue LIMIT child orders for the parent MARKET order.
stateMarketDataTimeoutOptional, 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.

BitShort CodeDescription
0NoMarketDataMissing Market Data for this exchange
1StaleMarketDataStale Market Data for this exchange. Market data is considered stale when the time since the last market data message received exceeds configured stateMarketDataTimeout
2ExcludedByUserExchange excluded by user
3UndefinedSymbolAlgorithm can't find instrument metadata for this exchange
4RecentOrderRejectionWe recently received unexplained an order reject from this exchange (it is blacklisted)
5OrderTypeUnsupportedByExchangeOrderType or TimeInForce constraint are not compatible with this exchange
6NotEnoughBalanceWe do not have enough balance to trade on exchange
7MinOrderSizeMinimum 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.