ccxt-look
Version:
1,188 lines (1,146 loc) • 52.1 kB
JavaScript
'use strict';
// ----------------------------------------------------------------------------
const Exchange = require ('./base/Exchange');
const { ExchangeError, AuthenticationError, ArgumentsRequired, OrderNotFound, BadRequest, PermissionDenied, RateLimitExceeded } = require ('./base/errors');
const { TICK_SIZE } = require ('./base/functions/number');
const Precise = require ('./base/Precise');
// ----------------------------------------------------------------------------
module.exports = class fairdesk extends Exchange {
describe () {
return this.deepExtend (super.describe (), {
'id': 'fairdesk',
'name': 'Fairdesk',
'countries': [ 'CN' ], // China
'rateLimit': 100,
'version': 'v1',
'certified': false,
'pro': true,
'hostname': 'api.fairdesk.com',
'has': {
'CORS': undefined,
'spot': false,
'margin': false,
'swap': undefined, // has but not fully implemented
'future': false,
'option': false,
'cancelAllOrders': true,
'cancelOrder': true,
'createOrder': true,
'editOrder': false,
'fetchBalance': true,
'fetchBorrowRate': false,
'fetchBorrowRateHistories': false,
'fetchBorrowRateHistory': false,
'fetchBorrowRates': false,
'fetchBorrowRatesPerSymbol': false,
'fetchClosedOrders': true,
'fetchCurrencies': false,
'fetchDepositAddress': true,
'fetchDeposits': true,
'fetchIndexOHLCV': false,
'fetchLeverageTiers': false,
'fetchMarkets': true,
'fetchMarkOHLCV': false,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchOrder': true,
'fetchOrderBook': true,
'fetchOrders': true,
'fetchPositions': true,
'fetchPremiumIndexOHLCV': false,
'fetchTicker': true,
'fetchTrades': true,
'fetchTradingFee': false,
'fetchTradingFees': false,
'fetchWithdrawals': false,
'setLeverage': true,
'transfer': false,
'withdraw': false,
},
'urls': {
'logo': 'https://static.fairdesk.com/font/fairdesklogo.png',
'test': {
'public': 'https://api-testnet.fairdesk.com/api/v1/public',
'private': 'https://api-testnet.fairdesk.com/api/v1/private',
},
'api': {
'public': 'https://{hostname}/api/v1/public',
'private': 'https://{hostname}/api/v1/private',
},
'www': 'https://www.fairdesk.com',
'doc': 'https://github.com/fairdesk/fairdesk-api-docs',
'fees': 'https://www.fairdesk.com/fees',
'referral': 'https://www.fairdesk.com/signup?ref=URIJD5NI',
},
'timeframes': {
'1m': '60',
'3m': '180',
'5m': '300',
'15m': '900',
'30m': '1800',
'1h': '3600',
'2h': '7200',
'3h': '10800',
'4h': '14400',
'6h': '21600',
'12h': '43200',
'1d': '86400',
'1w': '604800',
'1M': '2592000',
},
'api': {
'public': {
'get': [
'products', // contracts only
'md/orderbook', // ?symbol=BTCUSDT
'md/ticker24h', // ?symbol=BTCUSDT
'md/trade-recent', // ?symbol=BTCUSDT
'md/trade-history', // ?symbol=BTCUSDT&from=1651382628000
'md/kline', // ?symbol=BTCUSDT&interval=5m&from=1651382628000&to=1651469028000
],
},
'private': {
'get': [
// swap
'account/order-detail', // orderId=571643709
'account/order-histories', // ?symbol=&type=&pageIndex=1&pageSize=1000
'account/balance',
'account/symbol-config',
'account/open-orders',
'account/positions',
'account/trade-histories',
// wallet
'wallet/deposit-address', // ?currency=ETH
'wallet/deposit-records', // ?startTime=0¤cy=USDT&pageIndex=1&pageSize=20
],
'post': [
// swap
'trade/place-order',
'trade/cancel-all-order',
],
'put': [
// swap
'account/config/adjust-leverage',
],
'delete': [
// swap
'trade/cancel-order',
],
},
},
'precisionMode': TICK_SIZE,
'fees': {
'trading': {
'tierBased': false,
'percentage': true,
'taker': this.parseNumber ('0.001'),
'maker': this.parseNumber ('0.001'),
},
},
'requiredCredentials': {
'apiKey': true,
'secret': true,
},
'exceptions': {
'exact': {
// not documented
'412': BadRequest, // {"code":412,"msg":"Missing parameter - resolution","data":null}
'6001': BadRequest, // {"error":{"code":6001,"message":"invalid argument"},"id":null,"result":null}
// documented
'19999': BadRequest, // REQUEST_IS_DUPLICATED Duplicated request ID
// not documented
'30000': BadRequest, // {"code":30000,"msg":"Please double check input arguments","data":null}
'39995': RateLimitExceeded, // {"code": "39995","msg": "Too many requests."}
'39996': PermissionDenied, // {"code": "39996","msg": "Access denied."}
},
'broad': {
'401 Insufficient privilege': PermissionDenied, // {"code": "401","msg": "401 Insufficient privilege."}
'401 Request IP mismatch': PermissionDenied, // {"code": "401","msg": "401 Request IP mismatch."}
'Failed to find api-key': AuthenticationError, // {"msg":"Failed to find api-key 1c5ec63fd-660d-43ea-847a-0d3ba69e106e","code":10500}
'Missing required parameter': BadRequest, // {"msg":"Missing required parameter","code":10500}
'API Signature verification failed': AuthenticationError, // {"msg":"API Signature verification failed.","code":10500}
'Api key not found': AuthenticationError, // {"msg":"Api key not found 698dc9e3-6faa-4910-9476-12857e79e198","code":"10500"}
},
},
'options': {
'x-fairdesk-request-expiry': 60, // in seconds
'createOrderByQuoteRequiresPrice': true,
'networks': {
'ERC20': 'ETH',
'TRC20': 'TRX',
},
'defaultNetworks': {
'USDT': 'ETH',
},
'defaultSubType': 'linear',
'accountsByType': {
'future': 'future',
},
},
});
}
parseSafeNumber (value = undefined) {
if (value === undefined) {
return value;
}
let parts = value.split (',');
value = parts.join ('');
parts = value.split (' ');
return this.safeNumber (parts, 0);
}
parseMaxLevel (symbolName) {
const hh = [ 'BTCUSDT' ];
const h = [ 'ETHUSDT' ];
const m = [ 'ADAUSDT', 'BNBUSDT', 'DOTUSDT' ];
const l = [ 'XRPUSDT', 'UNIUSDT', 'DOGEUSDT' ];
symbolName = symbolName.toLocaleUpperCase ();
const hhLeverages = [ 50000, 125, 100, 80, 60, 40, 20, 10, 5 ];
const hLeverages = [ 30000, 80, 60, 40, 20, 10, 5 ];
const mLeverages = [ 20000, 50, 30, 20, 10, 5, 4 ];
const lLeverages = [ 10000, 25, 20, 15, 10, 5, 4 ];
if (hh.indexOf (symbolName) >= 0) {
return hhLeverages;
}
if (h.indexOf (symbolName) >= 0) {
return hLeverages;
}
if (m.indexOf (symbolName) >= 0) {
return mLeverages;
}
if (l.indexOf (symbolName) >= 0) {
return lLeverages;
}
return lLeverages;
}
parseSwapMarket (market) {
// {
// amountDecimal: 3
// baseCurrency: "BTC"
// defaultLeverage: 20
// displayName: "BTC/USDT"
// fundingInterval: "Every 8 hours"
// limitPriceDiffRate: 0.1
// liquidationFeeRate: 0.01
// logUrl: "https://sgtnstatic-1306519353.file.myqcloud.com/currency/BTC.png"
// makerFeeRate: "0.0001"
// marketMaxQty: "0.000000"
// marketPriceDiffRate: 0.1
// maxOrderQty: "100.000000"
// maxPrice: "500000.000000"
// minOrderQty: "0.000000"
// minPrice: "0.500000"
// priceDecimal: 1
// priceScale: 6
// productType: "Perpetual"
// quoteCurrency: "USDT"
// ratioScale: 8
// settleCurrency: "USDT"
// status: "TRADING"
// stepSize: "0.001000"
// strategyPriceDiffRate: 0.075
// symbol: "btcusdt"
// symbolId: 1211
// takerFeeRate: "0.00015"
// tickSize: "0.500000"
// type: "FUTURE_PERPETUAL"
// valueScale: 6
// }
const id = this.safeString (market, 'symbol');
const baseId = this.safeString (market, 'baseCurrency');
const quoteId = this.safeString (market, 'quoteCurrency');
const settleId = this.safeString (market, 'settleCurrency');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
const settle = this.safeCurrencyCode (settleId);
let inverse = false;
if (settleId !== quoteId) {
inverse = true;
}
const priceScale = this.safeInteger (market, 'priceScale');
const ratioScale = this.safeInteger (market, 'ratioScale');
const minPriceEp = this.safeString (market, 'minPrice');
const maxPriceEp = this.safeString (market, 'maxPrice');
const makerFeeRateEr = this.safeString (market, 'makerFeeRate');
const takerFeeRateEr = this.safeString (market, 'takerFeeRate');
const status = this.safeString (market, 'status');
// the size of one contract, only used if `contract` is true
const contractSize = this.parseNumber (this.safeString (market, 'stepSize'));
const maxLevel = this.parseMaxLevel (id)[1];
return {
'id': id,
'symbol': base + '/' + quote,
'base': base,
'quote': quote,
'settle': settle,
'baseId': baseId,
'quoteId': quoteId,
'settleId': settleId,
'type': 'swap',
'spot': false,
'margin': false,
'swap': true,
'future': false,
'option': false,
'active': status === 'TRADING',
'contract': true,
'linear': !inverse,
'inverse': inverse,
'taker': this.parseNumber (takerFeeRateEr),
'maker': this.parseNumber (makerFeeRateEr),
'contractSize': contractSize,
'expiry': undefined,
'expiryDatetime': undefined,
'strike': undefined,
'optionType': undefined,
'priceScale': priceScale,
'ratioScale': ratioScale,
'precision': {
'amount': this.safeNumber (market, 'stepSize'),
'price': this.safeNumber (market, 'tickSize'),
},
'limits': {
'leverage': {
'min': this.parseNumber ('1'),
'max': this.parseNumber (maxLevel),
},
'amount': {
'min': undefined,
'max': undefined,
},
'price': {
'min': this.parseNumber (minPriceEp),
'max': this.parseNumber (maxPriceEp),
},
'cost': {
'min': this.parseNumber (this.safeString (market, 'minOrderQty')),
'max': this.parseNumber (this.safeString (market, 'maxOrderQty')),
},
},
'info': market,
};
}
async fetchMarkets (params = {}) {
const res = await this.publicGetProducts (params);
const products = res.data;
const result = [];
for (let i = 0; i < products.length; i++) {
let market = products[i];
market = this.parseSwapMarket (market);
result.push (market);
}
return result;
}
async fetchCurrencies (params = {}) {
const response = await this.userGetPublicInstrumentChainCurrency (params);
const currencies = this.safeValue (response, 'data', {});
const result = {};
for (let i = 0; i < currencies.length; i++) {
const currency = currencies[i];
const id = this.safeString (currency, 'currency');
const name = this.safeString (currency, 'currency');
const code = this.safeCurrencyCode (id);
const valueScaleString = this.safeString (currency, 'valueScale');
const valueScale = parseInt (valueScaleString);
const minValueEv = this.safeString (currency, 'minValueEv');
const maxValueEv = this.safeString (currency, 'maxValueEv');
let minAmount = undefined;
let maxAmount = undefined;
let precision = undefined;
if (valueScale !== undefined) {
const precisionString = this.parsePrecision (valueScaleString);
precision = this.parseNumber (precisionString);
minAmount = this.parseNumber (Precise.stringMul (minValueEv, precisionString));
maxAmount = this.parseNumber (Precise.stringMul (maxValueEv, precisionString));
}
result[code] = {
'id': id,
'info': currency,
'code': code,
'name': name,
'active': undefined,
'deposit': undefined,
'withdraw': undefined,
'fee': undefined,
'precision': precision,
'limits': {
'amount': {
'min': minAmount,
'max': maxAmount,
},
'withdraw': {
'min': undefined,
'max': undefined,
},
},
'valueScale': valueScale,
};
}
return result;
}
parseBidAsk (bidask, priceKey = 0, amountKey = 1, market = undefined) {
if (market === undefined) {
throw new ArgumentsRequired (this.id + ' parseBidAsk() requires a market argument');
}
const amount = this.safeString (bidask, amountKey);
return [
this.parseNumber (this.safeString (bidask, priceKey)),
this.parseNumber (amount),
];
}
parseOrderBook (orderbook, symbol, timestamp = undefined, bidsKey = 'bids', asksKey = 'asks', priceKey = 0, amountKey = 1, market = undefined) {
const result = {
'symbol': symbol,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'nonce': undefined,
};
const sides = [ bidsKey, asksKey ];
for (let i = 0; i < sides.length; i++) {
const side = sides[i];
const orders = [];
const bidasks = this.safeValue (orderbook, side);
for (let k = 0; k < bidasks.length; k++) {
orders.push (this.parseBidAsk (bidasks[k], priceKey, amountKey, market));
}
result[side] = orders;
}
result[bidsKey] = this.sortBy (result[bidsKey], 0, true);
result[asksKey] = this.sortBy (result[asksKey], 0);
return result;
}
async fetchOrderBook (symbol, limit = undefined, params = {}) {
await this.loadMarkets ();
const symbolId = this.parseSymbol (symbol);
const market = this.market (symbolId);
const request = {
'symbol': market['id'],
};
const response = await this.publicGetMdOrderbook (this.extend (request, params));
const result = this.safeValue (response, 'data', {});
const timestamp = this.safeIntegerProduct (result, 'timestamp', 0.000001);
const orderbook = this.parseOrderBook (result, symbol, timestamp, 'bids', 'asks', 0, 1, market);
orderbook['nonce'] = this.safeInteger (result, 'sequence');
return orderbook;
}
parseOHLCV (ohlcv, market = undefined) {
return [
this.parseNumber (this.safeString (ohlcv, 'closeTime')),
this.parseNumber (this.safeString (ohlcv, 'open')),
this.parseNumber (this.safeString (ohlcv, 'high')),
this.parseNumber (this.safeString (ohlcv, 'low')),
this.parseNumber (this.safeString (ohlcv, 'close')),
this.parseNumber (this.safeString (ohlcv, 'volume')),
];
}
async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
const request = {
'interval': timeframe,
};
let startTime = 0;
let endTime = 0;
const duration = this.parseTimeframe (timeframe);
const now = this.seconds ();
if (since !== undefined) {
if (limit === undefined) {
limit = 1000; // max 1000
}
since = parseInt (since / 1000);
startTime = since;
// time ranges ending in the future are not accepted
// https://github.com/ccxt/ccxt/issues/8050
endTime = Math.min (now, this.sum (since, duration * limit));
} else if (limit !== undefined) {
limit = Math.min (limit, 1000);
startTime = now - duration * this.sum (limit, 1);
endTime = now;
} else {
throw new ArgumentsRequired (this.id + ' fetchOHLCV() requires a since argument, or a limit argument, or both');
}
request['from'] = startTime * 1000;
request['to'] = endTime * 1000;
await this.loadMarkets ();
const symbolId = this.parseSymbol (symbol);
const market = this.market (symbolId);
request['symbol'] = market['id'];
const response = await this.publicGetMdKline (this.extend (request, params));
const rows = this.safeValue (response, 'data', {});
return this.parseOHLCVs (rows, market, timeframe, since, limit);
}
parseTicker (ticker, market = undefined) {
const marketId = this.safeString (ticker, 'symbol');
market = this.safeMarket (marketId, market);
const symbol = market['symbol'];
const timestamp = this.safeIntegerProduct (ticker, 'timestamp', 0.000001);
const last = this.safeString (ticker, 'close');
const open = this.safeString (ticker, 'open');
return this.safeTicker ({
'symbol': symbol,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'high': this.safeString (ticker, 'high'),
'low': this.safeString (ticker, 'low'),
'bid': this.safeString (ticker, 'bid'),
'bidVolume': undefined,
'ask': this.safeString (ticker, 'askEp'),
'askVolume': undefined,
'vwap': undefined,
'open': open,
'close': last,
'last': last,
'previousClose': undefined, // previous day close
'change': undefined,
'percentage': undefined,
'average': undefined,
'baseVolume': this.safeString (ticker, 'baseVolume'),
'quoteVolume': this.safeString (ticker, 'quoteVolume'),
'info': ticker,
}, market, false);
}
parseSymbol (symbol) {
const str = symbol.replace ('/', '');
const res = str.toLowerCase ();
return res;
}
async fetchTicker (symbol, params = {}) {
await this.loadMarkets ();
const symbolId = this.parseSymbol (symbol);
const market = this.market (symbolId);
const request = {
'symbol': market['id'],
};
const method = 'publicGetMdTicker24h';
const response = await this[method] (this.extend (request, params));
const result = this.safeValue (response, 'data', {});
return this.parseTicker (result, market);
}
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
const symbolId = this.parseSymbol (symbol);
await this.loadMarkets ();
const market = this.market (symbolId);
const request = {
'symbol': market['id'],
};
let method = 'publicGetMdTradeRecent';
if (since) {
method = 'publicGetMdTradeHistory';
request['from'] = since;
}
const response = await this[method] (this.extend (request, params));
const result = this.safeValue (response, 'data', {});
const trades = this.safeValue (result, 'trades', []);
return this.parseTrades (trades, market, since, limit);
}
parseTrade (trade, market = undefined) {
let priceString = undefined;
let amountString = undefined;
let timestamp = undefined;
let id = undefined;
let side = undefined;
let costString = undefined;
let type = undefined;
let fee = undefined;
const marketId = this.safeString (trade, 'symbol');
market = this.safeMarket (marketId, market);
const symbol = market['symbol'];
let orderId = undefined;
let takerOrMaker = undefined;
if (Array.isArray (trade)) {
const tradeLength = trade.length;
timestamp = this.safeIntegerProduct (trade, 0, 0.001);
if (tradeLength > 4) {
id = this.safeString (trade, tradeLength - 4);
}
side = this.safeStringLower (trade, tradeLength - 3);
priceString = this.safeString (trade, tradeLength - 2);
amountString = this.safeString (trade, tradeLength - 1);
} else {
timestamp = this.safeNumber (trade, 'timestamp');
id = this.safeString2 (trade, 'execId', 'execID');
orderId = this.safeString (trade, 'orderID');
side = this.safeStringLower (trade, 'side');
type = this.parseOrderType (this.safeString (trade, 'ordType'));
const execStatus = this.safeString (trade, 'execStatus');
if (execStatus === 'MakerFill') {
takerOrMaker = 'maker';
}
priceString = this.safeString (trade, 'price');
amountString = this.safeString (trade, 'qty');
costString = this.safeString2 (trade, 'price', 'qty');
const feeCostString = this.safeString (trade, 'execFeeEv');
if (feeCostString !== undefined) {
const feeRateString = this.safeString (trade, 'feeRateEr');
let feeCurrencyCode = undefined;
if (market['spot']) {
feeCurrencyCode = (side === 'buy') ? market['base'] : market['quote'];
} else {
const info = this.safeValue (market, 'info');
if (info !== undefined) {
const settlementCurrencyId = this.safeString (info, 'settlementCurrency');
feeCurrencyCode = this.safeCurrencyCode (settlementCurrencyId);
}
}
fee = {
'cost': this.parseNumber (feeCostString),
'rate': this.parseNumber (feeRateString),
'currency': feeCurrencyCode,
};
}
}
return this.safeTrade ({
'info': trade,
'id': id,
'symbol': symbol,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'order': orderId,
'type': type,
'side': side,
'takerOrMaker': takerOrMaker,
'price': priceString,
'amount': amountString,
'cost': costString,
'fee': fee,
}, market);
}
parseSwapBalance (response) {
const result = { 'info': response };
const data = this.safeValue (response, 'data', {});
const balance = this.safeValue (data, 'accounts', {})[0];
const currencyId = this.safeString (balance, 'currency');
const code = this.safeCurrencyCode (currencyId);
const account = this.account ();
const accountBalanceEv = this.safeString (balance, 'accountBalance');
const totalUsedBalanceEv = this.safeString (balance, 'availBalance');
account['total'] = accountBalanceEv;
account['used'] = totalUsedBalanceEv;
result[code] = account;
return this.safeBalance (result);
}
async fetchBalance (params = {}) {
await this.loadMarkets ();
const method = 'privateGetAccountBalance';
const request = {};
params = this.omit (params, 'type');
const response = await this[method] (this.extend (request, params));
const result = this.parseSwapBalance (response);
return result;
}
parseOrderStatus (status) {
const statuses = {
'Created': 'open',
'Untriggered': 'open',
'Deactivated': 'closed',
'Triggered': 'open',
'Rejected': 'rejected',
'New': 'open',
'PartiallyFilled': 'open',
'Filled': 'closed',
'Canceled': 'canceled',
};
return this.safeString (statuses, status, status);
}
parseOrderType (type) {
const types = {
'Limit': 'limit',
'Market': 'market',
};
return this.safeString (types, type, type);
}
parseTimeInForce (timeInForce) {
const timeInForces = {
'GoodTillCancel': 'GTC',
'PostOnly': 'PO',
'ImmediateOrCancel': 'IOC',
'FillOrKill': 'FOK',
};
return this.safeString (timeInForces, timeInForce, timeInForce);
}
parseSwapOrder (order, market = undefined) {
const id = this.safeString (order, 'orderId');
const state = () => {
let value = '';
if (this.safeString (order, 'status') === 'FILLED') {
const displayStr = `${this.safeString (order, 'side')}_${this.safeString (order, 'positionSide')}`;
switch (displayStr) {
case 'BUY_LONG':
value = 'open';
break;
case 'BUY_SHORT':
value = 'closed';
break;
case 'SELL_SHORT':
value = 'open';
break;
case 'SELL_LONG':
value = 'closed';
break;
default:
break;
}
return value;
}
if (this.safeString (order, 'status') === 'CANCELED') {
value = 'canceled';
return value;
}
if (this.safeString (order, 'status') === 'EXPIRED') {
value = 'expired';
return value;
}
return value;
};
let clientOrderId = this.safeString (order, 'clientOrderId');
if ((clientOrderId !== undefined) && (clientOrderId.length < 1)) {
clientOrderId = undefined;
}
const marketId = this.safeString (order, 'symbol');
const symbol = this.safeSymbol (marketId, market);
const status = state (); // 'open', 'closed', 'canceled', 'expired', 'rejected'
const side = (this.safeStringLower (order, 'side')).toLowerCase ();
const type = (this.parseOrderType (this.safeString (order, 'type'))).toLowerCase ();
const price = this.parseNumber (this.safeString (order, 'price') === '--' ? '' : this.safeString (order, 'price'));
const amount = this.safeNumber (order, 'origQty');
const filled = this.safeNumber (order, 'origQty') - this.safeNumber (order, 'executedQty');
// const remaining = this.safeNumber (order, 'leavesQty');
const remaining = undefined;
const timestamp = this.safeNumber (order, 'transactTime');
// const cost = this.safeNumber (order, 'cumValue');
const cost = undefined;
let lastTradeTimestamp = this.safeNumber (order, 'transactTimeNs');
if (lastTradeTimestamp === 0) {
lastTradeTimestamp = undefined;
}
const timeInForce = this.parseTimeInForce (this.safeString (order, 'timeInForce'));
const stopPrice = this.safeNumber (order, 'avlPrice');
const postOnly = (timeInForce === 'POST_ONLY');
return {
'info': order,
'id': id,
'clientOrderId': clientOrderId,
'datetime': this.iso8601 (timestamp),
'timestamp': timestamp,
'lastTradeTimestamp': lastTradeTimestamp,
'symbol': symbol,
'type': type,
'timeInForce': timeInForce,
'postOnly': postOnly,
'side': side,
'price': price,
'stopPrice': stopPrice,
'amount': amount,
'filled': filled,
'remaining': remaining,
'cost': cost,
'average': undefined,
'status': status,
'fee': undefined,
'trades': undefined,
};
}
parseOrder (order, market = undefined) {
return this.parseSwapOrder (order, market);
}
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
await this.loadMarkets ();
const symbolId = this.parseSymbol (symbol);
const market = this.market (symbolId);
side = this.capitalize (side);
type = this.capitalize (type);
const request = {
// common
'symbol': market['id'],
'side': side.toLocaleUpperCase (), // Sell, Buy
'type': type.toLocaleUpperCase (), // Market, Limit, Stop, StopLimit, MarketIfTouched, LimitIfTouched or Pegged for swap orders
// swap
'clientOrderId': `CCXT_${this.uuid ()}`,
'orderRespType': 'ACK',
'positionSide': side === 'Buy' ? 'LONG' : 'SHORT',
'isolated': params.isolated || false,
};
const stopPrice = this.safeString2 (params, 'stopPx', 'stopPrice');
if (stopPrice !== undefined) {
request['stopPrice'] = stopPrice;
}
params = this.omit (params, [ 'stopPx', 'stopPrice' ]);
if (market['swap']) {
request['quantity'] = amount;
if (stopPrice !== undefined) {
const triggerType = this.safeString (params, 'triggerType', 'ByMarkPrice');
request['triggerType'] = triggerType;
}
}
if ((type === 'Limit') || (type === 'StopLimit') || (type === 'LimitIfTouched')) {
const priceString = price.toString ();
request['timeInForce'] = 'GTC';
request['price'] = priceString;
}
const takeProfitPrice = this.safeString (params, 'takeProfitPrice');
if (takeProfitPrice !== undefined) {
request['tpPrice'] = takeProfitPrice;
params = this.omit (params, 'tpPrice');
}
const stopLossPrice = this.safeString (params, 'stopLossPrice');
if (stopLossPrice !== undefined) {
request['slPrice'] = stopLossPrice;
params = this.omit (params, 'slPrice');
}
const method = market['spot'] ? 'privatePostSpotOrders' : 'privatePostTradePlaceOrder';
const response = await this[method] (this.extend (request, params));
const data = this.safeValue (response, 'data', {});
return this.parseOrder (data, market);
}
async cancelOrder (id, symbol = undefined, params = {}) {
if (symbol === undefined) {
throw new ArgumentsRequired (this.id + ' cancelOrder() requires a symbol argument');
}
const symbolId = this.parseSymbol (symbol);
await this.loadMarkets ();
const market = this.market (symbolId);
const request = {
'symbol': market['id'],
'orderId': id,
};
const method = 'privateDeleteTradeCancelOrder';
const response = await this[method] (this.extend (request, params));
if (response.status !== 0) {
return response;
}
const data = this.safeValue (response, 'data', {});
return this.parseOrder (data, market);
}
async cancelAllOrders (symbol = undefined, params = {}) {
await this.loadMarkets ();
const request = {
'settleCcy': 'USDT',
};
const method = 'privatePostTradeCancelAllOrder';
return await this[method] (this.extend (request, params));
}
async fetchOrder (id, symbol = undefined, params = {}) {
if (symbol === undefined) {
throw new ArgumentsRequired (this.id + ' fetchOrder() requires a symbol argument');
}
await this.loadMarkets ();
const market = this.market (symbol);
const method = market['spot'] ? 'privateGetSpotOrdersActive' : 'privateGetAccountOrderDetail';
const request = {
'symbol': market['id'],
};
const clientOrderId = this.safeString2 (params, 'clientOrderId', 'clOrdID');
params = this.omit (params, [ 'clientOrderId', 'clOrdID' ]);
if (clientOrderId !== undefined) {
request['clOrdID'] = clientOrderId;
} else {
request['orderID'] = id;
}
const response = await this[method] (this.extend (request, params));
const data = this.safeValue (response, 'data', {});
let order = data;
if (Array.isArray (data)) {
const numOrders = data.length;
if (numOrders < 1) {
if (clientOrderId !== undefined) {
throw new OrderNotFound (this.id + ' fetchOrder ' + symbol + ' order with clientOrderId ' + clientOrderId + ' not found');
} else {
throw new OrderNotFound (this.id + ' fetchOrder ' + symbol + ' order with id ' + id + ' not found');
}
}
order = this.safeValue (data, 0, {});
}
return this.parseOrder (order, market);
}
async fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
if (symbol === undefined) {
throw new ArgumentsRequired (this.id + ' fetchOrders() requires a symbol argument');
}
const symbolId = this.parseSymbol (symbol);
await this.loadMarkets ();
const market = this.market (symbolId);
const method = market['spot'] ? 'privateGetSpotOrders' : 'privateGetAccountOrderHistories';
const request = {
'symbol': market['id'],
};
if (since !== undefined) {
request['start'] = since;
}
if (limit !== undefined) {
request['limit'] = limit;
}
const response = await this[method] (this.extend (request, params));
const data = this.safeValue (response, 'data', {});
const rows = this.safeValue (data, 'rows', []);
return this.parseOrders (rows, market, since, limit);
}
async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
if (symbol === undefined) {
throw new ArgumentsRequired (this.id + ' fetchOpenOrders() requires a symbol argument');
}
await this.loadMarkets ();
const symbolId = this.parseSymbol (symbol);
const market = this.market (symbolId);
const method = market['spot'] ? 'privateGetSpotOrders' : 'privateGetAccountOpenOrders';
const request = {
'symbol': market['id'],
};
let response = undefined;
try {
response = await this[method] (this.extend (request, params));
} catch (e) {
if (e instanceof OrderNotFound) {
return [];
}
}
const data = this.safeValue (response, 'data', {});
if (Array.isArray (data)) {
return this.parseOrders (data, market, since, limit);
} else {
const rows = this.safeValue (data, 'rows', []);
return this.parseOrders (rows, market, since, limit);
}
}
async fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
if (symbol === undefined) {
throw new ArgumentsRequired (this.id + ' fetchClosedOrders() requires a symbol argument');
}
const symbolId = this.parseSymbol (symbol);
await this.loadMarkets ();
const market = this.market (symbolId);
const method = market['spot'] ? 'privateGetExchangeSpotOrder' : 'privateGetAccountOrderHistories';
const request = {
'symbol': market['id'],
'status': 'CANCELED',
};
if (since !== undefined) {
request['start'] = since;
}
if (limit !== undefined) {
request['limit'] = limit;
}
const response = await this[method] (this.extend (request, params));
const data = this.safeValue (response, 'data', {});
if (Array.isArray (data)) {
return this.parseOrders (data, market, since, limit);
} else {
const rows = this.safeValue (data, 'rows', []);
return this.parseOrders (rows, market, since, limit);
}
}
async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
if (symbol === undefined) {
throw new ArgumentsRequired (this.id + ' fetchMyTrades() requires a symbol argument');
}
await this.loadMarkets ();
const symbolId = this.parseSymbol (symbol);
const market = this.market (symbolId);
const method = 'privateGetAccountTradeHistories';
const request = {
'symbol': market['id'],
};
if (since !== undefined) {
request['start'] = since;
}
if (market['swap'] && (limit !== undefined)) {
request['limit'] = limit;
}
const response = await this[method] (this.extend (request, params));
const data = this.safeValue (response, 'data', {});
const rows = this.safeValue (data, 'rows', []);
return this.parseTrades (rows, market, since, limit);
}
async fetchDepositAddress (code, params = {}) {
await this.loadMarkets ();
const currency = this.currency (code);
const request = {
'currency': currency['id'],
};
const defaultNetworks = this.safeValue (this.options, 'defaultNetworks');
const defaultNetwork = this.safeStringUpper (defaultNetworks, code);
const networks = this.safeValue (this.options, 'networks', {});
let network = this.safeStringUpper (params, 'network', defaultNetwork);
network = this.safeString (networks, network, network);
if (network === undefined) {
request['chainName'] = currency['id'];
} else {
request['chainName'] = network;
params = this.omit (params, 'network');
}
const response = await this.privateGetWalletDepositAddress (this.extend (request, params));
const data = this.safeValue (response, 'data', {})[0];
const address = this.safeString (data, 'address');
const tag = this.safeString (data, 'chain');
this.checkAddress (address);
return {
'currency': code,
'address': address,
'tag': tag,
'network': undefined,
'info': data,
};
}
async fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
let currency = undefined;
if (code !== undefined) {
currency = this.currency (code);
}
const response = await this.privateGetWalletDepositRecords (params);
const data = this.safeValue (response, 'data', {});
const rows = this.safeValue (data, 'rows', {});
return this.parseTransactions (rows, currency, since, limit);
}
parseTransactionStatus (status) {
const statuses = {
'Success': 'ok',
'Succeed': 'ok',
};
return this.safeString (statuses, status, status);
}
parseTransaction (transaction, currency = undefined) {
const id = this.safeString (transaction, 'id');
const address = this.safeString (transaction, 'address');
const tag = undefined;
const txid = this.safeString (transaction, 'txId');
const currencyId = this.safeString (transaction, 'currency');
currency = this.safeCurrency (currencyId, currency);
const code = currency['code'];
const timestamp = this.safeInteger (transaction, 'time');
let type = this.safeStringLower (transaction, 'type');
const feeCost = this.parseNumber (this.safeString (transaction, 'fee'));
let fee = undefined;
if (feeCost !== undefined) {
type = 'withdrawal';
fee = {
'cost': feeCost,
'currency': code,
};
}
const status = this.parseTransactionStatus (this.safeString (transaction, 'status'));
const amount = this.parseNumber (this.safeString (transaction, 'amount'));
return {
'info': transaction,
'id': id,
'txid': txid,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'network': this.safeString (transaction, 'chain'),
'address': address,
'addressTo': address,
'addressFrom': undefined,
'tag': tag,
'tagTo': tag,
'tagFrom': undefined,
'type': type,
'amount': amount,
'currency': code,
'status': status,
'updated': undefined,
'fee': fee,
};
}
async fetchPositions (symbols = undefined, params = {}) {
await this.loadMarkets ();
const request = {};
const response = await this.privateGetAccountPositions (this.extend (request, params));
const positions = this.safeValue (response, 'data', {});
const result = [];
for (let i = 0; i < positions.length; i++) {
const position = positions[i];
result.push (this.parsePosition (position));
}
return this.filterByArray (result, 'symbol', symbols, false);
}
parsePosition (position, market = undefined) {
const marketId = this.safeString (position, 'symbol');
market = this.safeMarket (marketId, market);
const symbol = market['symbol'];
const collateral = this.safeString (position, 'positionMargin');
const notionalString = this.safeString (position, 'value');
const maintenanceMarginPercentageString = this.safeString (position, 'maintMarginReq');
const maintenanceMarginString = Precise.stringMul (notionalString, maintenanceMarginPercentageString);
const initialMarginString = this.safeString (position, 'assignedPosBalance');
const initialMarginPercentageString = Precise.stringDiv (initialMarginString, notionalString);
const liquidationPrice = this.safeNumber (position, 'liquidationPrice');
const markPriceString = this.safeString (position, 'markPrice');
const contracts = this.safeString (position, 'size');
const contractSize = this.safeValue (market, 'contractSize');
const contractSizeString = this.numberToString (contractSize);
const leverage = this.safeNumber (position, 'leverage');
const entryPriceString = this.safeString (position, 'avgEntryPrice');
const rawSide = this.safeString (position, 'positionSide');
const side = (rawSide === 'Buy') ? 'long' : 'short';
let priceDiff = undefined;
const currency = this.safeString (position, 'baseCurrency');
if (currency === 'USD') {
if (side === 'long') {
priceDiff = Precise.stringSub (markPriceString, entryPriceString);
} else {
priceDiff = Precise.stringSub (entryPriceString, markPriceString);
}
} else {
// inverse
if (side === 'long') {
priceDiff = Precise.stringSub (Precise.stringDiv ('1', entryPriceString), Precise.stringDiv ('1', markPriceString));
} else {
priceDiff = Precise.stringSub (Precise.stringDiv ('1', markPriceString), Precise.stringDiv ('1', entryPriceString));
}
}
const unrealizedPnl = Precise.stringMul (Precise.stringMul (priceDiff, contracts), contractSizeString);
const percentage = Precise.stringMul (Precise.stringDiv (unrealizedPnl, initialMarginString), '100');
const marginRatio = Precise.stringDiv (maintenanceMarginString, collateral);
return {
'info': position,
'symbol': symbol,
'contracts': this.parseNumber (contracts),
'contractSize': contractSize,
'unrealizedPnl': this.parseNumber (unrealizedPnl),
'leverage': leverage,
'liquidationPrice': liquidationPrice,
'collateral': this.parseNumber (collateral),
'notional': this.parseNumber (notionalString),
'markPrice': this.parseNumber (markPriceString), // markPrice lags a bit ¯\_(ツ)_/¯
'entryPrice': this.parseNumber (entryPriceString),
'timestamp': undefined,
'initialMargin': this.parseNumber (initialMarginString),
'initialMarginPercentage': this.parseNumber (initialMarginPercentageString),
'maintenanceMargin': this.parseNumber (maintenanceMarginString),
'maintenanceMarginPercentage': this.parseNumber (maintenanceMarginPercentageString),
'marginRatio': this.parseNumber (marginRatio),
'datetime': undefined,
'marginType': undefined,
'side': side,
'hedged': false,
'percentage': this.parseNumber (percentage),
};
}
sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
const query = this.omit (params, this.extractParams (path));
const requestPath = '/' + this.implodeParams (path, params);
let url = requestPath;
let queryString = '';
if (method === 'GET') {
if (Object.keys (query).length) {
queryString = this.urlencodeWithArrayRepeat (query);
url += '?' + queryString;
}
}
if (api === 'private') {
this.checkRequiredCredentials ();
const timestamp = this.milliseconds ();
const xFairdeskRequestExpiry = this.safeInteger (this.options, 'x-fairdesk-request-expiry', 60);
const expiry = this.sum (timestamp, xFairdeskRequestExpiry * 1000);
const expiryString = expiry.toString ();
headers = {
'x-fairdesk-access-key': this.apiKey,
'x-fairdesk-request-expiry': expiryString,
};
let payload = '';
if (method === 'POST' || method === 'PUT' || method === 'DELETE') {
payload = this.json (params);
body = payload;
headers['Content-Type'] = 'application/json';
}
const auth = '/api/v1/private' + requestPath + queryString + expiryString + payload;
headers['x-fairdesk-request-signature'] = this.hmac (this.encode (auth), this.encode (this.secret));
}
url = this.implodeHostname (this.urls['api'][api]) + url;