UNPKG

tlab-trading-toolkit

Version:

A trading toolkit for building advanced trading bots on the GDAX platform

295 lines (294 loc) 13.2 kB
"use strict"; /*************************************************************************************************************************** * @license * * Copyright 2017 Coinbase, Inc. * * * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * * with the License. You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * * License for the specific language governing permissions and limitations under the License. * ***************************************************************************************************************************/ Object.defineProperty(exports, "__esModule", { value: true }); const BookBuilder_1 = require("../../lib/BookBuilder"); const BitfinexAuth = require("./BitfinexAuth"); const BitfinexAuth_1 = require("./BitfinexAuth"); // import { PRODUCT_MAP, REVERSE_CURRENCY_MAP, REVERSE_PRODUCT_MAP } from './BitfinexCommon'; const ProductMap_1 = require("../ProductMap"); const types_1 = require("../../lib/types"); const request = require("superagent"); const API_V1 = 'https://api.bitfinex.com/v1'; const ORDER_TYPE_MAP = { limit: 'exchange limit', market: 'exchange market', stop: 'exchange stop' }; /** * An adapter class that maps the standardized API calls to Bitfinex's API interface */ class BitfinexExchangeAPI { /** * Returns the Bitfinex product that's equivalent to the given Generic product. If it doesn't exist, * return the given product * @param genericProduct * @returns {string} Bitfinex product code */ static product(genericProduct) { return ProductMap_1.ProductMap.ExchangeMap.get('Bitfinex').getExchangeProduct(genericProduct) || genericProduct; } static genericProduct(exchangeProduct) { return ProductMap_1.ProductMap.ExchangeMap.get('Bitfinex').getGenericProduct(exchangeProduct) || exchangeProduct; } static getMarket(genericProduct) { return ProductMap_1.ProductMap.ExchangeMap.get('Bitfinex').getMarket(genericProduct); } static getMarketForExchangeProduct(exchangeProduct) { return ProductMap_1.ProductMap.ExchangeMap.get('Bitfinex').getMarket(BitfinexExchangeAPI.genericProduct(exchangeProduct)); } static convertBSOPToOrder(bfOrder) { return { time: new Date(+bfOrder.timestamp * 1000), id: bfOrder.id.toString(), productId: BitfinexExchangeAPI.genericProduct(bfOrder.symbol), size: types_1.Big(bfOrder.executed_amount), price: types_1.Big(bfOrder.price), side: bfOrder.side, status: bfOrder.is_live ? 'open' : 'done', extra: { exchange: bfOrder.exchange, aveExecutionPrice: bfOrder.avg_execution_price, remainingAmount: bfOrder.remaining_amount, type: bfOrder.type } }; } constructor(config) { this.owner = 'Bitfinex'; this.auth = config.auth && config.auth.key && config.auth.secret ? config.auth : undefined; this.logger = config.logger; } loadProducts() { return request.get(`${API_V1}/symbols_details`) .accept('application/json') .then((res) => { if (res.status !== 200) { throw new Error('loadProducts did not get the expected response from the server. ' + res.body); } const bfProducts = res.body; const products = bfProducts.map((prod) => { const base = prod.pair.slice(0, 3); const quote = prod.pair.slice(3, 6); let ccxtMarket = BitfinexExchangeAPI.getMarketForExchangeProduct(prod.pair); return { id: BitfinexExchangeAPI.genericProduct(prod.pair) || prod.pair, baseCurrency: ccxtMarket.base || base, quoteCurrency: ccxtMarket.quote || quote, baseMinSize: types_1.Big(prod.minimum_order_size), baseMaxSize: types_1.Big(prod.maximum_order_size), quoteIncrement: types_1.Big(prod.minimum_order_size) }; }); return products; }); } loadMidMarketPrice(genericProduct) { return this.loadTicker(genericProduct).then((ticker) => { return ticker.bid.plus(ticker.ask).times(0.5); }); } loadOrderbook(genericProduct) { const product = BitfinexExchangeAPI.product(genericProduct); return request.get(`${API_V1}/book/${product}`) .query({ grouped: 1 }) .accept('application/json') .then((res) => { if (res.status !== 200) { throw new Error('loadOrderbook did not get the expected response from the server. ' + res.body); } return this.convertBitfinexBookToGdaxBook(res.body); }); } loadTicker(genericProduct) { const product = BitfinexExchangeAPI.product(genericProduct); return request.get(`${API_V1}/pubticker/${product}`) .accept('application/json') .then((res) => { if (res.status !== 200) { throw new Error('loadTicker did not get the expected response from the server. ' + res.body); } const ticker = res.body; return { productId: genericProduct, ask: ticker.ask ? types_1.Big(ticker.ask) : null, bid: ticker.bid ? types_1.Big(ticker.bid) : null, price: types_1.Big(ticker.last_price || 0), volume: types_1.Big(ticker.volume || 0), time: new Date(+ticker.timestamp * 1000) }; }); } checkAuth() { return new Promise((resolve, reject) => { if (this.auth === null) { return reject(new Error('You cannot make authenticated requests if a ExchangeAuthConfig object was not provided to the BitfinexExchangeAPI constructor')); } return resolve(this.auth); }); } placeOrder(order) { return this.checkAuth().then((auth) => { const bfOrder = { product_id: BitfinexExchangeAPI.product(order.productId), size: order.size, price: order.price, side: order.side, type: ORDER_TYPE_MAP[order.type], post_only: !!order.postOnly }; return BitfinexAuth.placeOrder(auth, bfOrder).then((result) => { if (this.logger) { this.logger.log('debug', 'Order placed on Bitfinex', result); } return BitfinexExchangeAPI.convertBSOPToOrder(result); }); }); } cancelOrder(id) { return this.checkAuth().then((auth) => { return BitfinexAuth.cancelOrder(auth, parseInt(id, 10)).then((result) => { if (this.logger) { this.logger.log('debug', 'Order cancelled on Bitfinex', result); } return result.id.toString(); }); }); } cancelAllOrders() { return this.checkAuth().then((auth) => { return BitfinexAuth.cancelAllOrders(auth).then((result) => { if (this.logger) { this.logger.log('debug', 'All Orders cancelled on Bitfinex', result); } return []; }); }); } loadOrder(id) { return this.checkAuth().then((auth) => { return BitfinexAuth.orderStatus(auth, parseInt(id, 10)).then((result) => { if (this.logger) { this.logger.log('debug', 'Bitfinex Order status', result); } return BitfinexExchangeAPI.convertBSOPToOrder(result); }); }); } loadAllOrders() { return this.checkAuth().then((auth) => { return BitfinexAuth.activeOrders(auth).then((results) => { if (this.logger) { this.logger.log('debug', `${results.length} Bitfinex active orders retrieved`); } return results.map((order) => BitfinexExchangeAPI.convertBSOPToOrder(order)); }); }); } loadBalances() { return this.checkAuth().then((auth) => { return BitfinexAuth.loadBalances(auth).then((results) => { if (this.logger) { this.logger.log('debug', 'Bitfinex wallet balances retrieved', results); } const balances = {}; results.forEach((wallet) => { if (!balances[wallet.type]) { balances[wallet.type] = {}; } const cur = wallet.currency.toUpperCase(); balances[wallet.type][cur] = { available: types_1.Big(wallet.available), balance: types_1.Big(wallet.amount) }; }); return balances; }); }); } // -------------------------- Transfer methods ------------------------------------------------- requestCryptoAddress(cur) { return undefined; } requestTransfer(req) { if (!BitfinexAuth_1.isBFWallet(req.walletIdFrom)) { return Promise.reject(new Error(`walletIdFrom "${req.walletIdFrom} is not a valid Bitfinex Wallet name`)); } if (!BitfinexAuth_1.isBFWallet(req.walletIdTo)) { return Promise.reject(new Error(`walletIdTo "${req.walletIdTo} is not a valid Bitfinex Wallet name`)); } return this.checkAuth().then((auth) => { const bfRequest = { amount: req.amount.toString(), currency: req.currency, walletfrom: req.walletIdFrom, walletto: req.walletIdTo }; return BitfinexAuth.transfer(auth, bfRequest).then((response) => { if (response.status === 200) { const bfResult = response.body[0]; return { success: bfResult.status === 'success', details: bfResult.message }; } const err = new Error('Bitfinex transfer request failed'); err.details = response.body; return Promise.reject(err); }); }); } requestWithdrawal(req) { return undefined; } transfer(cur, amount, from, to, options) { return undefined; } // -------------------------- Helper methods ------------------------------------------------- convertBitfinexBookToGdaxBook(bfBook) { const book = new BookBuilder_1.BookBuilder(this.logger); bfBook.asks.forEach((order) => { addToLevel('sell', order); }); bfBook.bids.forEach((order) => { addToLevel('buy', order); }); // The 'websocket feed' will start counting from 1 book.sequence = 0; return book; function addToLevel(side, order) { try { book.addLevel(side, convertOrder(side, order)); } catch (err) { const newSize = types_1.Big(order.amount).abs().plus(book.getOrder(order.price).size); order.amount = newSize.toString(); book.modify(order.price, newSize, side); } } function convertOrder(side, order) { const price = types_1.Big(order.price); const size = types_1.Big(order.amount).abs(); const level = new BookBuilder_1.AggregatedLevelWithOrders(price); level.addOrder({ id: order.price, price: price, size: size, side: side }); return level; } } } exports.BitfinexExchangeAPI = BitfinexExchangeAPI;