tlab-trading-toolkit
Version:
A trading toolkit for building advanced trading bots on the GDAX platform
295 lines (294 loc) • 13.2 kB
JavaScript
;
/***************************************************************************************************************************
* @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;