sfccxt
Version:
A JavaScript / Python / PHP cryptocurrency trading library with support for 130+ exchanges
1,157 lines (1,133 loc) • 69.6 kB
JavaScript
'use strict';
// ---------------------------------------------------------------------------
const Exchange = require ('./base/Exchange');
const { ExchangeError, ArgumentsRequired, AuthenticationError, NullResponse, InvalidOrder, InsufficientFunds, InvalidNonce, OrderNotFound, RateLimitExceeded, DDoSProtection, BadSymbol } = require ('./base/errors');
const { TICK_SIZE } = require ('./base/functions/number');
const Precise = require ('./base/Precise');
// ---------------------------------------------------------------------------
module.exports = class cex extends Exchange {
describe () {
return this.deepExtend (super.describe (), {
'id': 'cex',
'name': 'CEX.IO',
'countries': [ 'GB', 'EU', 'CY', 'RU' ],
'rateLimit': 1500,
'pro': true,
'has': {
'CORS': undefined,
'spot': true,
'margin': false, // has but not through api
'swap': false,
'future': false,
'option': false,
'addMargin': false,
'cancelOrder': true,
'cancelOrders': false,
'createDepositAddress': false,
'createOrder': true,
'createStopLimitOrder': false,
'createStopMarketOrder': false,
'createStopOrder': false,
'editOrder': true,
'fetchBalance': true,
'fetchClosedOrders': true,
'fetchCurrencies': true,
'fetchDeposit': false,
'fetchDepositAddress': true,
'fetchDepositAddresses': false,
'fetchDeposits': false,
'fetchFundingHistory': false,
'fetchFundingRate': false,
'fetchFundingRateHistory': false,
'fetchFundingRates': false,
'fetchIndexOHLCV': false,
'fetchMarginMode': false,
'fetchMarkets': true,
'fetchMarkOHLCV': false,
'fetchOHLCV': true,
'fetchOpenInterestHistory': false,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchOrders': true,
'fetchPositionMode': false,
'fetchPremiumIndexOHLCV': false,
'fetchTicker': true,
'fetchTickers': true,
'fetchTrades': true,
'fetchTradingFee': false,
'fetchTradingFees': true,
'fetchTransactions': false,
'fetchTransfer': false,
'fetchTransfers': false,
'fetchWithdrawal': false,
'fetchWithdrawals': false,
'fetchWithdrawalWhitelist': false,
'reduceMargin': false,
'setLeverage': false,
'setMargin': false,
'setMarginMode': false,
'transfer': false,
'withdraw': false,
},
'timeframes': {
'1m': '1m',
'1h': '1h',
'1d': '1d',
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/1294454/27766442-8ddc33b0-5ed8-11e7-8b98-f786aef0f3c9.jpg',
'api': {
'rest': 'https://cex.io/api',
},
'www': 'https://cex.io',
'doc': 'https://cex.io/cex-api',
'fees': [
'https://cex.io/fee-schedule',
'https://cex.io/limits-commissions',
],
'referral': 'https://cex.io/r/0/up105393824/0/',
},
'requiredCredentials': {
'apiKey': true,
'secret': true,
'uid': true,
},
'api': {
'public': {
'get': [
'currency_profile',
'currency_limits/',
'last_price/{pair}/',
'last_prices/{currencies}/',
'ohlcv/hd/{yyyymmdd}/{pair}',
'order_book/{pair}/',
'ticker/{pair}/',
'tickers/{currencies}/',
'trade_history/{pair}/',
],
'post': [
'convert/{pair}',
'price_stats/{pair}',
],
},
'private': {
'post': [
'active_orders_status/',
'archived_orders/{pair}/',
'balance/',
'cancel_order/',
'cancel_orders/{pair}/',
'cancel_replace_order/{pair}/',
'close_position/{pair}/',
'get_address/',
'get_crypto_address',
'get_myfee/',
'get_order/',
'get_order_tx/',
'open_orders/{pair}/',
'open_orders/',
'open_position/{pair}/',
'open_positions/{pair}/',
'place_order/{pair}/',
'raw_tx_history',
],
},
},
'fees': {
'trading': {
'maker': this.parseNumber ('0.0016'),
'taker': this.parseNumber ('0.0025'),
},
'funding': {
'withdraw': {},
'deposit': {
// 'USD': amount => amount * 0.035 + 0.25,
// 'EUR': amount => amount * 0.035 + 0.24,
// 'RUB': amount => amount * 0.05 + 15.57,
// 'GBP': amount => amount * 0.035 + 0.2,
'BTC': 0.0,
'ETH': 0.0,
'BCH': 0.0,
'DASH': 0.0,
'BTG': 0.0,
'ZEC': 0.0,
'XRP': 0.0,
'XLM': 0.0,
},
},
},
'precisionMode': TICK_SIZE,
'exceptions': {
'exact': {},
'broad': {
'Insufficient funds': InsufficientFunds,
'Nonce must be incremented': InvalidNonce,
'Invalid Order': InvalidOrder,
'Order not found': OrderNotFound,
'limit exceeded': RateLimitExceeded, // {"error":"rate limit exceeded"}
'Invalid API key': AuthenticationError,
'There was an error while placing your order': InvalidOrder,
'Sorry, too many clients already': DDoSProtection,
'Invalid Symbols Pair': BadSymbol,
'Wrong currency pair': BadSymbol, // {"error":"There was an error while placing your order: Wrong currency pair.","safe":true}
},
},
'options': {
'fetchOHLCVWarning': true,
'createMarketBuyOrderRequiresPrice': true,
'order': {
'status': {
'c': 'canceled',
'd': 'closed',
'cd': 'canceled',
'a': 'open',
},
},
'defaultNetwork': 'ERC20',
'defaultNetworks': {
'USDT': 'TRC20',
},
'networks': {
'ERC20': 'Ethereum',
'BTC': 'BTC',
'BEP20': 'Binance Smart Chain',
'BSC': 'Binance Smart Chain',
'TRC20': 'Tron',
},
'networksById': {
'Ethereum': 'ERC20',
'BTC': 'BTC',
'Binance Smart Chain': 'BEP20',
'Tron': 'TRC20',
},
},
});
}
async fetchCurrenciesFromCache (params = {}) {
// this method is now redundant
// currencies are now fetched before markets
const options = this.safeValue (this.options, 'fetchCurrencies', {});
const timestamp = this.safeInteger (options, 'timestamp');
const expires = this.safeInteger (options, 'expires', 1000);
const now = this.milliseconds ();
if ((timestamp === undefined) || ((now - timestamp) > expires)) {
const response = await this.publicGetCurrencyProfile (params);
this.options['fetchCurrencies'] = this.extend (options, {
'response': response,
'timestamp': now,
});
}
return this.safeValue (this.options['fetchCurrencies'], 'response');
}
async fetchCurrencies (params = {}) {
/**
* @method
* @name cex#fetchCurrencies
* @description fetches all available currencies on an exchange
* @param {object} params extra parameters specific to the cex api endpoint
* @returns {object} an associative dictionary of currencies
*/
const response = await this.fetchCurrenciesFromCache (params);
this.options['currencies'] = {
'timestamp': this.milliseconds (),
'response': response,
};
//
// {
// "e":"currency_profile",
// "ok":"ok",
// "data":{
// "symbols":[
// {
// "code":"GHS",
// "contract":true,
// "commodity":true,
// "fiat":false,
// "description":"CEX.IO doesn't provide cloud mining services anymore.",
// "precision":8,
// "scale":0,
// "minimumCurrencyAmount":"0.00000001",
// "minimalWithdrawalAmount":-1
// },
// {
// "code":"BTC",
// "contract":false,
// "commodity":false,
// "fiat":false,
// "description":"",
// "precision":8,
// "scale":0,
// "minimumCurrencyAmount":"0.00000001",
// "minimalWithdrawalAmount":0.002
// },
// {
// "code":"ETH",
// "contract":false,
// "commodity":false,
// "fiat":false,
// "description":"",
// "precision":8,
// "scale":2,
// "minimumCurrencyAmount":"0.00000100",
// "minimalWithdrawalAmount":0.01
// }
// ],
// "pairs":[
// {
// "symbol1":"BTC",
// "symbol2":"USD",
// "pricePrecision":1,
// "priceScale":"/1000000",
// "minLotSize":0.002,
// "minLotSizeS2":20
// },
// {
// "symbol1":"ETH",
// "symbol2":"USD",
// "pricePrecision":2,
// "priceScale":"/10000",
// "minLotSize":0.1,
// "minLotSizeS2":20
// }
// ]
// }
// }
//
const data = this.safeValue (response, 'data', []);
const currencies = this.safeValue (data, 'symbols', []);
const result = {};
for (let i = 0; i < currencies.length; i++) {
const currency = currencies[i];
const id = this.safeString (currency, 'code');
const code = this.safeCurrencyCode (id);
const active = true;
result[code] = {
'id': id,
'code': code,
'name': id,
'active': active,
'deposit': undefined,
'withdraw': undefined,
'precision': this.parseNumber (this.safeString (currency, 'precision')),
'fee': undefined,
'limits': {
'amount': {
'min': this.safeNumber (currency, 'minimumCurrencyAmount'),
'max': undefined,
},
'withdraw': {
'min': this.safeNumber (currency, 'minimalWithdrawalAmount'),
'max': undefined,
},
},
'info': currency,
};
}
return result;
}
async fetchMarkets (params = {}) {
/**
* @method
* @name cex#fetchMarkets
* @description retrieves data on all markets for cex
* @param {object} params extra parameters specific to the exchange api endpoint
* @returns {[object]} an array of objects representing market data
*/
const currenciesResponse = await this.fetchCurrenciesFromCache (params);
const currenciesData = this.safeValue (currenciesResponse, 'data', {});
const currencies = this.safeValue (currenciesData, 'symbols', []);
const currenciesById = this.indexBy (currencies, 'code');
const pairs = this.safeValue (currenciesData, 'pairs', []);
const response = await this.publicGetCurrencyLimits (params);
//
// {
// "e":"currency_limits",
// "ok":"ok",
// "data": {
// "pairs":[
// {
// "symbol1":"BTC",
// "symbol2":"USD",
// "minLotSize":0.002,
// "minLotSizeS2":20,
// "maxLotSize":30,
// "minPrice":"1500",
// "maxPrice":"35000"
// },
// {
// "symbol1":"BCH",
// "symbol2":"EUR",
// "minLotSize":0.1,
// "minLotSizeS2":20,
// "maxLotSize":null,
// "minPrice":"25",
// "maxPrice":"8192"
// }
// ]
// }
// }
//
const result = [];
const markets = this.safeValue (response['data'], 'pairs');
for (let i = 0; i < markets.length; i++) {
const market = markets[i];
const baseId = this.safeString (market, 'symbol1');
const quoteId = this.safeString (market, 'symbol2');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
const baseCurrency = this.safeValue (currenciesById, baseId, {});
const quoteCurrency = this.safeValue (currenciesById, quoteId, {});
let pricePrecisionString = this.safeString (quoteCurrency, 'precision', '8');
for (let j = 0; j < pairs.length; j++) {
const pair = pairs[j];
if ((pair['symbol1'] === baseId) && (pair['symbol2'] === quoteId)) {
// we might need to account for `priceScale` here
pricePrecisionString = this.safeString (pair, 'pricePrecision', pricePrecisionString);
}
}
const baseCurrencyPrecision = this.safeString (baseCurrency, 'precision', '8');
const baseCurrencyScale = this.safeString (baseCurrency, 'scale', '0');
const amountPrecisionString = Precise.stringSub (baseCurrencyPrecision, baseCurrencyScale);
result.push ({
'id': baseId + '/' + quoteId,
'symbol': base + '/' + quote,
'base': base,
'quote': quote,
'settle': undefined,
'baseId': baseId,
'quoteId': quoteId,
'settleId': undefined,
'type': 'spot',
'spot': true,
'margin': undefined,
'swap': false,
'future': false,
'option': false,
'active': undefined,
'contract': false,
'linear': undefined,
'inverse': undefined,
'contractSize': undefined,
'expiry': undefined,
'expiryDatetime': undefined,
'strike': undefined,
'optionType': undefined,
'precision': {
'amount': this.parseNumber (this.parsePrecision (amountPrecisionString)),
'price': this.parseNumber (this.parsePrecision (pricePrecisionString)),
},
'limits': {
'leverage': {
'min': undefined,
'max': undefined,
},
'amount': {
'min': this.safeNumber (market, 'minLotSize'),
'max': this.safeNumber (market, 'maxLotSize'),
},
'price': {
'min': this.safeNumber (market, 'minPrice'),
'max': this.safeNumber (market, 'maxPrice'),
},
'cost': {
'min': this.safeNumber (market, 'minLotSizeS2'),
'max': undefined,
},
},
'info': market,
});
}
return result;
}
parseBalance (response) {
const result = { 'info': response };
const ommited = [ 'username', 'timestamp' ];
const balances = this.omit (response, ommited);
const currencyIds = Object.keys (balances);
for (let i = 0; i < currencyIds.length; i++) {
const currencyId = currencyIds[i];
const balance = this.safeValue (balances, currencyId, {});
const account = this.account ();
account['free'] = this.safeString (balance, 'available');
// https://github.com/ccxt/ccxt/issues/5484
account['used'] = this.safeString (balance, 'orders', '0');
const code = this.safeCurrencyCode (currencyId);
result[code] = account;
}
return this.safeBalance (result);
}
async fetchBalance (params = {}) {
/**
* @method
* @name cex#fetchBalance
* @description query for balance and get the amount of funds available for trading or funds locked in orders
* @param {object} params extra parameters specific to the cex api endpoint
* @returns {object} a [balance structure]{@link https://docs.ccxt.com/en/latest/manual.html?#balance-structure}
*/
await this.loadMarkets ();
const response = await this.privatePostBalance (params);
return this.parseBalance (response);
}
async fetchOrderBook (symbol, limit = undefined, params = {}) {
/**
* @method
* @name cex#fetchOrderBook
* @description fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
* @param {string} symbol unified symbol of the market to fetch the order book for
* @param {int|undefined} limit the maximum amount of order book entries to return
* @param {object} params extra parameters specific to the cex api endpoint
* @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-book-structure} indexed by market symbols
*/
await this.loadMarkets ();
const market = this.market (symbol);
const request = {
'pair': market['id'],
};
if (limit !== undefined) {
request['depth'] = limit;
}
const response = await this.publicGetOrderBookPair (this.extend (request, params));
const timestamp = this.safeTimestamp (response, 'timestamp');
return this.parseOrderBook (response, market['symbol'], timestamp);
}
parseOHLCV (ohlcv, market = undefined) {
//
// [
// 1591403940,
// 0.024972,
// 0.024972,
// 0.024969,
// 0.024969,
// 0.49999900
// ]
//
return [
this.safeTimestamp (ohlcv, 0),
this.safeNumber (ohlcv, 1),
this.safeNumber (ohlcv, 2),
this.safeNumber (ohlcv, 3),
this.safeNumber (ohlcv, 4),
this.safeNumber (ohlcv, 5),
];
}
async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
/**
* @method
* @name cex#fetchOHLCV
* @description fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
* @param {string} symbol unified symbol of the market to fetch OHLCV data for
* @param {string} timeframe the length of time each candle represents
* @param {int|undefined} since timestamp in ms of the earliest candle to fetch
* @param {int|undefined} limit the maximum amount of candles to fetch
* @param {object} params extra parameters specific to the cex api endpoint
* @returns {[[int]]} A list of candles ordered as timestamp, open, high, low, close, volume
*/
await this.loadMarkets ();
const market = this.market (symbol);
if (since === undefined) {
since = this.milliseconds () - 86400000; // yesterday
} else {
if (this.options['fetchOHLCVWarning']) {
throw new ExchangeError (this.id + " fetchOHLCV warning: CEX can return historical candles for a certain date only, this might produce an empty or null reply. Set exchange.options['fetchOHLCVWarning'] = false or add ({ 'options': { 'fetchOHLCVWarning': false }}) to constructor params to suppress this warning message.");
}
}
const request = {
'pair': market['id'],
'yyyymmdd': this.yyyymmdd (since, ''),
};
try {
const response = await this.publicGetOhlcvHdYyyymmddPair (this.extend (request, params));
//
// {
// "time":20200606,
// "data1m":"[[1591403940,0.024972,0.024972,0.024969,0.024969,0.49999900]]",
// }
//
const key = 'data' + this.timeframes[timeframe];
const data = this.safeString (response, key);
const ohlcvs = JSON.parse (data);
return this.parseOHLCVs (ohlcvs, market, timeframe, since, limit);
} catch (e) {
if (e instanceof NullResponse) {
return [];
}
}
}
parseTicker (ticker, market = undefined) {
const timestamp = this.safeTimestamp (ticker, 'timestamp');
const volume = this.safeString (ticker, 'volume');
const high = this.safeString (ticker, 'high');
const low = this.safeString (ticker, 'low');
const bid = this.safeString (ticker, 'bid');
const ask = this.safeString (ticker, 'ask');
const last = this.safeString (ticker, 'last');
const symbol = this.safeSymbol (undefined, market);
return this.safeTicker ({
'symbol': symbol,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'high': high,
'low': low,
'bid': bid,
'bidVolume': undefined,
'ask': ask,
'askVolume': undefined,
'vwap': undefined,
'open': undefined,
'close': last,
'last': last,
'previousClose': undefined,
'change': undefined,
'percentage': undefined,
'average': undefined,
'baseVolume': volume,
'quoteVolume': undefined,
'info': ticker,
}, market);
}
async fetchTickers (symbols = undefined, params = {}) {
/**
* @method
* @name cex#fetchTickers
* @description fetches price tickers for multiple markets, statistical calculations with the information calculated over the past 24 hours each market
* @param {[string]|undefined} symbols unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
* @param {object} params extra parameters specific to the cex api endpoint
* @returns {object} an array of [ticker structures]{@link https://docs.ccxt.com/en/latest/manual.html#ticker-structure}
*/
await this.loadMarkets ();
symbols = this.marketSymbols (symbols);
const currencies = Object.keys (this.currencies);
const request = {
'currencies': currencies.join ('/'),
};
const response = await this.publicGetTickersCurrencies (this.extend (request, params));
const tickers = this.safeValue (response, 'data', []);
const result = {};
for (let t = 0; t < tickers.length; t++) {
const ticker = tickers[t];
const marketId = this.safeString (ticker, 'pair');
const market = this.safeMarket (marketId, undefined, ':');
const symbol = market['symbol'];
result[symbol] = this.parseTicker (ticker, market);
}
return this.filterByArray (result, 'symbol', symbols);
}
async fetchTicker (symbol, params = {}) {
/**
* @method
* @name cex#fetchTicker
* @description fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
* @param {string} symbol unified symbol of the market to fetch the ticker for
* @param {object} params extra parameters specific to the cex api endpoint
* @returns {object} a [ticker structure]{@link https://docs.ccxt.com/en/latest/manual.html#ticker-structure}
*/
await this.loadMarkets ();
const market = this.market (symbol);
const request = {
'pair': market['id'],
};
const ticker = await this.publicGetTickerPair (this.extend (request, params));
return this.parseTicker (ticker, market);
}
parseTrade (trade, market = undefined) {
//
// fetchTrades (public)
//
// {
// "type": "sell",
// "date": "1638401878",
// "amount": "0.401000",
// "price": "249",
// "tid": "11922"
// }
//
const timestamp = this.safeTimestamp (trade, 'date');
const id = this.safeString (trade, 'tid');
const type = undefined;
const side = this.safeString (trade, 'type');
const priceString = this.safeString (trade, 'price');
const amountString = this.safeString (trade, 'amount');
market = this.safeMarket (undefined, market);
return this.safeTrade ({
'info': trade,
'id': id,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'symbol': market['symbol'],
'type': type,
'side': side,
'order': undefined,
'takerOrMaker': undefined,
'price': priceString,
'amount': amountString,
'cost': undefined,
'fee': undefined,
}, market);
}
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
/**
* @method
* @name cex#fetchTrades
* @description get the list of most recent trades for a particular symbol
* @param {string} symbol unified symbol of the market to fetch trades for
* @param {int|undefined} since timestamp in ms of the earliest trade to fetch
* @param {int|undefined} limit the maximum amount of trades to fetch
* @param {object} params extra parameters specific to the cex api endpoint
* @returns {[object]} a list of [trade structures]{@link https://docs.ccxt.com/en/latest/manual.html?#public-trades}
*/
await this.loadMarkets ();
const market = this.market (symbol);
const request = {
'pair': market['id'],
};
const response = await this.publicGetTradeHistoryPair (this.extend (request, params));
return this.parseTrades (response, market, since, limit);
}
async fetchTradingFees (params = {}) {
/**
* @method
* @name cex#fetchTradingFees
* @description fetch the trading fees for multiple markets
* @param {object} params extra parameters specific to the cex api endpoint
* @returns {object} a dictionary of [fee structures]{@link https://docs.ccxt.com/en/latest/manual.html#fee-structure} indexed by market symbols
*/
await this.loadMarkets ();
const response = await this.privatePostGetMyfee (params);
//
// {
// e: 'get_myfee',
// ok: 'ok',
// data: {
// 'BTC:USD': { buy: '0.25', sell: '0.25', buyMaker: '0.15', sellMaker: '0.15' },
// 'ETH:USD': { buy: '0.25', sell: '0.25', buyMaker: '0.15', sellMaker: '0.15' },
// ..
// }
// }
//
const data = this.safeValue (response, 'data', {});
const result = {};
for (let i = 0; i < this.symbols.length; i++) {
const symbol = this.symbols[i];
const market = this.market (symbol);
const fee = this.safeValue (data, market['id'], {});
const makerString = this.safeString (fee, 'buyMaker');
const takerString = this.safeString (fee, 'buy');
const maker = this.parseNumber (Precise.stringDiv (makerString, '100'));
const taker = this.parseNumber (Precise.stringDiv (takerString, '100'));
result[symbol] = {
'info': fee,
'symbol': symbol,
'maker': maker,
'taker': taker,
'percentage': true,
};
}
return result;
}
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
/**
* @method
* @name cex#createOrder
* @description create a trade order
* @param {string} symbol unified symbol of the market to create an order in
* @param {string} type 'market' or 'limit'
* @param {string} side 'buy' or 'sell'
* @param {float} amount how much of currency you want to trade in units of base currency
* @param {float|undefined} price the price at which the order is to be fullfilled, in units of the quote currency, ignored in market orders
* @param {object} params extra parameters specific to the cex api endpoint
* @returns {object} an [order structure]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure}
*/
// for market buy it requires the amount of quote currency to spend
if ((type === 'market') && (side === 'buy')) {
if (this.options['createMarketBuyOrderRequiresPrice']) {
if (price === undefined) {
throw new InvalidOrder (this.id + " createOrder() requires the price argument with market buy orders to calculate total order cost (amount to spend), where cost = amount * price. Supply a price argument to createOrder() call if you want the cost to be calculated for you from price and amount, or, alternatively, add .options['createMarketBuyOrderRequiresPrice'] = false to supply the cost in the amount argument (the exchange-specific behaviour)");
} else {
amount = amount * price;
}
}
}
await this.loadMarkets ();
const market = this.market (symbol);
const request = {
'pair': market['id'],
'type': side,
'amount': amount,
};
if (type === 'limit') {
request['price'] = price;
} else {
request['order_type'] = type;
}
const response = await this.privatePostPlaceOrderPair (this.extend (request, params));
//
// {
// "id": "12978363524",
// "time": 1586610022259,
// "type": "buy",
// "price": "0.033934",
// "amount": "0.10722802",
// "pending": "0.10722802",
// "complete": false
// }
//
const placedAmount = this.safeNumber (response, 'amount');
const remaining = this.safeNumber (response, 'pending');
const timestamp = this.safeValue (response, 'time');
const complete = this.safeValue (response, 'complete');
const status = complete ? 'closed' : 'open';
let filled = undefined;
if ((placedAmount !== undefined) && (remaining !== undefined)) {
filled = Math.max (placedAmount - remaining, 0);
}
return {
'id': this.safeString (response, 'id'),
'info': response,
'clientOrderId': undefined,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'lastTradeTimestamp': undefined,
'type': type,
'side': this.safeString (response, 'type'),
'symbol': market['symbol'],
'status': status,
'price': this.safeNumber (response, 'price'),
'amount': placedAmount,
'cost': undefined,
'average': undefined,
'remaining': remaining,
'filled': filled,
'fee': undefined,
'trades': undefined,
};
}
async cancelOrder (id, symbol = undefined, params = {}) {
/**
* @method
* @name cex#cancelOrder
* @description cancels an open order
* @param {string} id order id
* @param {string|undefined} symbol not used by cex cancelOrder ()
* @param {object} params extra parameters specific to the cex api endpoint
* @returns {object} An [order structure]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure}
*/
await this.loadMarkets ();
const request = {
'id': id,
};
return await this.privatePostCancelOrder (this.extend (request, params));
}
parseOrder (order, market = undefined) {
// Depending on the call, 'time' can be a unix int, unix string or ISO string
// Yes, really
let timestamp = this.safeValue (order, 'time');
if (typeof timestamp === 'string' && timestamp.indexOf ('T') >= 0) {
// ISO8601 string
timestamp = this.parse8601 (timestamp);
} else {
// either integer or string integer
timestamp = parseInt (timestamp);
}
let symbol = undefined;
if (market === undefined) {
const baseId = this.safeString (order, 'symbol1');
const quoteId = this.safeString (order, 'symbol2');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
symbol = base + '/' + quote;
if (symbol in this.markets) {
market = this.market (symbol);
}
}
const status = this.parseOrderStatus (this.safeString (order, 'status'));
const price = this.safeNumber (order, 'price');
let amount = this.safeNumber (order, 'amount');
// sell orders can have a negative amount
// https://github.com/ccxt/ccxt/issues/5338
if (amount !== undefined) {
amount = Math.abs (amount);
}
const remaining = this.safeNumber2 (order, 'pending', 'remains');
const filled = amount - remaining;
let fee = undefined;
let cost = undefined;
if (market !== undefined) {
symbol = market['symbol'];
const taCost = this.safeNumber (order, 'ta:' + market['quote']);
const ttaCost = this.safeNumber (order, 'tta:' + market['quote']);
cost = this.sum (taCost, ttaCost);
const baseFee = 'fa:' + market['base'];
const baseTakerFee = 'tfa:' + market['base'];
const quoteFee = 'fa:' + market['quote'];
const quoteTakerFee = 'tfa:' + market['quote'];
let feeRate = this.safeNumber (order, 'tradingFeeMaker');
if (!feeRate) {
feeRate = this.safeNumber (order, 'tradingFeeTaker', feeRate);
}
if (feeRate) {
feeRate /= 100.0; // convert to mathematically-correct percentage coefficients: 1.0 = 100%
}
if ((baseFee in order) || (baseTakerFee in order)) {
const baseFeeCost = this.safeNumber2 (order, baseFee, baseTakerFee);
fee = {
'currency': market['base'],
'rate': feeRate,
'cost': baseFeeCost,
};
} else if ((quoteFee in order) || (quoteTakerFee in order)) {
const quoteFeeCost = this.safeNumber2 (order, quoteFee, quoteTakerFee);
fee = {
'currency': market['quote'],
'rate': feeRate,
'cost': quoteFeeCost,
};
}
}
if (!cost) {
cost = price * filled;
}
const side = order['type'];
let trades = undefined;
const orderId = order['id'];
if ('vtx' in order) {
trades = [];
for (let i = 0; i < order['vtx'].length; i++) {
const item = order['vtx'][i];
const tradeSide = this.safeString (item, 'type');
if (tradeSide === 'cancel') {
// looks like this might represent the cancelled part of an order
// { id: '4426729543',
// type: 'cancel',
// time: '2017-09-22T00:24:30.476Z',
// user: 'up106404164',
// c: 'user:up106404164:a:BCH',
// d: 'order:4426728375:a:BCH',
// a: '0.09935956',
// amount: '0.09935956',
// balance: '0.42580261',
// symbol: 'BCH',
// order: '4426728375',
// buy: null,
// sell: null,
// pair: null,
// pos: null,
// cs: '0.42580261',
// ds: 0 }
continue;
}
const tradePrice = this.safeNumber (item, 'price');
if (tradePrice === undefined) {
// this represents the order
// {
// "a": "0.47000000",
// "c": "user:up106404164:a:EUR",
// "d": "order:6065499239:a:EUR",
// "cs": "1432.93",
// "ds": "476.72",
// "id": "6065499249",
// "buy": null,
// "pos": null,
// "pair": null,
// "sell": null,
// "time": "2018-04-22T13:07:22.152Z",
// "type": "buy",
// "user": "up106404164",
// "order": "6065499239",
// "amount": "-715.97000000",
// "symbol": "EUR",
// "balance": "1432.93000000" }
continue;
}
// todo: deal with these
if (tradeSide === 'costsNothing') {
continue;
}
// --
// if (side !== tradeSide)
// throw new Error (JSON.stringify (order, null, 2));
// if (orderId !== item['order'])
// throw new Error (JSON.stringify (order, null, 2));
// --
// partial buy trade
// {
// "a": "0.01589885",
// "c": "user:up106404164:a:BTC",
// "d": "order:6065499239:a:BTC",
// "cs": "0.36300000",
// "ds": 0,
// "id": "6067991213",
// "buy": "6065499239",
// "pos": null,
// "pair": null,
// "sell": "6067991206",
// "time": "2018-04-22T23:09:11.773Z",
// "type": "buy",
// "user": "up106404164",
// "order": "6065499239",
// "price": 7146.5,
// "amount": "0.01589885",
// "symbol": "BTC",
// "balance": "0.36300000",
// "symbol2": "EUR",
// "fee_amount": "0.19" }
// --
// trade with zero amount, but non-zero fee
// {
// "a": "0.00000000",
// "c": "user:up106404164:a:EUR",
// "d": "order:5840654423:a:EUR",
// "cs": 559744,
// "ds": 0,
// "id": "5840654429",
// "buy": "5807238573",
// "pos": null,
// "pair": null,
// "sell": "5840654423",
// "time": "2018-03-15T03:20:14.010Z",
// "type": "sell",
// "user": "up106404164",
// "order": "5840654423",
// "price": 730,
// "amount": "0.00000000",
// "symbol": "EUR",
// "balance": "5597.44000000",
// "symbol2": "BCH",
// "fee_amount": "0.01" }
// --
// trade which should have an amount of exactly 0.002BTC
// {
// "a": "16.70000000",
// "c": "user:up106404164:a:GBP",
// "d": "order:9927386681:a:GBP",
// "cs": "86.90",
// "ds": 0,
// "id": "9927401610",
// "buy": "9927401601",
// "pos": null,
// "pair": null,
// "sell": "9927386681",
// "time": "2019-08-21T15:25:37.777Z",
// "type": "sell",
// "user": "up106404164",
// "order": "9927386681",
// "price": 8365,
// "amount": "16.70000000",
// "office": "UK",
// "symbol": "GBP",
// "balance": "86.90000000",
// "symbol2": "BTC",
// "fee_amount": "0.03"
// }
const tradeTimestamp = this.parse8601 (this.safeString (item, 'time'));
const tradeAmount = this.safeNumber (item, 'amount');
const feeCost = this.safeNumber (item, 'fee_amount');
let absTradeAmount = (tradeAmount < 0) ? -tradeAmount : tradeAmount;
let tradeCost = undefined;
if (tradeSide === 'sell') {
tradeCost = absTradeAmount;
absTradeAmount = this.sum (feeCost, tradeCost) / tradePrice;
} else {
tradeCost = absTradeAmount * tradePrice;
}
trades.push ({
'id': this.safeString (item, 'id'),
'timestamp': tradeTimestamp,
'datetime': this.iso8601 (tradeTimestamp),
'order': orderId,
'symbol': symbol,
'price': tradePrice,
'amount': absTradeAmount,
'cost': tradeCost,
'side': tradeSide,
'fee': {
'cost': feeCost,
'currency': market['quote'],
},
'info': item,
'type': undefined,
'takerOrMaker': undefined,
});
}
}
return {
'id': orderId,
'clientOrderId': undefined,
'datetime': this.iso8601 (timestamp),
'timestamp': timestamp,
'lastTradeTimestamp': undefined,
'status': status,
'symbol': symbol,
'type': (price === undefined) ? 'market' : 'limit',
'timeInForce': undefined,
'postOnly': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'cost': cost,
'amount': amount,
'filled': filled,
'remaining': remaining,
'trades': trades,
'fee': fee,
'info': order,
'average': undefined,
};
}
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
/**
* @method
* @name cex#fetchOpenOrders
* @description fetch all unfilled currently open orders
* @param {string|undefined} symbol unified market symbol
* @param {int|undefined} since the earliest time in ms to fetch open orders for
* @param {int|undefined} limit the maximum number of open orders structures to retrieve
* @param {object} params extra parameters specific to the cex api endpoint
* @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure}
*/
await this.loadMarkets ();
const request = {};
let method = 'privatePostOpenOrders';
let market = undefined;
if (symbol !== undefined) {
market = this.market (symbol);
request['pair'] = market['id'];
method += 'Pair';
}
const orders = await this[method] (this.extend (request, params));
for (let i = 0; i < orders.length; i++) {
orders[i] = this.extend (orders[i], { 'status': 'open' });
}
return this.parseOrders (orders, market, since, limit);
}
async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
/**
* @method
* @name cex#fetchClosedOrders
* @description fetches information on multiple closed orders made by the user
* @param {string} symbol unified market symbol of the market orders were made in
* @param {int|undefined} since the earliest time in ms to fetch orders for
* @param {int|undefined} limit the maximum number of orde structures to retrieve
* @param {object} params extra parameters specific to the cex api endpoint
* @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure}
*/
await this.loadMarkets ();
const method = 'privatePostArchivedOrdersPair';
if (symbol === undefined) {
throw new ArgumentsRequired (this.id + ' fetchClosedOrders() requires a symbol argument');
}
const market = this.market (symbol);
const request = { 'pair': market['id'] };
const response = await this[method] (this.extend (request, params));
return this.parseOrders (response, market, since, limit);
}
async fetchOrder (id, symbol = undefined, params = {}) {
/**
* @method
* @name cex#fetchOrder
* @description fetches information on an order made by the user
* @param {string|undefined} symbol not used by cex fetchOrder
* @param {object} params extra parameters specific to the cex api endpoint
* @returns {object} An [order structure]{@link https://docs.ccxt.com/en/latest/manual.html#order-structure}
*/
await this.loadMar