consequunturatque
Version:
A JavaScript / Python / PHP cryptocurrency trading library with support for 130+ exchanges
1,153 lines (1,130 loc) • 72.1 kB
JavaScript
'use strict';
const Exchange = require ('./base/Exchange');
const { ExchangeError, ArgumentsRequired, BadRequest, InsufficientFunds, InvalidAddress, BadSymbol, InvalidOrder } = require ('./base/errors');
const Precise = require ('./base/Precise');
module.exports = class xena extends Exchange {
describe () {
return this.deepExtend (super.describe (), {
'id': 'xena',
'name': 'Xena Exchange',
'countries': [ 'VC', 'UK' ],
'rateLimit': 100,
'certified': true,
'has': {
'CORS': false,
'cancelAllOrders': true,
'cancelOrder': true,
'createDepositAddress': true,
'createOrder': true,
'editOrder': true,
'fetchBalance': true,
'fetchClosedOrders': true,
'fetchCurrencies': true,
'fetchDepositAddress': true,
'fetchDeposits': true,
'fetchLedger': true,
'fetchMarkets': true,
'fetchMyTrades': true,
'fetchOHLCV': true,
'fetchOpenOrders': true,
'fetchOrderBook': true,
'fetchTicker': true,
'fetchTickers': true,
'fetchTime': true,
'fetchTrades': true,
'fetchWithdrawals': true,
'withdraw': true,
},
'urls': {
'logo': 'https://user-images.githubusercontent.com/51840849/87489843-bb469280-c64c-11ea-91aa-69c6326506af.jpg',
'test': {
'public': 'https://trading.demo.xena.io/api',
'private': 'https://api.demo.xena.io',
},
'api': {
'public': 'https://trading.xena.exchange/api',
'private': 'https://api.xena.exchange',
},
'www': 'https://xena.exchange',
'doc': 'https://support.xena.exchange/support/solutions/44000808700',
'fees': 'https://trading.xena.exchange/en/platform-specification/fee-schedule',
},
'timeframes': {
'1m': '1m',
'5m': '5m',
'15m': '15m',
'30m': '30m',
'1h': '1h',
'4h': '4h',
'12h': '12h',
'1d': '1d',
'1w': '1w',
},
'api': {
'public': {
'get': [
'common/currencies',
'common/instruments',
'common/features',
'common/commissions',
'common/news',
'market-data/candles/{marketId}/{timeframe}',
'market-data/market-watch',
'market-data/dom/{symbol}',
'market-data/candles/{symbol}/{timeframe}',
'market-data/trades/{symbol}',
'market-data/server-time',
'market-data/v2/candles/{symbol}/{timeframe}',
'market-data/v2/trades/{symbol}',
'market-data/v2/dom/{symbol}/',
'market-data/v2/server-time',
],
},
'private': {
'get': [
'trading/accounts/{accountId}/order',
'trading/accounts/{accountId}/active-orders',
'trading/accounts/{accountId}/last-order-statuses',
'trading/accounts/{accountId}/positions',
'trading/accounts/{accountId}/positions-history',
'trading/accounts/{accountId}/margin-requirements',
'trading/accounts',
'trading/accounts/{accountId}/balance',
'trading/accounts/{accountId}/trade-history',
// 'trading/accounts/{accountId}/trade-history?symbol=BTC/USDT&client_order_id=EMBB8Veke&trade_id=220143254',
'transfers/accounts',
'transfers/accounts/{accountId}',
'transfers/accounts/{accountId}/deposit-address/{currency}',
'transfers/accounts/{accountId}/deposits',
'transfers/accounts/{accountId}/trusted-addresses',
'transfers/accounts/{accountId}/withdrawals',
'transfers/accounts/{accountId}/balance-history',
// 'transfers/accounts/{accountId}/balance-history?currency={currency}&from={time}&to={time}&kind={kind}&kind={kind}',
// 'transfers/accounts/{accountId}/balance-history?page={page}&limit={limit}',
// 'transfers/accounts/{accountId}/balance-history?txid=3e1db982c4eed2d6355e276c5bae01a52a27c9cef61574b0e8c67ee05fc26ccf',
],
'post': [
'trading/order/new',
'trading/order/heartbeat',
'trading/order/cancel',
'trading/order/mass-cancel',
'trading/order/replace',
'trading/position/maintenance',
'transfers/accounts/{accountId}/withdrawals',
'transfers/accounts/{accountId}/deposit-address/{currency}',
],
},
},
'fees': {
'trading': {
'maker': 0.0005,
'taker': 0.001,
'tierBased': true,
'percentage': true,
},
'funding': {
'tierBased': false,
'percentage': false,
'withdraw': {},
'deposit': {},
},
},
'exceptions': {
'exact': {
'Validation failed': BadRequest,
'Unknown derivative symbol': BadSymbol, // {"error":"Unknown derivative symbol"}
'Unknown account': BadRequest, // {"error":"Unknown account"}
'Wrong TransactTime': BadRequest, // {"error":"Wrong TransactTime"}
'ClOrdId is empty': BadRequest, // {"error":"ClOrdId is empty"}
},
'broad': {
'Invalid aggregation ratio or depth': BadRequest,
'address': InvalidAddress,
'Money not enough': InsufficientFunds,
'parse error': BadRequest,
'Not enough': InsufficientFunds, // {"error":"Not enough free margin"}
},
},
'options': {
'defaultType': 'margin', // 'margin',
'accountId': undefined, // '1012838157',
},
});
}
async fetchTime (params = {}) {
const response = await this.publicGetMarketDataV2ServerTime (params);
//
// {
// "msgType":"0",
// "transactTime":1594774454112817637
// }
//
const transactTime = this.safeInteger (response, 'transactTime');
return parseInt (transactTime / 1000000);
}
async fetchMarkets (params = {}) {
const response = await this.publicGetCommonInstruments (params);
//
// [
// {
// "id":"ETHUSD_3M_250920",
// "type":"Margin",
// "marginType":"XenaFuture",
// "symbol":"ETHUSD_3M_250920",
// "baseCurrency":"ETH",
// "quoteCurrency":"USD",
// "settlCurrency":"BTC",
// "tickSize":2,
// "minOrderQuantity":"1",
// "orderQtyStep":"1",
// "limitOrderMaxDistance":"10",
// "priceInputMask":"0000.00",
// "enabled":true,
// "liquidationMaxDistance":"0.01",
// "contractValue":"1",
// "contractCurrency":"BTC",
// "lotSize":"1",
// "tickValue":"0.00000001", // linear contracts only
// "maxOrderQty":"175000",
// "maxPosVolume":"1750000",
// "mark":".ETHUSD_3M_250920",
// "underlying":".ETHUSD_TWAP",
// "openInterest":".ETHUSD_3M_250920_OpenInterest",
// "floatingPL":"BidAsk", // perpetual contracts only
// "addUvmToFreeMargin":"ProfitAndLoss",
// "margin":{
// "netting":"PositionsAndOrders",
// "rates":[
// {"maxVolume":"175000","initialRate":"0.05","maintenanceRate":"0.0125"},
// {"maxVolume":"350000","initialRate":"0.1","maintenanceRate":"0.025"},
// {"maxVolume":"500000","initialRate":"0.2","maintenanceRate":"0.05"},
// {"maxVolume":"750000","initialRate":"0.3","maintenanceRate":"0.075"},
// {"maxVolume":"1050000","initialRate":"0.4","maintenanceRate":"0.1"},
// {"maxVolume":"1400000","initialRate":"0.5","maintenanceRate":"0.125"},
// {"maxVolume":"1750000","initialRate":"1","maintenanceRate":"0.25"}
// ],
// "rateMultipliers":{
// "LimitBuy":"1",
// "LimitSell":"1",
// "Long":"1",
// "MarketBuy":"1",
// "MarketSell":"1",
// "Short":"1",
// "StopBuy":"0",
// "StopSell":"0"
// }
// },
// "clearing":{"enabled":true,"index":".ETHUSD_3M_250920"},
// "premium":{"enabled":true,"index":".XBTUSD_Premium_IR_Corrected"}, // perpetual contracts only
// "riskAdjustment":{"enabled":true,"index":".RiskAdjustment_IR"},
// "expiration":{"enabled":true,"index":".ETHUSD_TWAP"}, // futures only
// "pricePrecision":3,
// "priceRange":{
// "enabled":true,
// "distance":"0.03",
// "movingBoundary":"0",
// "lowIndex":".ETHUSD_3M_250920_LOWRANGE",
// "highIndex":".ETHUSD_3M_250920_HIGHRANGE"
// },
// "priceLimits":{
// "enabled":true,
// "distance":"0.5",
// "movingBoundary":"0",
// "lowIndex":".ETHUSD_3M_250920_LOWLIMIT",
// "highIndex":".ETHUSD_3M_250920_HIGHLIMIT"
// },
// "inverse":true, // inverse contracts only
// "serie":"ETHUSD", // futures only
// "tradingStartDate":"2020-03-27 07:00:00",
// "expiryDate":"2020-09-25 08:00:00" // futures only
// },
// {
// "type":"Index",
// "symbol":".ETHUSD_Premium_IR_Corrected",
// "tickSize":6,
// "enabled":true,
// "basis":365
// },
// ]
//
const result = [];
for (let i = 0; i < response.length; i++) {
const market = response[i];
let type = this.safeStringLower (market, 'type');
const id = this.safeString (market, 'symbol');
const numericId = this.safeString (market, 'id');
const marginType = this.safeString (market, 'marginType');
const baseId = this.safeString (market, 'baseCurrency');
const quoteId = this.safeString (market, 'quoteCurrency');
const base = this.safeCurrencyCode (baseId);
const quote = this.safeCurrencyCode (quoteId);
let symbol = id;
if (type === 'margin') {
if (marginType === 'XenaFuture') {
type = 'future';
} else if (marginType === 'XenaListedPerpetual') {
type = 'swap';
symbol = base + '/' + quote;
}
}
const future = (type === 'future');
const swap = (type === 'swap');
const pricePrecision = this.safeInteger2 (market, 'tickSize', 'pricePrecision');
const precision = {
'price': pricePrecision,
'amount': 0,
};
const maxCost = this.safeNumber (market, 'maxOrderQty');
const minCost = this.safeNumber (market, 'minOrderQuantity');
const limits = {
'amount': {
'min': undefined,
'max': undefined,
},
'price': {
'min': undefined,
'max': undefined,
},
'cost': {
'min': minCost,
'max': maxCost,
},
};
const active = this.safeValue (market, 'enabled', false);
const inverse = this.safeValue (market, 'inverse', false);
result.push ({
'id': id,
'symbol': symbol,
'base': base,
'quote': quote,
'baseId': baseId,
'quoteId': quoteId,
'numericId': numericId,
'active': active,
'type': type,
'spot': false,
'future': future,
'swap': swap,
'inverse': inverse,
'precision': precision,
'limits': limits,
'info': market,
});
}
return result;
}
async fetchCurrencies (params = {}) {
const response = await this.publicGetCommonCurrencies (params);
//
// {
// "BAB": {
// "name":"BAB",
// "title":"Bitcoin ABC",
// "blockchain":{
// "name":"BAB",
// "title":"Bitcoin ABC",
// "deposit":{"confirmations":6},
// "withdraw":{"confirmations":1},
// "addressReuseAllowed":false,
// "view":{
// "uriTemplate":"bitcoinabc:%s?message=Xena Exchange",
// "recommendedFee":"0.00001",
// "transactionUrl":"https://blockchair.com/bitcoin-cash/transaction/${txId}",
// "walletUrl":"https://blockchair.com/bitcoin-cash/address/${walletId}"
// }
// },
// "precision":5,
// "withdraw":{"minAmount":"0.01","commission":"0.001"},
// "view":{
// "color":"#DC7C08",
// "site":"https://www.bitcoinabc.org"
// },
// "enabled":true
// },
// }
const ids = Object.keys (response);
const result = {};
for (let i = 0; i < ids.length; i++) {
const id = ids[i];
const currency = response[id];
const code = this.safeCurrencyCode (id);
const name = this.safeString (currency, 'title');
const precision = this.safeInteger (currency, 'precision');
const enabled = this.safeValue (currency, 'enabled');
const active = (enabled === true);
const withdraw = this.safeValue (currency, 'withdraw', {});
result[code] = {
'id': id,
'code': code,
'info': currency,
'name': name,
'active': active,
'fee': this.safeNumber (withdraw, 'commission'),
'precision': precision,
'limits': {
'amount': {
'min': undefined,
'max': undefined,
},
'withdraw': {
'min': this.safeNumber (withdraw, 'minAmount'),
'max': undefined,
},
},
};
}
return result;
}
parseTicker (ticker, market = undefined) {
//
// fetchTicker, fetchTickers
//
// {
// "symbol":".XBTUSD_3M_250920_MID",
// "firstPx":"9337.49",
// "lastPx":"9355.81",
// "highPx":"9579.42",
// "lowPx":"9157.63",
// "buyVolume":"0",
// "sellVolume":"0",
// "bid":"0",
// "ask":"0"
// }
//
const timestamp = this.milliseconds ();
const marketId = this.safeString (ticker, 'symbol');
const symbol = this.safeSymbol (marketId, market);
const last = this.safeNumber (ticker, 'lastPx');
const open = this.safeNumber (ticker, 'firstPx');
let percentage = undefined;
let change = undefined;
let average = undefined;
if ((last !== undefined) && (open !== undefined)) {
change = last - open;
average = this.sum (last, open) / 2;
if (open > 0) {
percentage = change / open * 100;
}
}
const buyVolume = this.safeNumber (ticker, 'buyVolume');
const sellVolume = this.safeNumber (ticker, 'sellVolume');
const baseVolume = this.sum (buyVolume, sellVolume);
return {
'symbol': symbol,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'high': this.safeNumber (ticker, 'highPx'),
'low': this.safeNumber (ticker, 'lowPx'),
'bid': this.safeNumber (ticker, 'bid'),
'bidVolume': undefined,
'ask': this.safeNumber (ticker, 'ask'),
'askVolume': undefined,
'vwap': undefined,
'open': open,
'close': last,
'last': last,
'previousClose': undefined,
'change': change,
'percentage': percentage,
'average': average,
'baseVolume': baseVolume,
'quoteVolume': undefined,
'info': ticker,
};
}
async fetchTicker (symbol, params = {}) {
await this.loadMarkets ();
const tickers = await this.fetchTickers (undefined, params);
if (symbol in tickers) {
return tickers[symbol];
}
throw new BadSymbol (this.id + ' fetchTicker could not find a ticker with symbol ' + symbol);
}
async fetchTickers (symbols = undefined, params = {}) {
await this.loadMarkets ();
const tickers = await this.publicGetMarketDataMarketWatch (params);
//
// [
// {
// "symbol":".XBTUSD_3M_250920_MID",
// "firstPx":"9337.49",
// "lastPx":"9355.81",
// "highPx":"9579.42",
// "lowPx":"9157.63",
// "buyVolume":"0",
// "sellVolume":"0",
// "bid":"0",
// "ask":"0"
// }
// ]
//
const result = {};
for (let i = 0; i < tickers.length; i++) {
const ticker = this.parseTicker (tickers[i]);
const symbol = ticker['symbol'];
result[symbol] = ticker;
}
return this.filterByArray (result, 'symbol', symbols);
}
async fetchOrderBook (symbol, limit = undefined, params = {}) {
await this.loadMarkets ();
const request = {
'symbol': this.marketId (symbol),
};
if (limit !== undefined) {
request['depth'] = limit;
}
const response = await this.publicGetMarketDataV2DomSymbol (this.extend (request, params));
//
// {
// "msgType":"W",
// "mdStreamId":"DOM:XBTUSD:aggregated",
// "lastUpdateTime":1594772683037691997,
// "mdBookType":"2",
// "symbol":"XBTUSD",
// "lowRangePx":"9132.24",
// "highRangePx":"9410.36",
// "lowLimitPx":"9132.24",
// "highLimitPx":"9410.36",
// "clearingPx":"9253.4",
// "bestBid":"9269.8",
// "bestAsk":"9275.9",
// "mdEntry":[
// {"mdEntryType":"1","mdEntryPx":"9275.9","mdEntrySize":"3000","numberOfOrders":1},
// {"mdEntryType":"1","mdEntryPx":"9277.7","mdEntrySize":"50000","numberOfOrders":1},
// {"mdEntryType":"1","mdEntryPx":"9277.8","mdEntrySize":"2000","numberOfOrders":1},
// {"mdEntryType":"0","mdEntryPx":"9269.8","mdEntrySize":"2000","numberOfOrders":1},
// {"mdEntryType":"0","mdEntryPx":"9267.9","mdEntrySize":"3000","numberOfOrders":1},
// {"mdEntryType":"0","mdEntryPx":"9267.8","mdEntrySize":"50000","numberOfOrders":1},
// ]
// }
//
const mdEntry = this.safeValue (response, 'mdEntry', []);
const mdEntriesByType = this.groupBy (mdEntry, 'mdEntryType');
const lastUpdateTime = this.safeInteger (response, 'lastUpdateTime');
const timestamp = parseInt (lastUpdateTime / 1000000);
return this.parseOrderBook (mdEntriesByType, symbol, timestamp, '0', '1', 'mdEntryPx', 'mdEntrySize');
}
async fetchAccounts (params = {}) {
const response = await this.privateGetTradingAccounts (params);
//
// {
// "accounts": [
// { "id":8273231, "kind": "Spot" },
// { "id":10012833469, "kind": "Margin", "currency": "BTC" }
// ]
// }
//
const accounts = this.safeValue (response, 'accounts');
const result = [];
for (let i = 0; i < accounts.length; i++) {
const account = accounts[i];
const accountId = this.safeString (account, 'id');
const currencyId = this.safeString (account, 'currency');
const code = this.safeCurrencyCode (currencyId);
const type = this.safeStringLower (account, 'kind');
result.push ({
'id': accountId,
'type': type,
'currency': code,
'info': account,
});
}
return result;
}
async findAccountByType (type) {
await this.loadMarkets ();
await this.loadAccounts ();
const accountsByType = this.groupBy (this.accounts, 'type');
const accounts = this.safeValue (accountsByType, type);
if (accounts === undefined) {
throw new ExchangeError (this.id + " findAccountByType() could not find an accountId with type '" + type + "', specify the 'accountId' parameter instead"); // eslint-disable-line quotes
}
const numAccounts = accounts.length;
if (numAccounts > 1) {
throw new ExchangeError (this.id + " findAccountByType() found more than one accountId with type '" + type + "', specify the 'accountId' parameter instead"); // eslint-disable-line quotes
}
return accounts[0];
}
async getAccountId (params) {
await this.loadMarkets ();
await this.loadAccounts ();
const defaultAccountId = this.safeString (this.options, 'accountId');
const accountId = this.safeString (params, 'accountId', defaultAccountId);
if (accountId !== undefined) {
return accountId;
}
const defaultType = this.safeString (this.options, 'defaultType', 'margin');
const type = this.safeString (params, 'type', defaultType);
params = this.omit (params, 'type');
if (type === undefined) {
throw new ArgumentsRequired (this.id + " requires an 'accountId' parameter or a 'type' parameter ('spot' or 'margin')");
}
const account = await this.findAccountByType (type);
return account['id'];
}
async fetchBalance (params = {}) {
await this.loadMarkets ();
await this.loadAccounts ();
const accountId = await this.getAccountId (params);
const request = {
'accountId': accountId,
};
const response = await this.privateGetTradingAccountsAccountIdBalance (this.extend (request, params));
//
// {
// "msgType":"XAR",
// "balances":[
// {
// "currency":"BTC",
// "lastUpdateTime":1619384111905916598,
// "available":"0.00549964",
// "onHold":"0",
// "settled":"0.00549964",
// "equity":"0.00549964"
// }
// ]
// }
//
const result = { 'info': response };
let timestamp = undefined;
const balances = this.safeValue (response, 'balances', []);
for (let i = 0; i < balances.length; i++) {
const balance = balances[i];
const lastUpdateTime = this.safeString (balance, 'lastUpdateTime');
const lastUpdated = lastUpdateTime.slice (0, 13);
const currentTimestamp = parseInt (lastUpdated);
timestamp = (timestamp === undefined) ? currentTimestamp : Math.max (timestamp, currentTimestamp);
const currencyId = this.safeString (balance, 'currency');
const code = this.safeCurrencyCode (currencyId);
const account = this.account ();
account['free'] = this.safeString (balance, 'available');
account['used'] = this.safeString (balance, 'onHold');
result[code] = account;
}
result['timestamp'] = timestamp;
result['datetime'] = this.iso8601 (timestamp);
return this.parseBalance (result, false);
}
parseTrade (trade, market = undefined) {
//
// {
// "mdUpdateAction":"0",
// "mdEntryType":"2",
// "mdEntryPx":"9225.16",
// "mdEntrySize":"10000",
// "transactTime":1594728504524977655,
// "tradeId":"6ac51bb7-7505-4f35-85ef-61eb738cb4d9",
// "aggressorSide":"1"
// }
//
// fetchMyTrades
//
// {
// "msgType":"8",
// "account":1012838158,
// "clOrdId":"xXWKLQVl3",
// "orderId":"89eee8bd-98ae-4d06-97dc-ee2d12997fe7",
// "symbol":"ETHUSD",
// "transactTime":1595143349089739000,
// "execId":"c4bd0ee2330930924e0f6fdde4630e56751692a4",
// "tradeId":"30a394b2-6d53-4bc4-b276-d8e19f470ba1",
// "side":"2",
// "lastQty":"1",
// "lastPx":"234.58",
// "avgPx":"234.58",
// "calculatedCcyLastQty":"0",
// "netMoney":"0",
// "lastLiquidityInd":"2",
// "commission":"0.00000011",
// "commRate":"0.00045",
// "commCurrency":"BTC",
// "positionId":132162662,
// "positionEffect":"C"
// }
//
const id = this.safeString (trade, 'tradeId');
let timestamp = this.safeInteger (trade, 'transactTime');
if (timestamp !== undefined) {
timestamp = parseInt (timestamp / 1000000);
}
let side = this.safeStringLower2 (trade, 'side', 'aggressorSide');
if (side === '1') {
side = 'buy';
} else if (side === '2') {
side = 'sell';
}
const orderId = this.safeString (trade, 'orderId');
const marketId = this.safeString (trade, 'symbol');
const symbol = this.safeSymbol (marketId, market);
const priceString = this.safeString2 (trade, 'lastPx', 'mdEntryPx');
const amountString = this.safeString2 (trade, 'lastQty', 'mdEntrySize');
const price = this.parseNumber (priceString);
const amount = this.parseNumber (amountString);
const cost = this.parseNumber (Precise.stringMul (priceString, amountString));
let fee = undefined;
const feeCost = this.safeNumber (trade, 'commission');
if (feeCost !== undefined) {
const feeCurrencyId = this.safeString (trade, 'commCurrency');
const feeCurrencyCode = this.safeCurrencyCode (feeCurrencyId);
const feeRate = this.safeNumber (trade, 'commRate');
fee = {
'cost': feeCost,
'rate': feeRate,
'currency': feeCurrencyCode,
};
}
return {
'id': id,
'info': trade,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'symbol': symbol,
'type': undefined,
'order': orderId,
'side': side,
'takerOrMaker': undefined,
'price': price,
'amount': amount,
'cost': cost,
'fee': fee,
};
}
async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
await this.loadAccounts ();
const accountId = await this.getAccountId (params);
const request = {
'accountId': accountId,
// 'page': 1,
// 'limit': integer,
// 'from': time,
// 'to': time,
// 'symbol': currency['id'],
// 'trade_id': id,
// 'client_order_id': id,
};
let market = undefined;
if (symbol !== undefined) {
market = this.market (symbol);
request['symbol'] = market['id'];
}
if (since !== undefined) {
request['from'] = since * 1000000;
}
if (limit !== undefined) {
request['limit'] = limit;
}
const response = await this.privateGetTradingAccountsAccountIdTradeHistory (this.extend (request, params));
//
// [
// {
// "msgType":"8",
// "account":1012838158,
// "clOrdId":"xXWKLQVl3",
// "orderId":"89eee8bd-98ae-4d06-97dc-ee2d12997fe7",
// "symbol":"ETHUSD",
// "transactTime":1595143349089739000,
// "execId":"c4bd0ee2330930924e0f6fdde4630e56751692a4",
// "tradeId":"30a394b2-6d53-4bc4-b276-d8e19f470ba1",
// "side":"2",
// "lastQty":"1",
// "lastPx":"234.58",
// "avgPx":"234.58",
// "calculatedCcyLastQty":"0",
// "netMoney":"0",
// "lastLiquidityInd":"2",
// "commission":"0.00000011",
// "commRate":"0.00045",
// "commCurrency":"BTC",
// "positionId":132162662,
// "positionEffect":"C"
// },
// {
// "msgType":"8",
// "account":1012838158,
// "clOrdId":"3ce8c305-9936-4e97-9206-71ae3ff40305",
// "orderId":"a93c686d-990e-44d9-9cbe-61107744b990",
// "symbol":"ETHUSD",
// "transactTime":1595143315369226000,
// "execId":"1c745881722ad966a4ce71600cd058d59da0d1c3",
// "tradeId":"77f75bd8-27c4-4b1a-a5e8-0d59239ce216",
// "side":"1",
// "lastQty":"1",
// "lastPx":"234.72",
// "avgPx":"234.72",
// "calculatedCcyLastQty":"0",
// "netMoney":"0",
// "lastLiquidityInd":"2",
// "commission":"0.00000011",
// "commRate":"0.00045",
// "commCurrency":"BTC",
// "positionId":132162662,
// "positionEffect":"O"
// }
// ]
//
return this.parseTrades (response, market, since, limit);
}
parseOHLCV (ohlcv, market = undefined) {
//
// {
// "transactTime":1594784700000000000,
// "firstPx":"9246.3",
// "lastPx":"9232.8",
// "highPx":"9246.3",
// "lowPx":"9232.8",
// "buyVolume":"0",
// "sellVolume":"0"
// }
//
const transactTime = this.safeInteger (ohlcv, 'transactTime');
const timestamp = parseInt (transactTime / 1000000);
const buyVolume = this.safeNumber (ohlcv, 'buyVolume');
const sellVolume = this.safeNumber (ohlcv, 'sellVolume');
const volume = this.sum (buyVolume, sellVolume);
return [
timestamp,
this.safeNumber (ohlcv, 'firstPx'),
this.safeNumber (ohlcv, 'highPx'),
this.safeNumber (ohlcv, 'lowPx'),
this.safeNumber (ohlcv, 'lastPx'),
volume,
];
}
async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
const request = {
'symbol': market['id'],
'timeframe': this.timeframes[timeframe],
};
const durationInSeconds = this.parseTimeframe (timeframe);
const duration = durationInSeconds * 1000;
if (since !== undefined) {
request['from'] = since * 1000000;
if (limit !== undefined) {
request['to'] = this.sum (since, limit * duration) * 1000000;
}
} else {
const now = this.milliseconds ();
// max limit is 1000
if (limit !== undefined) {
request['from'] = (now - limit * duration) * 1000000;
}
}
const response = await this.publicGetMarketDataV2CandlesSymbolTimeframe (this.extend (request, params));
//
// {
// "mdEntry":[
// {"transactTime":1594784700000000000,"firstPx":"9246.3","lastPx":"9232.8","highPx":"9246.3","lowPx":"9232.8","buyVolume":"0","sellVolume":"0"},
// {"transactTime":1594785600000000000,"firstPx":"9231.8","lastPx":"9227.3","highPx":"9232.8","lowPx":"9227.3","buyVolume":"0","sellVolume":"0"},
// {"transactTime":1594786500000000000,"firstPx":"9226.3","lastPx":"9230.3","highPx":"9230.3","lowPx":"9220.6","buyVolume":"0","sellVolume":"0"}
// ]
// }
//
const mdEntry = this.safeValue (response, 'mdEntry', []);
return this.parseOHLCVs (mdEntry, market, timeframe, since, limit);
}
async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets ();
const market = this.market (symbol);
const request = {
'symbol': market['id'],
// 'from': this.iso8601 (since),
// 'to': this.iso8601 (this.milliseconds ()),
// 'page': 1,
// 'limit': limit,
};
if (since !== undefined) {
request['from'] = this.iso8601 (since);
}
if (limit !== undefined) {
request['limit'] = limit;
}
const response = await this.publicGetMarketDataV2TradesSymbol (this.extend (request, params));
//
// {
// "msgType":"W",
// "lastUpdateTime":1594737830902223803,
// "symbol":"XBTUSD",
// "mdEntry":[
// {
// "mdUpdateAction":"0",
// "mdEntryType":"2",
// "mdEntryPx":"9225.16",
// "mdEntrySize":"10000",
// "transactTime":1594728504524977655,
// "tradeId":"6ac51bb7-7505-4f35-85ef-61eb738cb4d9",
// "aggressorSide":"1"
// },
// ]
// }
//
const mdEntry = this.safeValue (response, 'mdEntry', []);
return this.parseTrades (mdEntry, market, since, limit);
}
parseOrderStatus (status) {
const statuses = {
'A': 'open', // PendingNew
'0': 'open', // New
'1': 'open', // PartiallyFilled
'2': 'closed', // Filled
'6': 'canceled', // PendingCancel
'4': 'canceled', // Cancelled
'E': 'open', // PendingReplace
'8': 'rejected', // Rejected
};
return this.safeString (statuses, status, status);
}
parseOrder (order, market = undefined) {
//
// createOrder
//
// {
// "msgType":"8",
// "account":1012838720,
// "clOrdId":"XAq0pRQ1g",
// "orderId":"64d7a06a-27e5-422e-99d9-3cadc04f5a35",
// "symbol":"XBTUSD",
// "ordType":"2",
// "price":"9000",
// "transactTime":1593778763271127920,
// "execId":"ff5fb8153652f0516bf07b6979255bed053c84b9",
// "execType":"I",
// "ordStatus":"0",
// "side":"1",
// "orderQty":"1",
// "leavesQty":"1",
// "cumQty":"0",
// "positionEffect":"O",
// "marginAmt":"0.00000556",
// "marginAmtType":"11"
// }
//
const id = this.safeString (order, 'orderId');
const clientOrderId = this.safeString (order, 'clOrdId');
const transactTime = this.safeInteger (order, 'transactTime');
const timestamp = parseInt (transactTime / 1000000);
const status = this.parseOrderStatus (this.safeString (order, 'ordStatus'));
const marketId = this.safeString (order, 'symbol');
const symbol = this.safeSymbol (marketId, market);
const price = this.safeNumber (order, 'price');
const amount = this.safeNumber (order, 'orderQty');
const filled = this.safeNumber (order, 'cumQty');
const remaining = this.safeNumber (order, 'leavesQty');
let side = this.safeStringLower (order, 'side');
if (side === '1') {
side = 'buy';
} else if (side === '1') {
side = 'sell';
}
let type = this.safeStringLower (order, 'ordType');
if (type === '1') {
type = 'market';
} else if (type === '2') {
type = 'limit';
} else if (type === '3') {
type = 'stop';
} else if (type === '4') {
type = 'stop-limit';
}
return this.safeOrder ({
'id': id,
'clientOrderId': clientOrderId,
'info': order,
'timestamp': timestamp,
'datetime': this.iso8601 (timestamp),
'lastTradeTimestamp': undefined,
'symbol': symbol,
'type': type,
'timeInForce': undefined,
'postOnly': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'amount': amount,
'cost': undefined,
'average': undefined,
'filled': filled,
'remaining': remaining,
'status': status,
'fee': undefined,
'trades': undefined,
});
}
async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
await this.loadMarkets ();
await this.loadAccounts ();
const accountId = await this.getAccountId (params);
const orderTypes = {
'market': '1',
'limit': '2',
'stop': '3',
'stop-limit': '4',
};
const orderType = this.safeString (orderTypes, type);
if (orderType === undefined) {
throw new InvalidOrder (this.id + ' createOrder does not support order type ' + type + ', supported order types are market, limit, stop, stop-limit');
}
const orderSides = {
'buy': '1',
'sell': '2',
};
const orderSide = this.safeString (orderSides, side);
if (orderSide === undefined) {
throw new InvalidOrder (this.id + ' createOrder does not support order side ' + side + ', supported order sides are buy, sell');
}
const market = this.market (symbol);
const request = {
'account': parseInt (accountId),
'symbol': market['id'],
'ordType': orderType,
'side': orderSide,
'orderQty': this.amountToPrecision (symbol, amount),
'transactTime': this.milliseconds () * 1000000,
// 'clOrdId': this.uuid (), // required
// 'price': this.priceToPrecision (symbol, price), // required for limit and stop-limit orders
// 'stopPx': this.priceToPrecision (symbol, stopPx), // required for stop and stop-limit orders
// 'timeInForce': '1', // default '1' = GoodTillCancelled, '3' = ImmediateOrCancel, '4' = FillOrKill
// 'execInst': '0',
// '0' = StayOnOfferSide, maker only, reject instead of aggressive execution
// '9' = PegToOfferSide, maker only, best available level instead of aggressive execution
// 'o' = CancelOnConnectionLoss
// 'positionID': 1013838923, // required when positionEffect == 'C' with hedged accounting
// 'positionEffect': 'O', // 'C' = Close, 'O' = Open, send C along with the positionID if the order must close a position with hedged accounting mode
// 'text': 'comment', // optional
// 'grpID': 'group-identifier', // group identifier for cancel on disconnect orders
};
if ((type === 'limit') || (type === 'stop-limit')) {
if (price === undefined) {
throw new InvalidOrder (this.id + ' createOrder() requires a price argument for order type ' + type);
}
request['price'] = this.priceToPrecision (symbol, price);
}
if ((type === 'stop') || (type === 'stop-limit')) {
const stopPx = this.safeNumber (params, 'stopPx');
if (stopPx === undefined) {
throw new InvalidOrder (this.id + ' createOrder() requires a stopPx param for order type ' + type);
}
request['stopPx'] = this.priceToPrecision (symbol, stopPx);
params = this.omit (params, 'stopPx');
}
const clientOrderId = this.safeString2 (params, 'clientOrderId', 'clOrdId', this.uuid ());
if (clientOrderId !== undefined) {
request['clOrdId'] = clientOrderId;
params = this.omit (params, [ 'clientOrderId', 'clOrdId' ]);
}
const response = await this.privatePostTradingOrderNew (this.extend (request, params));
//
// {
// "msgType":"8",
// "account":1012838720,
// "clOrdId":"XAq0pRQ1g",
// "orderId":"64d7a06a-27e5-422e-99d9-3cadc04f5a35",
// "symbol":"XBTUSD",
// "ordType":"2",
// "price":"9000",
// "transactTime":1593778763271127920,
// "execId":"ff5fb8153652f0516bf07b6979255bed053c84b9",
// "execType":"I",
// "ordStatus":"0",
// "side":"1",
// "orderQty":"1",
// "leavesQty":"1",
// "cumQty":"0",
// "positionEffect":"O",
// "marginAmt":"0.00000556",
// "marginAmtType":"11"
// }
//
return this.parseOrder (response, market);
}
async editOrder (id, symbol, type, side, amount = undefined, price = undefined, params = {}) {
if (symbol === undefined) {
throw new ArgumentsRequired (this.id + ' cancelOrder() requires a symbol argument');
}
await this.loadMarkets ();
await this.loadAccounts ();
const accountId = await this.getAccountId (params);
const market = this.market (symbol);
const request = {
'account': parseInt (accountId),
'clOrdId': this.uuid (),
'symbol': market['id'],
'transactTime': this.milliseconds () * 1000000,
// 'origClOrdId': this.uuid (), // one of orderId or origClOrdId is required
// 'orderId': id,
// 'side': '1', // 1 = buy, 2 = sell
// 'execInst': '0',
// '0' = StayOnOfferSide, maker only, reject instead of aggressive execution
// '9' = PegToOfferSide, maker only, best available level instead of aggressive execution
// 'o' = CancelOnConnectionLoss
// 'orderQty': 38 M decimal
// 'price': this.priceToPrecision (symbol, price), // required for limit and stop-limit orders
// 'stopPx': this.priceToPrecision (symbol, stopPx), // required for stop and stop-limit orders
// 'capPrice': this.priceToPrecision (symbol, capPrice), // the price beyond which the order will not move for trailing stop and attempt-zero-loss
// 'pegPriceType': '8', // '8' = TrailingStopPeg, identifies a trailing stop or an attempt-zero-loss order
// 'pegOffsetType': '2', // '2' = BasisPoints, the unit of the distance to the stop price for a trailing stop or an attempt-zero-loss order
// 'pegOffsetValue': 123, // distance to the trailing stop or attempt-zero-loss
};
const clientOrderId = this.safeString2 (params, 'clientOrderId', 'origClOrdId');
if (clientOrderId !== undefined) {
request['origClOrdId'] = clientOrderId;
params = this.omit (params, [ 'clientOrderId', 'origClOrdId' ]);
} else {
request['orderId'] = id;
}
if (amount !== undefined) {
request['orderQty'] = this.amountToPrecision (symbol, amount);
}
if (price !== undefined) {
request['price'] = this.priceToPrecision (symbol, price);
}
const stopPx = this.safeNumber (params, 'stopPx');
if (stopPx !== undefined) {
request['stopPx'] = this.priceToPrecision (symbol, stopPx);
params = this.omit (params, 'stopPx');
}
const capPrice = this.safeNumber (params, 'capPrice');
if (capPrice !== undefined) {
request['capPrice'] = this.priceToPrecision (symbol, capPrice);
params = this.omit (params, 'capPrice');
}
const response = await this.privatePostTradingOrderReplace (this.extend (request, params));
return this.parseOrder (response, market);
}
async cancelOrder (id, symbol = undefined, params = {}) {
if (symbol === undefined) {
throw new ArgumentsRequired (this.id + ' cancelOrder() requires a symbol argument');
}
await this.loadMarkets ();
await this.loadAccounts ();
const accountId = await this.getAccountId (params);
const clientOrderId = this.safeString2 (params, 'clientOrderId', 'origClOrdId');
params = this.omit (params, [ 'clientOrderId', 'origClOrdId' ]);
const market = this.market (symbol);
const request = {
'account': parseInt (accountId),
'symbol': market['id'],
'clOrdId': this.uuid (),
'transactTime': this.milliseconds () * 1000000,
};
if (clientOrderId !== undefined) {
request['origClOrdId'] = clientOrderId;
} else {
request['orderId'] = id;
}
const response = await this.privatePostTradingOrderCancel (this.extend (request, params));
//
// {
// "msgType":"8",
// "account":1012838158,
// "clOrdId":"0fa3fb55-9dc0-4cfc-a1db-6aa8b7dd2d98",
// "origClOrdId":"3b2878bb-24d8-4922-9d2a-5b8009416677",
// "orderId":"665b418e-9d09-4461-b733-d317f6bff43f",
// "symbol":"ETHUSD",
// "ordType":"2",
// "price":"640",
// "transactTime":1595060080941618739,
// "execId":"c541c0ca4