{binance} Spot Trading: Liquidity

In previous posts we looked at creating market orders and limit orders with {binance}. We saw a couple of successful trades. However, sometimes trades are not successful and the orders are not filled. Let’s try to understand why.

The setup will be the same as the previous posts and we’ll use the Binance testnet. Let’s take a look at our testnet account balance.

spot_account_balances()
# A tibble: 8 × 3
  asset      free locked
  <chr>     <dbl>  <dbl>
1 BNB      971.       0 
2 BTC        1.41     0 
3 BUSD   37438.     116.
4 ETH       89.3      0 
5 LTC      500        0 
6 TRX   498722.       0 
7 USDT   10111.       0 
8 XRP    50000        0 

Expired Orders 

We’ve got plenty of TRX. Let’s try to sell 5000 TRX in exchange for USDT.

spot_new_market_order(symbol = "TRXUSDT", side = "SELL", quantity = 5000)
# A tibble: 1 × 11
  symbol  order_id transact_time       price orig_qty exec_qty status  time_in_force type   side
  <chr>      <int> <dttm>              <dbl>    <dbl>    <dbl> <chr>   <chr>         <chr>  <chr>
1 TRXUSDT      650 2021-11-07 07:11:42     0     5000        0 EXPIRED GTC           MARKET SELL

The order was not successful (status is EXPIRED). What went wrong? It’s not like we don’t have enough TRX. The problem is that there’s nobody who wants to buy our TRX with USDT! A successful trade requires two parties: a seller and a buyer.

Order Book 

The order book reflects the orders which are waiting to be executed. Let’s retrieve the order book for all pairs on the testnet.

order_book <- market_price_ticker() %>% pull(symbol) %>% market_order_book()
# A tibble: 20 × 4
   symbol  last_update_id bids              asks             
   <chr>            <int> <list>            <list>           
 1 BNBBUSD              3 <tibble [1 × 2]>  <tibble [0 × 2]> 
 2 BTCBUSD             66 <tibble [1 × 2]>  <tibble [1 × 2]> 
 3 ETHBUSD              4 <tibble [1 × 2]>  <tibble [0 × 2]> 
 4 LTCBUSD              2 <tibble [0 × 2]>  <tibble [0 × 2]> 
 5 TRXBUSD             40 <tibble [1 × 2]>  <tibble [0 × 2]> 
 6 XRPBUSD              2 <tibble [0 × 2]>  <tibble [0 × 2]> 
 7 BNBUSDT         432661 <tibble [15 × 2]> <tibble [3 × 2]> 
 8 BTCUSDT         634251 <tibble [44 × 2]> <tibble [20 × 2]>
 9 ETHUSDT           2352 <tibble [9 × 2]>  <tibble [10 × 2]>
10 LTCUSDT              8 <tibble [0 × 2]>  <tibble [0 × 2]> 
11 TRXUSDT             50 <tibble [0 × 2]>  <tibble [0 × 2]> 
12 XRPUSDT             18 <tibble [0 × 2]>  <tibble [0 × 2]> 
13 BNBBTC               8 <tibble [1 × 2]>  <tibble [0 × 2]> 
14 ETHBTC              22 <tibble [1 × 2]>  <tibble [0 × 2]> 
15 LTCBTC               2 <tibble [0 × 2]>  <tibble [0 × 2]> 
16 TRXBTC               2 <tibble [0 × 2]>  <tibble [0 × 2]> 
17 XRPBTC               3 <tibble [0 × 2]>  <tibble [0 × 2]> 
18 LTCBNB               2 <tibble [0 × 2]>  <tibble [0 × 2]> 
19 TRXBNB               2 <tibble [0 × 2]>  <tibble [0 × 2]> 
20 XRPBNB               2 <tibble [0 × 2]>  <tibble [0 × 2]>

The asks are orders from sellers (these orders specify the price they are asking) and the bids are orders from buyers (these orders specify the price that they are bidding). Interesting! So there are a few pairs (like BNB/USDT, BTC/USDT and ETH/USDT) where there is a lot of interest in trading. But other pairs have only a few orders (or none at all!).

Let’s take a closer look at the order book for TRX/USDT and TRX/BUSD.

order_book %>%
  filter(symbol %in% c("TRXUSDT", "TRXBUSD")) %>%
  select(-last_update_id) %>%
  pivot_longer(bids:asks, names_to = "type", values_to = "orders") %>%
  unnest(cols = c(orders))
# A tibble: 4 × 4
  symbol  type  price      qty
  <chr>   <chr> <dbl>    <dbl>
1 TRXBUSD bids  0.15     773.1
2 TRXBUSD bids  0.14     167  
3 TRXUSDT asks  0.1   336429. 
4 TRXUSDT asks  0.102   2000  

There are just bids for TRX/BUSD and only asks for TRX/USDT. This means that there’s nobody interested in buying our TRX for USDT, but there is interest in buying TRX for BUSD.

Filling Orders 

Let’s change our order then, offering to sell 200 TRX for BUSD at the highest bid price.

spot_new_limit_order(symbol = "TRXBUSD", side = "SELL", quantity = 200, price = 0.15)
# A tibble: 1 × 11
  symbol  order_id transact_time       price orig_qty exec_qty status time_in_force type  side
  <chr>      <int> <dttm>              <dbl>    <dbl>    <dbl> <chr>  <chr>         <chr> <chr>
1 TRXBUSD      189 2021-11-07 07:41:13  0.15      200      200 FILLED GTC           LIMIT SELL

Our order was successful (status is FILLED). Checking back on the order book we see that the quantity of the bid has dropped from 773.1 TRX to 573.1 TRX because we sold them 200 TRX.

# A tibble: 2 × 4
  symbol  type  price   qty
  <chr>   <chr> <dbl> <dbl>
1 TRXBUSD bids   0.15 573.1
2 TRXBUSD bids   0.14 167  

Partially Filling Orders 

The previous order was filled completely, meaning that we managed to sell the full quantity that we offered. But what happens if the order can’t be completed fully?

Somebody still wants to buy another 573.1 TRX at a price of 0.15 BUSD per TRX. What happens if we offer to sell a larger amount at that price?

spot_new_limit_order(symbol = "TRXBUSD", side = "SELL", quantity = 1000, price = 0.15)
# A tibble: 1 × 11
  symbol  order_id transact_time       price orig_qty exec_qty status           time_in_force type  side
  <chr>      <int> <dttm>              <dbl>    <dbl>    <dbl> <chr>            <chr>         <chr> <chr>
1 TRXBUSD      190 2021-11-07 07:44:36  0.15     1000    573.1 PARTIALLY_FILLED GTC           LIMIT SELL

Aha! So now the status is PARTIALLY_FILLED. The original quantity for the order (orig_qty) was 1000 TRX but only 573.1 TRX of that has been executed (exec_qty).

Time in Force 

We can be more specific about the way that we want our order to be handled using the time_in_force option. This specifies how long the order is valid and dictates what happens when there are no matching orders in the order book. Possible values for time_in_force are:

  • "GTC" — (Good Til Canceled) order will remain on the book unless canceled;
  • "IOC" — (Immediate Or Cancel) try to fill as much as possible before it expires; or
  • "FOK" — (Fill Or Kill) Order will expire if full order cannot be filled.

Let’s look at the updated order book for TRX/BUSD.

# A tibble: 3 × 4
  symbol  type  price   qty
  <chr>   <chr> <dbl> <dbl>
1 TRXBUSD bids   0.13  1000
2 TRXBUSD asks   0.14  3000
3 TRXBUSD asks   0.15  5000

Somebody is offering to buy 1000 TRX at 0.13 BUSD per TRX. Let’s offer more TRX than they are requesting but specify "FOK" for time_in_force.

spot_new_limit_order(symbol = "TRXBUSD", side = "SELL", quantity = 5000, price = 0.13, time_in_force = "FOK")
# A tibble: 1 × 11
  symbol  order_id transact_time       price orig_qty exec_qty status  time_in_force type  side
  <chr>      <int> <dttm>              <dbl>    <dbl>    <dbl> <chr>   <chr>         <chr> <chr>
1 TRXBUSD      208 2021-11-07 08:10:47  0.13     5000        0 EXPIRED FOK           LIMIT SELL

The full order could not be filled (we offered 5000 TRX but there was only 1000 TRX requested), so it expired. The executed quantity (exec_qty) is zero: no trading happened.

Let’s try again with time_in_force set to "IOC".

spot_new_limit_order(symbol = "TRXBUSD", side = "SELL", quantity = 5000, price = 0.13, time_in_force = "IOC")
# A tibble: 1 × 11
  symbol  order_id transact_time       price orig_qty exec_qty status  time_in_force type  side
  <chr>      <int> <dttm>              <dbl>    <dbl>    <dbl> <chr>   <chr>         <chr> <chr>
1 TRXBUSD      209 2021-11-07 08:10:52  0.13     5000     1000 EXPIRED IOC           LIMIT SELL

That order has also expired but now the executed quantity is 1000 TRX. So, 1000 TRX was requested and we offered 5000 TRX. Of that, 1000 TRX was sold and the balace of our order (the remaining 4000 TRX) was cancelled.

Some time later there’s another 1000 TRX requested at the same price. Let’s try with time_in_force set to "GTC".

spot_new_limit_order(symbol = "TRXBUSD", side = "SELL", quantity = 5000, price = 0.13, time_in_force = "GTC")
# A tibble: 1 × 11
  symbol  order_id transact_time       price orig_qty exec_qty status           time_in_force type  side
  <chr>      <int> <dttm>              <dbl>    <dbl>    <dbl> <chr>            <chr>         <chr> <chr>
1 TRXBUSD      211 2021-11-07 08:11:05  0.13     5000     1000 PARTIALLY_FILLED GTC           LIMIT SELL

Aha! So now the status of the order is PARTIALLY_FILLED. Again the executed quantity is 1000 TRX, meaning that we’ve sold the full quantity requested. However, now, because we specified "GTC", the balance of the order (another 4000 TRX) is still pending. The order persists on the books until either we cancel it or it is filled.


The {binance} package is developed & supported by Fathom Data.