Disclaimer:
- The symbols and values used here are fictional, and do not imply anything about the actual setup on the live exchange.
- For simplicity, the examples in this document do not include commission.
Smart Order Routing (SOR) allows you to potentially get better liquidity by filling an order with liquidity from other order books with the same base asset and interchangeable quote assets. Interchangeable quote assets are quote assets with fixed 1 to 1 exchange rate, such as stablecoins pegged to the same fiat currency.
Note that even though the quote assets are interchangeable, when selling the base asset you will always receive the quote asset of the symbol in your order.
When you place an order using SOR, it goes through the eligible order books, looks for best price levels for each order book in that SOR configuration, and takes from those books if possible.
Note: If the order using SOR cannot fully fill based on the eligible order books' liquidity, LIMIT IOC
or MARKET
orders will immediately expire, while LIMIT GTC
orders will place the remaining quantity on the order book you originally submitted the order to.
Let's consider a SOR configuration containing the symbols BTCUSDT
, BTCUSDC
and BTCUSDP
, and the following ASK
(SELL
side) order books for those symbols:
BTCUSDT quantity 3 price 30,800
BTCUSDT quantity 3 price 30,500
BTCUSDC quantity 1 price 30,000
BTCUSDC quantity 1 price 28,000
BTCUSDP quantity 1 price 35,000
BTCUSDP quantity 1 price 29,000
If you send a LIMIT GTC BUY
order for BTCUSDT
with quantity=0.5
and price=31000
, you would match with the best SELL price on the BTCUSDT book at 30,500. You would spend 15,250 USDT and receive 0.5 BTC.
If you send a LIMIT GTC BUY
order using SOR for BTCUSDT
with quantity=0.5
and price=31000
, you would match with the best SELL price across all symbols in the SOR, which is BTCUSDC at price 28,000. You would spend 14,000 USDT (not USDC!) and receive 0.5 BTC.
{
"symbol": "BTCUSDT",
"orderId": 2,
"orderListId": -1,
"clientOrderId": "sBI1KM6nNtOfj5tccZSKly",
"transactTime": 1689149087774,
"price": "31000.00000000",
"origQty": "0.50000000",
"executedQty": "0.50000000",
"cummulativeQuoteQty": "14000.00000000",
"status": "FILLED",
"timeInForce": "GTC",
"type": "LIMIT",
"side": "BUY",
"workingTime": 1689149087774,
"fills": [
{
"matchType": "ONE_PARTY_TRADE_REPORT",
"price": "28000.00000000",
"qty": "0.50000000",
"commission": "0.00000000",
"commissionAsset": "BTC",
"tradeId": -1,
"allocId": 0
}
],
"workingFloor": "SOR",
"selfTradePreventionMode": "NONE",
"usedSor": true
}
Using the same order book as Example 1:
BTCUSDT quantity 3 price 30,800
BTCUSDT quantity 3 price 30,500
BTCUSDC quantity 1 price 30,000
BTCUSDC quantity 1 price 28,000
BTCUSDP quantity 1 price 35,000
BTCUSDP quantity 1 price 29,000
If you send a LIMIT GTC BUY
order for BTCUSDT
with quantity=5
and price=31000
, you would:
- match with the 3 BTCUSDT at 30,500, and buy 3 BTC for 91,500 USDT
- then match with the 3 BTCUSDT at 30,800, and buy 2 BTC for 61,600 USDT
In total, you spend 153,100 USDT and receive 5 BTC.
If you send the same LIMIT GTC BUY
order using SOR for BTCUSDT
with quantity=5
and price=31000
, you would:
- match with 1 BTCUSDC at 28,000, and buy 1 BTC for 28,000 USDT
- match with 1 BTCUSDP at 29,000, and buy 1 BTC for 29,000 USDT
- match with 1 BTCUSDC at 30,000, and buy 1 BTC for 30,000 USDT
- match with 3 BTCUSDT at 30,500, and buy 2 BTC for 61,000 USDT
In total, you spend 148,000 USDT and receive 5 BTC.
{
"symbol": "BTCUSDT",
"orderId": 2,
"orderListId": -1,
"clientOrderId": "tHonoNjWfOSaKiTygN3bfY",
"transactTime": 1689146154686,
"price": "31000.00000000",
"origQty": "5.00000000",
"executedQty": "5.00000000",
"cummulativeQuoteQty": "148000.00000000",
"status": "FILLED",
"timeInForce": "GTC",
"type": "LIMIT",
"side": "BUY",
"workingTime": 1689146154686,
"fills": [
{
"matchType": "ONE_PARTY_TRADE_REPORT",
"price": "28000.00000000",
"qty": "1.00000000",
"commission": "0.00000000",
"commissionAsset": "BTC",
"tradeId": -1,
"allocId": 0
},
{
"matchType": "ONE_PARTY_TRADE_REPORT",
"price": "29000.00000000",
"qty": "1.00000000",
"commission": "0.00000000",
"commissionAsset": "BTC",
"tradeId": -1,
"allocId": 1
},
{
"matchType": "ONE_PARTY_TRADE_REPORT",
"price": "30000.00000000",
"qty": "1.00000000",
"commission": "0.00000000",
"commissionAsset": "BTC",
"tradeId": -1,
"allocId": 2
},
{
"matchType": "ONE_PARTY_TRADE_REPORT",
"price": "30500.00000000",
"qty": "2.00000000",
"commission": "0.00000000",
"commissionAsset": "BTC",
"tradeId": -1,
"allocId": 3
}
],
"workingFloor": "SOR",
"selfTradePreventionMode": "NONE",
"usedSor": true
}
Using the same order book as Example 1 and 2:
BTCUSDT quantity 3 price 30,800
BTCUSDT quantity 3 price 30,500
BTCUSDC quantity 1 price 30,000
BTCUSDC quantity 1 price 28,000
BTCUSDP quantity 1 price 35,000
BTCUSDP quantity 1 price 29,000
If you send a MARKET BUY
order for BTCUSDT
using SOR with quantity=11
, there is only 10 BTC in total available across all eligible order books. Once all the order books in SOR configuration have been exhausted, the remaining quantity of 1 expires.
{
"symbol": "BTCUSDT",
"orderId": 2,
"orderListId": -1,
"clientOrderId": "jdFYWTNyzplbNvVJEzQa0o",
"transactTime": 1689149513461,
"price": "0.00000000",
"origQty": "11.00000000",
"executedQty": "10.00000000",
"cummulativeQuoteQty": "305900.00000000",
"status": "EXPIRED",
"timeInForce": "GTC",
"type": "MARKET",
"side": "BUY",
"workingTime": 1689149513461,
"fills": [
{
"matchType": "ONE_PARTY_TRADE_REPORT",
"price": "28000.00000000",
"qty": "1.00000000",
"commission": "0.00000000",
"commissionAsset": "BTC",
"tradeId": -1,
"allocId": 0
},
{
"matchType": "ONE_PARTY_TRADE_REPORT",
"price": "29000.00000000",
"qty": "1.00000000",
"commission": "0.00000000",
"commissionAsset": "BTC",
"tradeId": -1,
"allocId": 1
},
{
"matchType": "ONE_PARTY_TRADE_REPORT",
"price": "30000.00000000",
"qty": "1.00000000",
"commission": "0.00000000",
"commissionAsset": "BTC",
"tradeId": -1,
"allocId": 2
},
{
"matchType": "ONE_PARTY_TRADE_REPORT",
"price": "30500.00000000",
"qty": "3.00000000",
"commission": "0.00000000",
"commissionAsset": "BTC",
"tradeId": -1,
"allocId": 3
},
{
"matchType": "ONE_PARTY_TRADE_REPORT",
"price": "30800.00000000",
"qty": "3.00000000",
"commission": "0.00000000",
"commissionAsset": "BTC",
"tradeId": -1,
"allocId": 4
},
{
"matchType": "ONE_PARTY_TRADE_REPORT",
"price": "35000.00000000",
"qty": "1.00000000",
"commission": "0.00000000",
"commissionAsset": "BTC",
"tradeId": -1,
"allocId": 5
}
],
"workingFloor": "SOR",
"selfTradePreventionMode": "NONE",
"usedSor": true
}
Let's consider a SOR configuration containing the symbols BTCUSDT
, BTCUSDC
and BTCUSDP
and the following BID
(BUY
side) order book for those symbols:
BTCUSDT quantity 5 price 29,500
BTCUSDC quantity 5 price 35,000
BTCUSDC quantity 5 price 30,000
BTCUSDP quantity 5 price 28,000
If you send a LIMIT GTC SELL
order for BTCUSDT
with price=29000
and quantity=10
, you would sell 5 BTC and receive 147,500 USDT. Since there is no better price available on the BTCUSDT book, the remaining (unfilled) quantity of the order will rest there at the price of 29,000.
If you send a LIMIT GTC SELL
order using SOR for BTCUSDT
, you would:
- match with 5 BTCUSDC at 35,000 and sell 5 BTC for 175,000 USDT
- match with 5 BTCUSDC at 30,000 and sell 5 BTC for 150,000 USDT
In total, you sell 10 BTC and receive 325,000 USDT.
{
"symbol": "BTCUSDT",
"orderId": 1,
"orderListId": -1,
"clientOrderId": "W1iXSng1fS77dvanQJDGA5",
"transactTime": 1689147920113,
"price": "29000.00000000",
"origQty": "10.00000000",
"executedQty": "10.00000000",
"cummulativeQuoteQty": "325000.00000000",
"status": "FILLED",
"timeInForce": "GTC",
"type": "LIMIT",
"side": "SELL",
"workingTime": 1689147920113,
"fills": [
{
"matchType": "ONE_PARTY_TRADE_REPORT",
"price": "35000.00000000",
"qty": "5.00000000",
"commission": "0.00000000",
"commissionAsset": "USDT",
"tradeId": -1,
"allocId": 0
},
{
"matchType": "ONE_PARTY_TRADE_REPORT",
"price": "30000.00000000",
"qty": "5.00000000",
"commission": "0.00000000",
"commissionAsset": "USDT",
"tradeId": -1,
"allocId": 1
}
],
"workingFloor": "SOR",
"selfTradePreventionMode": "NONE",
"usedSor": true
}
Summary: The goal of SOR is to potentially access better liquidity across order books with interchangeable quote assets. Better liquidity access can fill orders more fully and at better prices during an order's taker phase.
You can find the current SOR configuration in Exchange Information (GET /api/v3/exchangeInfo
for Rest, and exchangeInfo
on Websocket API).
"sors": [
{
"baseAsset": "BTC",
"symbols": [
"BTCUSDT",
"BTCUSDC",
"BTCUSDP"
]
}
]
The sors
field is optional.
It is omitted in responses if SOR is not available.
On the Rest API, the request is POST /api/v3/sor/order
.
On the WebSocket API, the request is sor.order.place
.
This is a term used to determine where the order's last activity occurred (filling, expiring, or being placed as new, etc.).
If the workingFloor
is SOR
, this means your order interacted with other eligible order books in the SOR configuration.
If the workingFloor
is EXCHANGE
, this means your order interacted on the order book that you sent that order to.
matchType
field indicates a non-standard order fill.
When your order is filled by SOR, you will see matchType: ONE_PARTY_TRADE_REPORT
, indicating that you did not trade directly on the exchange (tradeId: -1
). Instead your order is filled by allocations.
allocId
field identifies the allocation so that you can query it later.
An allocation is a transfer of an asset from the exchange to your account. For example, when SOR takes liquidity from eligible order books, your order is filled by allocations. In this case you don't trade directly, but rather receive allocations from SOR corresponding to the trades made by SOR on your behalf.
[
{
"symbol": "BTCUSDT", // Symbol the order was submitted to
"allocationId": 0,
"allocationType": "SOR",
"orderId": 2,
"orderListId": -1,
"price": "30000.00000000", // Price of the fill
"qty": "5.00000000", // Quantity of the fill
"quoteQty": "150000.00000000",
"commission": "0.00000000",
"commissionAsset": "BTC",
"time": 1688379272280, // Time the allocation occurred
"isBuyer": true,
"isMaker": false,
"isAllocator": false
}
]
You can find them the same way you query any other order. The main difference is that in the response for an order that used SOR there are two extra fields: usedSor
and workingFloor
.
When SOR orders trade against order books other than the symbol submitted with the order, the order is filled with an allocation and not a trade. Orders placed with SOR can potentially have both allocations and trades.
In the API response, you can review the fills
fields. Allocations have an allocId
and "matchType": "ONE_PARTY_TRADE_REPORT"
, while trades will have a non-negative tradeId
.
Allocations can be queried using GET /api/v3/myAllocations
(Rest API) or myAllocations
(WebSocket API).
Trades can be queried using GET /api/v3/myTrades
(Rest API) or myTrades
(WebSocket API).