Как доверять своей книге заказов

Для любого серьезного трейдера мониторинг книги заказов имеет решающее значение для анализа рынка. Сегодня Bitfinex рада объявить о новом инструменте, который поможет пользователям API синхронизировать свои книги с быстро меняющимся рынком.

Это руководство предоставит обзор доступа к книгам заказов Bitfinex через наши API-интерфейсы и объяснит, как использовать наш последний инструмент синхронизации: контрольные суммы.

В следующем руководстве рассказывается, как взаимодействовать с интерфейсами REST и WebSocket, а также как использовать новый инструмент контрольной суммы.

ОТДЫХАТЬ

Использование конечной точки «книги» REST API отлично подходит для тех, кому нужен единственный снимок книги. Доступны две версии REST: V1 и V2 (рекомендуется V2):

V1

Чтобы получить книгу, отправьте GET-запрос по адресу: https://api.bitfinex.com/v1/book/SYMBOL

(Документация), где СИМВОЛ - это пара, о которой вы спрашиваете, например, BTCUSD, ETHUSD, LTCUSD и т. Д.

Ниже приведен пример, написанный на JavaScript:

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

Что вернет следующий ответ:

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

Размер книги по умолчанию - 25 с каждой стороны. Чтобы увеличить / ограничить запросы или ставки, вы можете передать limit_asks и / или limit_bids в URL-адресе.

Например: https://api.bitfinex.com/v1/book/SYMBOL?limit_asks=100&limit_bids=100

V2

Если вам нужна полная книга, возможно, будет наиболее полезно использовать v2 с другой точностью цен P0, P1, P2, P3. Чтобы получить книгу, отправьте запрос GET на адрес: https://api.bitfinex.com/v2/book/SYMBOL/PRECISION

Где SYMBOL - это символ, о котором вы спрашиваете, то есть tBTCUSD, tETHUSD, tLTCUSD и PRECISION - желаемая точность: P0 является наиболее точным, а P3 - наименее точным.

Ниже приведен пример, написанный на JavaScript:

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

Что вернет следующий ответ:

[
  [
    PRICE,
    COUNT,
    AMOUNT
  ]
]

Вы обнаружите, что ответ возвращается в виде списка. Ставки имеют положительную сумму; просит иметь отрицательную сумму.

WebSockets

Если вы чувствуете, что вам нужен постоянный поток обновлений, WebSockets - это инструмент для вашей работы.

Чтобы использовать WebSockets, начните с подключения к wss://api.bitfinex.com/ws/2

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

При открытии отправьте subscribe событие со своими любимыми pair и precision:

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

Теперь поток обновлений, которые вы можете обрабатывать как таковые:

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

Новая функция: контрольные суммы WebSocket

API книги WebSockets v2 (документация) теперь поддерживает возможность запрашивать контрольную сумму после каждой смены книги. Контрольная сумма представляет собой значение CRC32 и покрывает первые 25 ставок и 25 запросов. Вычислив свою собственную контрольную сумму и сравнив ее с предоставленным значением, вы можете убедиться, что ваши данные верны и актуальны. Ниже мы познакомим вас с основами запроса и применения контрольных сумм.

Сначала подключитесь к Bitfinex WebSocket:

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

В open отправьте сообщение с event: 'conf' и flag: 131072 с сообщением о подписке:

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' }))
})

Сообщение с контрольной суммой будет отправляться на каждой итерации книги:

[ CHAIN_ID, 'cs', CHECKSUM ]

где CHECKSUM - целое число со знаком.

Наконец, создайте строку, представляющую вашу книгу, используйте библиотеку CRC-32 (в данном случае узел) для создания значения контрольной суммы, а затем сравните его с контрольной суммой, возвращаемой WebSocket.

Ниже приводится краткий отрывок из указанных шагов:

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')
}

ПРИМЕЧАНИЕ. Важно воссоздать строку книги в том же формате, в котором была создана контрольная сумма. Иллюстрация:

Если бы у вас были ставки [{ price: 6000, amount: 1 }, { price: 5900, amount: 2 }] и запрос: [{ price: 6100, amount: -3 }, { price: 6200, amount: -4 }], ваша строка контрольной суммы была бы 6000:1:6100:-3:5900:2:6200:-4.

Сравнивая вычисленную контрольную сумму с контрольной суммой, предоставленной API, вы можете убедиться, что ваши локальные данные верны. Если значения расходятся, вы знаете, что вам нужно получить новый снимок книги заказов, повторно подписаться на канал или проверить свою реализацию API.

Контрольная сумма позволяет вам расслабиться, всегда зная, что ваши книги действительны и актуальны.

WebSocket с контрольными суммами - пример

Не стесняйтесь использовать следующий пример в качестве отправной точки в вашем собственном инструменте Bitfinex.

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++
})

Подробнее читайте в блоге Bitfinex.

Будьте в курсе новостей Bitfinex в Twitter, Facebook и LinkedIn.