preidman-ccxt
Version:
A JavaScript / Python / PHP cryptocurrency trading library with support for 100+ exchanges
587 lines (562 loc) • 23.2 kB
JavaScript
'use strict';
// ---------------------------------------------------------------------------
const Exchange = require ('./base/Exchange');
const { ExchangeError, AuthenticationError, OrderNotFound } = require ('./base/errors');
// ---------------------------------------------------------------------------
module.exports = class quadrigacx extends Exchange {
describe () {
return this.deepExtend (super.describe (), {
'id': 'quadrigacx',
'name': 'QuadrigaCX',
'countries': [ 'CA' ],
'rateLimit': 1000,
'version': 'v2',
'has': {
'fetchDepositAddress': true,
'fetchTickers': true,
'fetchOrder': true,
'fetchMyTrades': true,
'fetchTransactions': true,
'CORS': true,
'withdraw': true,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/27766825-98a6d0de-5ee7-11e7-9fa4-38e11a2c6f52.jpg',
'api': 'https://api.quadrigacx.com',
'www': 'https://www.quadrigacx.com',
'doc': 'https://www.quadrigacx.com/api_info',
'referral': 'https://www.quadrigacx.com/?ref=laiqgbp6juewva44finhtmrk',
},
'requiredCredentials': {
'apiKey': true,
'secret': true,
'uid': true,
},
'api': {
'public': {
'get': [
'order_book',
'ticker',
'transactions',
],
},
'private': {
'post': [
'balance',
'bitcoin_deposit_address',
'bitcoin_withdrawal',
'bitcoincash_deposit_address',
'bitcoincash_withdrawal',
'bitcoingold_deposit_address',
'bitcoingold_withdrawal',
'buy',
'cancel_order',
'ether_deposit_address',
'ether_withdrawal',
'litecoin_deposit_address',
'litecoin_withdrawal',
'lookup_order',
'open_orders',
'sell',
'user_transactions',
],
},
},
'markets': {
'BTC/CAD': { 'id': 'btc_cad', 'symbol': 'BTC/CAD', 'base': 'BTC', 'quote': 'CAD', 'baseId': 'btc', 'quoteId': 'cad', 'maker': 0.005, 'taker': 0.005 },
'BTC/USD': { 'id': 'btc_usd', 'symbol': 'BTC/USD', 'base': 'BTC', 'quote': 'USD', 'baseId': 'btc', 'quoteId': 'usd', 'maker': 0.005, 'taker': 0.005 },
'ETH/BTC': { 'id': 'eth_btc', 'symbol': 'ETH/BTC', 'base': 'ETH', 'quote': 'BTC', 'baseId': 'eth', 'quoteId': 'btc', 'maker': 0.002, 'taker': 0.002 },
'ETH/CAD': { 'id': 'eth_cad', 'symbol': 'ETH/CAD', 'base': 'ETH', 'quote': 'CAD', 'baseId': 'eth', 'quoteId': 'cad', 'maker': 0.005, 'taker': 0.005 },
'LTC/CAD': { 'id': 'ltc_cad', 'symbol': 'LTC/CAD', 'base': 'LTC', 'quote': 'CAD', 'baseId': 'ltc', 'quoteId': 'cad', 'maker': 0.005, 'taker': 0.005 },
'LTC/BTC': { 'id': 'ltc_btc', 'symbol': 'LTC/BTC', 'base': 'LTC', 'quote': 'BTC', 'baseId': 'ltc', 'quoteId': 'btc', 'maker': 0.005, 'taker': 0.005 },
'BCH/CAD': { 'id': 'bch_cad', 'symbol': 'BCH/CAD', 'base': 'BCH', 'quote': 'CAD', 'baseId': 'bch', 'quoteId': 'cad', 'maker': 0.005, 'taker': 0.005 },
'BCH/BTC': { 'id': 'bch_btc', 'symbol': 'BCH/BTC', 'base': 'BCH', 'quote': 'BTC', 'baseId': 'bch', 'quoteId': 'btc', 'maker': 0.005, 'taker': 0.005 },
'BSV/CAD': { 'id': 'bsv_cad', 'symbol': 'BSV/CAD', 'base': 'BSV', 'quote': 'CAD', 'baseId': 'bsv', 'quoteId': 'cad', 'maker': 0.005, 'taker': 0.005 },
'BTG/CAD': { 'id': 'btg_cad', 'symbol': 'BTG/CAD', 'base': 'BTG', 'quote': 'CAD', 'baseId': 'btg', 'quoteId': 'cad', 'maker': 0.005, 'taker': 0.005 },
'BTG/BTC': { 'id': 'btg_btc', 'symbol': 'BTG/BTC', 'base': 'BTG', 'quote': 'BTC', 'baseId': 'btg', 'quoteId': 'btc', 'maker': 0.005, 'taker': 0.005 },
},
'exceptions': {
'101': AuthenticationError,
'106': OrderNotFound, // { 'code':106, 'message': 'Cannot perform request - not found' }
},
});
}
async fetchBalance (params = {}) {
let balances = await this.privatePostBalance ();
let result = { 'info': balances };
let currencyIds = Object.keys (this.currencies_by_id);
for (let i = 0; i < currencyIds.length; i++) {
let currencyId = currencyIds[i];
let currency = this.currencies_by_id[currencyId];
let code = currency['code'];
result[code] = {
'free': this.safeFloat (balances, currencyId + '_available'),
'used': this.safeFloat (balances, currencyId + '_reserved'),
'total': this.safeFloat (balances, currencyId + '_balance'),
};
}
return this.parseBalance (result);
}
async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
let market = undefined;
let request = {};
if (symbol !== undefined) {
market = this.market (symbol);
request['book'] = market['id'];
}
if (limit !== undefined) {
request['limit'] = limit;
}
let response = await this.privatePostUserTransactions (this.extend (request, params));
let trades = this.filterBy (response, 'type', 2);
return this.parseTrades (trades, market, since, limit);
}
async fetchTransactions (symbol = undefined, since = undefined, limit = undefined, params = {}) {
let market = undefined;
let request = {};
if (symbol !== undefined) {
market = this.market (symbol);
request['book'] = market['id'];
}
if (limit !== undefined) {
request['limit'] = limit;
}
let response = await this.privatePostUserTransactions (this.extend (request, params));
let user_transactions = this.filterByArray (response, 'type', [0, 1], false);
// return user_transactions;
return this.parseTransactions (user_transactions, market, since, limit);
}
parseTransaction (transaction, currency = undefined) {
//
// {
// "btc":"0.99985260",
// "method":"Bitcoin",
// "fee":"0.00000000",
// "type":0,
// "datetime":"2018-10-08 05:26:23"
// }
//
// {
// "btc":"-0.50000000",
// "method":"Bitcoin",
// "fee":"0.00000000",
// "type":1,
// "datetime":"2018-08-27 13:50:10"
// }
//
let code = undefined;
let amount = undefined;
let omitted = this.omit (transaction, [ 'datetime', 'type', 'method', 'fee' ]);
let keys = Object.keys (omitted);
for (let i = 0; i < keys.length; i++) {
if (keys[i] in this.currencies_by_id) {
code = keys[i];
}
}
if (code !== undefined) {
amount = this.safeString (transaction, code);
}
let timestamp = this.parse8601 (this.safeString (transaction, 'datetime'));
let status = 'ok';
const fee = this.safeFloat (transaction, 'fee');
let type = this.safeInteger (transaction, 'type');
type = (type === 1) ? 'withdrawal' : 'deposit';
return {
'info': transaction,
'id': undefined,
'txid': undefined,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'address': undefined,
'tag': undefined,
'type': type,
'amount': amount,
'currency': code,
'status': status,
'updated': undefined,
'fee': {
'currency': code,
'cost': fee,
},
};
}
async fetchOrder (id, symbol = undefined, params = {}) {
let request = {
'id': id,
};
let response = await this.privatePostLookupOrder (this.extend (request, params));
return this.parseOrders (response);
}
parseOrderStatus (status) {
const statuses = {
'-1': 'canceled',
'0': 'open',
'1': 'open',
'2': 'closed',
};
return this.safeString (statuses, status, status);
}
parseOrder (order, market = undefined) {
let id = this.safeString (order, 'id');
let price = this.safeFloat (order, 'price');
let amount = undefined;
let filled = undefined;
let remaining = this.safeFloat (order, 'amount');
let cost = undefined;
let symbol = undefined;
let marketId = this.safeString (order, 'book');
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
} else {
let [ baseId, quoteId ] = marketId.split ('_');
let base = baseId.toUpperCase ();
let quote = quoteId.toUpperCase ();
base = this.commonCurrencyCode (base);
quote = this.commonCurrencyCode (quote);
symbol = base + '/' + quote;
}
let side = this.safeString (order, 'type');
if (side === '0') {
side = 'buy';
} else {
side = 'sell';
}
let status = this.parseOrderStatus (this.safeString (order, 'status'));
let timestamp = this.parse8601 (this.safeString (order, 'created'));
let lastTradeTimestamp = this.parse8601 (this.safeString (order, 'updated'));
let type = (price === 0.0) ? 'market' : 'limit';
if (market !== undefined) {
symbol = market['symbol'];
}
if (status === 'closed') {
amount = remaining;
filled = remaining;
remaining = 0;
}
if ((type === 'limit') && (price !== undefined)) {
if (filled !== undefined) {
cost = price * filled;
}
}
let result = {
'info': order,
'id': id,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'lastTradeTimestamp': lastTradeTimestamp,
'symbol': symbol,
'type': type,
'side': side,
'price': price,
'cost': cost,
'average': undefined,
'amount': amount,
'filled': filled,
'remaining': remaining,
'status': status,
'fee': undefined,
};
return result;
}
async fetchOrderBook (symbol, limit = undefined, params = {}) {
let orderbook = await this.publicGetOrderBook (this.extend ({
'book': this.marketId (symbol),
}, params));
let timestamp = parseInt (orderbook['timestamp']) * 1000;
return this.parseOrderBook (orderbook, timestamp);
}
async fetchTickers (symbols = undefined, params = {}) {
let response = await this.publicGetTicker (this.extend ({
'book': 'all',
}, params));
let ids = Object.keys (response);
let result = {};
for (let i = 0; i < ids.length; i++) {
let id = ids[i];
let symbol = id;
let market = undefined;
if (id in this.markets_by_id) {
market = this.markets_by_id[id];
symbol = market['symbol'];
} else {
let [ baseId, quoteId ] = id.split ('_');
let base = baseId.toUpperCase ();
let quote = quoteId.toUpperCase ();
base = this.commonCurrencyCode (base);
quote = this.commonCurrencyCode (base);
symbol = base + '/' + quote;
market = {
'symbol': symbol,
};
}
result[symbol] = this.parseTicker (response[id], market);
}
return result;
}
async fetchTicker (symbol, params = {}) {
await this.loadMarkets ();
let market = this.market (symbol);
let response = await this.publicGetTicker (this.extend ({
'book': market['id'],
}, params));
return this.parseTicker (response, market);
}
parseTicker (ticker, market = undefined) {
let symbol = undefined;
if (market !== undefined)
symbol = market['symbol'];
let timestamp = parseInt (ticker['timestamp']) * 1000;
let vwap = this.safeFloat (ticker, 'vwap');
let baseVolume = this.safeFloat (ticker, 'volume');
let quoteVolume = undefined;
if (baseVolume !== undefined && vwap !== undefined)
quoteVolume = baseVolume * vwap;
let last = this.safeFloat (ticker, 'last');
return {
'symbol': symbol,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'high': this.safeFloat (ticker, 'high'),
'low': this.safeFloat (ticker, 'low'),
'bid': this.safeFloat (ticker, 'bid'),
'bidVolume': undefined,
'ask': this.safeFloat (ticker, 'ask'),
'askVolume': undefined,
'vwap': vwap,
'open': undefined,
'close': last,
'last': last,
'previousClose': undefined,
'change': undefined,
'percentage': undefined,
'average': undefined,
'baseVolume': baseVolume,
'quoteVolume': quoteVolume,
'info': ticker,
};
}
parseTrade (trade, market = undefined) {
//
// fetchTrades (public)
//
// {"amount":"2.26252009","date":"1541355778","price":"0.03300000","tid":3701722,"side":"sell"}
//
// fetchMyTrades (private)
//
// {
// "datetime": "2018-01-01T00:00:00", // date and time
// "id": 123, // unique identifier (only for trades)
// "type": 2, // transaction type (0 - deposit; 1 - withdrawal; 2 - trade)
// "method": "...", // deposit or withdrawal method
// "(minor currency code)" – the minor currency amount
// "(major currency code)" – the major currency amount
// "order_id": "...", // a 64 character long hexadecimal string representing the order that was fully or partially filled (only for trades)
// "fee": 123.45, // transaction fee
// "rate": 54.321, // rate per btc (only for trades)
// }
//
let id = this.safeString2 (trade, 'tid', 'id');
let timestamp = this.parse8601 (this.safeString (trade, 'datetime'));
if (timestamp === undefined) {
timestamp = this.safeInteger (trade, 'date');
if (timestamp !== undefined) {
timestamp *= 1000;
}
}
let symbol = undefined;
let omitted = this.omit (trade, [ 'datetime', 'id', 'type', 'method', 'order_id', 'fee', 'rate' ]);
let keys = Object.keys (omitted);
let rate = this.safeFloat (trade, 'rate');
for (let i = 0; i < keys.length; i++) {
let marketId = keys[i];
let floatValue = this.safeFloat (trade, marketId);
if (floatValue === rate) {
if (marketId in this.markets_by_id) {
market = this.markets_by_id[marketId];
} else {
let currencyIds = marketId.split ('_');
let numCurrencyIds = currencyIds.length;
if (numCurrencyIds === 2) {
let baseId = currencyIds[0];
let quoteId = currencyIds[1];
let base = baseId.toUpperCase ();
let quote = quoteId.toUpperCase ();
base = this.commonCurrencyCode (base);
quote = this.commonCurrencyCode (base);
symbol = base + '/' + quote;
}
}
}
}
let orderId = this.safeString (trade, 'order_id');
let side = this.safeString (trade, 'side');
let price = this.safeFloat (trade, 'price', rate);
let amount = this.safeFloat (trade, 'amount');
let cost = undefined;
if (market !== undefined) {
symbol = market['symbol'];
let baseId = market['baseId'];
let quoteId = market['quoteId'];
if (amount === undefined) {
amount = this.safeFloat (trade, baseId);
if (amount !== undefined) {
amount = Math.abs (amount);
}
}
cost = this.safeFloat (trade, quoteId);
if (cost !== undefined) {
cost = Math.abs (cost);
}
if (side === undefined) {
let baseValue = this.safeFloat (trade, market['baseId']);
if ((baseValue !== undefined) && (baseValue > 0)) {
side = 'buy';
} else {
side = 'sell';
}
}
}
if (cost === undefined) {
if (price !== undefined) {
if (amount !== undefined) {
cost = amount * price;
}
}
}
let fee = undefined;
let feeCost = this.safeFloat (trade, 'fee');
if (feeCost !== undefined) {
let feeCurrency = undefined;
if (market !== undefined) {
feeCurrency = (side === 'buy') ? market['base'] : market['quote'];
}
fee = {
'cost': feeCost,
'currency': feeCurrency,
};
}
return {
'info': trade,
'id': id,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'symbol': symbol,
'order': orderId,
'type': undefined,
'side': side,
'takerOrMaker': undefined,
'price': price,
'amount': amount,
'cost': cost,
'fee': fee,
};
}
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
let market = this.market (symbol);
let response = await this.publicGetTransactions (this.extend ({
'book': market['id'],
}, params));
return this.parseTrades (response, market, since, limit);
}
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
let method = 'privatePost' + this.capitalize (side);
let order = {
'amount': amount,
'book': this.marketId (symbol),
};
if (type === 'limit')
order['price'] = price;
let response = await this[method] (this.extend (order, params));
return {
'info': response,
'id': response['id'].toString (),
};
}
async cancelOrder (id, symbol = undefined, params = {}) {
return await this.privatePostCancelOrder (this.extend ({
'id': id,
}, params));
}
async fetchDepositAddress (code, params = {}) {
let method = 'privatePost' + this.getCurrencyName (code) + 'DepositAddress';
let response = await this[method] (params);
// [E|e]rror
if (response.indexOf ('rror') >= 0) {
throw new ExchangeError (this.id + ' ' + response);
}
this.checkAddress (response);
return {
'currency': code,
'address': response,
'tag': undefined,
'info': response,
};
}
getCurrencyName (code) {
const currencies = {
'ETH': 'Ether',
'BTC': 'Bitcoin',
'LTC': 'Litecoin',
'BCH': 'Bitcoincash',
'BTG': 'Bitcoingold',
};
return currencies[code];
}
async withdraw (code, amount, address, tag = undefined, params = {}) {
this.checkAddress (address);
await this.loadMarkets ();
let request = {
'amount': amount,
'address': address,
};
let method = 'privatePost' + this.getCurrencyName (code) + 'Withdrawal';
let response = await this[method] (this.extend (request, params));
return {
'info': response,
'id': undefined,
};
}
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
let url = this.urls['api'] + '/' + this.version + '/' + path;
if (api === 'public') {
url += '?' + this.urlencode (params);
} else {
this.checkRequiredCredentials ();
let nonce = this.nonce ();
let request = [ nonce.toString (), this.uid, this.apiKey ].join ('');
let signature = this.hmac (this.encode (request), this.encode (this.secret));
let query = this.extend ({
'key': this.apiKey,
'nonce': nonce,
'signature': signature,
}, params);
body = this.json (query);
headers = {
'Content-Type': 'application/json',
};
}
return { 'url': url, 'method': method, 'body': body, 'headers': headers };
}
handleErrors (statusCode, statusText, url, method, headers, body, response) {
if (typeof body !== 'string')
return; // fallback to default error handler
if (body.length < 2)
return;
if ((body[0] === '{') || (body[0] === '[')) {
let error = this.safeValue (response, 'error');
if (error !== undefined) {
//
// {"error":{"code":101,"message":"Invalid API Code or Invalid Signature"}}
//
const code = this.safeString (error, 'code');
const feedback = this.id + ' ' + this.json (response);
const exceptions = this.exceptions;
if (code in exceptions) {
throw new exceptions[code] (feedback);
} else {
throw new ExchangeError (this.id + ' unknown "error" value: ' + this.json (response));
}
}
}
}
};