Bitfinex API: Order Books & Checksums

The Bitfinex order books provide a real time overview of market interest by listing all pubic bids and asks. On bitfinex.com, this data is visualized in the lower, center section of each trading page.

The order books are also available via Bitfinex’s various APIs, and today Bitfinex is unveiling a new feature that will help API users ensure their books are always in sync with the market.

API cover

To access the order books, you may request it via REST or WebSockets. The following guide breaks down how to interact with both the REST and WebSocket interfaces.

Furthermore, this guide introduces a new WebSockets feature: checksums, which can be used to ensure that your order book remains timely and up to date.


REST

Using the REST API “book” endpoint is great for those who need a single snapshot of the book. There are two versions of REST available, V1 and V2 (V2 is recommended):

v1

To receive the book send a GET request to: https://api.bitfinex.com/v1/book/SYMBOL

(documentation) where SYMBOL is the pair you are inquiring about, i.e., BTCUSD, ETHUSD, LTCUSD, etc.

The following is an example written in JavaScript:

const request = require('request')
request.get('https://api.bitfinex.com/v1/book/SYMBOL/book/btcusd',
  function(error, response, body) {
    console.log(body);
})

 

This will return the following response:

{
  "bids":[{
    "price":"574.61",
    "amount":"0.1439327",
    "timestamp":"1472506127.0"
  }],
  "asks":[{
    "price":"574.62",
    "amount":"19.1334",
    "timestamp":"1472506126.0"
  }]
}

 

The default size of the book is 25 per side. To increase/limit asks or bids you may pass limit_asks and/or limit_bids within the url.

For example: https://api.bitfinex.com/v1/book/SYMBOL?limit_asks=100&limit_bids=100

v2

If you need the full book, it may be most useful to use v2 with different price precisions P0, P1, P2, P3. To receive book, send a GET request to: https://api.bitfinex.com/v2/book/SYMBOL/PRECISION

Where SYMBOL is the symbol you are inquiring about, i.e., tBTCUSD, tETHUSD, tLTCUSD and PRECISION is the desired precision – P0 being the most precise, P3 being the least precise.

The following is an example written in JavaScript:

const request = require('request')
request.get('https://api.bitfinex.com/v2/book/tBTCUSD/P0',
  function(error, response, body) {
    console.log(body);
})

 

This will return a response as such:

[
  [
    PRICE,
    COUNT,
    AMOUNT
  ]
]

You will find that the response is returned in a list. Bids have a positive amount, asks have a negative amount.

WebSockets

If you feel that you need a constant stream of updates, WebSockets is the tool for the job.

To use WebSockets, start by connecting to wss://api.bitfinex.com/ws/2.

const WS = require('ws')
ws = new WS('wss://api.bitfinex.com/ws/2')

 

On open, send an subscribe event with your favorite pair and precision:

ws.on('open', function open () {
  ws.send(JSON.stringify({ event: 'subscribe', channel: 'book', pair: 'tBTCUSD', prec: 'P0' }))
})

 

Now a stream of updates and you can process them as such:

ws.on('message', function (msg) {
  console.log('New message: ', msg)
})

New Feature: WebSocket Checksums

The WebSockets v2 book API (documentation) now supports the option to request a checksum after each book change. The checksum is a CRC32 value and covers the first 25 bids and 25 asks. By calculating your own checksum and comparing it to the value provided, you can verify that your data is correct and up to date. Below we introduce the basics of requesting and applying checksums.
First, connect to the Bitfinex WebSocket:

const WS = require('ws')
const ws = new WS('wss://api.bitfinex.com/ws/2')

 

On open, send message with event: 'conf' and ‘flag: 131072', with your subscribe message:

ws.on('open', function open () {
  ws.send(JSON.stringify({ event: 'conf', flags: 131072 }))
  ws.send(JSON.stringify({ event: 'subscribe', channel: 'book', pair: pair, prec: 'P0' }))
})

 

A checksum message will be sent every book iteration:

[ CHAIN_ID, 'cs', CHECKSUM ]

where CHECKSUM is a signed integer.

 

Finally, create a string that represents your book, use a CRC-32 library (in this case Node) to create the checksum value, and then compare it to the checksum returned by the WebSocket. The following is a quick snippet of said steps:

const csdata = []
const bidsKeys = BOOK.psnap['bids']
const asksKeys = BOOK.psnap['asks']

for (let i = 0; i < 25; i++) {
  if (bidsKeys[i]) {
    const price = bidsKeys[i]
    const pp = BOOK.bids[price]
    csdata.push(pp.price, pp.amount)
  }
  if (asksKeys[i]) {
    const price = asksKeys[i]
    const pp = BOOK.asks[price]
    csdata.push(pp.price, -pp.amount)
  }
}

const csStr = csdata.join(':')
const csCalc = CRC.str(csStr)

if (csCalc !== checksum) {
  console.error('CHECKSUM_FAILED')
}

NOTE: It is important that you recreate your book string in the same format the checksum was created. For example:

If you had bids [{ price: 6000, amount: 1 }, { price: 5900, amount: 2 }] and asks: [{ price: 6100, amount: -3 }, { price: 6200, amount: -4 }], your checksum string would be 6000:1:6100:-3:5900:2:6200:-4.

 

By comparing your calculated checksum and the one provided by the API, you can verify that your local data is correct. If the values diverge, you’ll know that you need to get a new order book snapshot, re-subscribe to the channel, or inspect your implementation of the API.

The checksum allows you to rest easy by always knowing that your books are up to date and valid.


WebSocket with Checksums – An Example

Feel free to use the following example as a starting point in your own Bitfinex tool.

 

const WS = require('ws')
const CRC = require('crc-32')
const _ = require('lodash')

const BOOK = {}

// connect to websocket
const ws = new WS('wss://api.bitfinex.com/ws/2')

// handle connect
ws.on('open', function open () {
  BOOK.bids = {}
  BOOK.asks = {}
  BOOK.psnap = {}
  BOOK.mcnt = 0

  // send websocket conf event with checksum flag
  ws.send(JSON.stringify({ event: 'conf', flags: 131072 }))

  // send subscribe to get desired book updates
  ws.send(JSON.stringify({ event: 'subscribe', channel: 'book', pair: 'tBTCUSD', prec: 'P0' }))
})

// handle incoming messages
ws.on('message', function (msg) {
  msg = JSON.parse(msg)
  if (msg.event) return
  if (msg[1] === 'hb') return

  // if msg contains checksum, perform checksum
  if (msg[1] === 'cs') {
    const checksum = msg[2]
    const csdata = []
    const bidsKeys = BOOK.psnap['bids']
    const asksKeys = BOOK.psnap['asks']

    // collect all bids and asks into an array
    for (let i = 0; i < 25; i++) {
      if (bidsKeys[i]) {
        const price = bidsKeys[i]
        const pp = BOOK.bids[price]
        csdata.push(pp.price, pp.amount)
      }
      if (asksKeys[i]) {
        const price = asksKeys[i]
        const pp = BOOK.asks[price]
        csdata.push(pp.price, -pp.amount)
      }
    }

    // create string of array to compare with checksum
    const csStr = csdata.join(':')
    const csCalc = CRC.str(csStr)
    if (csCalc !== checksum) {
      console.error('CHECKSUM FAILED')
      process.exit(-1)
    } else {
      console.log('Checksum: ' + checksum + ' success!')
    }
    return
  }

  // handle book. create book or update/delete price points
  if (BOOK.mcnt === 0) {
    _.each(msg[1], function (pp) {
      pp = { price: pp[0], cnt: pp[1], amount: pp[2] }
      const side = pp.amount >= 0 ? 'bids' : 'asks'
      pp.amount = Math.abs(pp.amount)
      BOOK[side][pp.price] = pp
    })
  } else {
    msg = msg[1]
    const pp = { price: msg[0], cnt: msg[1], amount: msg[2] }

    // if count is zero, then delete price point
    if (!pp.cnt) {
      let found = true

      if (pp.amount > 0) {
        if (BOOK['bids'][pp.price]) {
          delete BOOK['bids'][pp.price]
        } else {
          found = false
        }
      } else if (pp.amount < 0) {
        if (BOOK['asks'][pp.price]) {
          delete BOOK['asks'][pp.price]
        } else {
          found = false
        }
      }

      if (!found) {
        console.error('Book delete failed. Price point not found')
      }
    } else {
      // else update price point
      const side = pp.amount >= 0 ? 'bids' : 'asks'
      pp.amount = Math.abs(pp.amount)
      BOOK[side][pp.price] = pp
    }

    // save price snapshots. Checksum relies on psnaps!
    _.each(['bids', 'asks'], function (side) {
      const sbook = BOOK[side]
      const bprices = Object.keys(sbook)
      const prices = bprices.sort(function (a, b) {
        if (side === 'bids') {
          return +a >= +b ? -1 : 1
        } else {
          return +a <= +b ? -1 : 1
        }
      })
      BOOK.psnap[side] = prices
    })
  }
  BOOK.mcnt++
})
Tags:
No Comments

Sorry, the comment form is closed at this time.