ccxt
Version:
594 lines (591 loc) • 24.8 kB
JavaScript
// ----------------------------------------------------------------------------
// PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
// https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code
// EDIT THE CORRESPONDENT .ts FILE INSTEAD
// ---------------------------------------------------------------------------
import probitRest from '../probit.js';
import { NotSupported, ExchangeError } from '../base/errors.js';
import { ArrayCache, ArrayCacheBySymbolById } from '../base/ws/Cache.js';
// ---------------------------------------------------------------------------
export default class probit extends probitRest {
describe() {
return this.deepExtend(super.describe(), {
'has': {
'ws': true,
'watchBalance': true,
'watchTicker': true,
'watchTickers': false,
'watchTrades': true,
'watchTradesForSymbols': false,
'watchMyTrades': true,
'watchOrders': true,
'watchOrderBook': true,
'watchOHLCV': false,
},
'urls': {
'api': {
'ws': 'wss://api.probit.com/api/exchange/v1/ws',
},
'test': {
'ws': 'wss://demo-api.probit.com/api/exchange/v1/ws',
},
},
'options': {
'watchOrderBook': {
'filter': 'order_books_l2',
'interval': 100, // or 500
},
},
'streaming': {},
});
}
/**
* @method
* @name probit#watchBalance
* @description watch balance and get the amount of funds available for trading or funds locked in orders
* @see https://docs-en.probit.com/reference/balance-1
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} a [balance structure]{@link https://docs.ccxt.com/#/?id=balance-structure}
*/
async watchBalance(params = {}) {
await this.authenticate(params);
const messageHash = 'balance';
return await this.subscribePrivate(messageHash, 'balance', params);
}
handleBalance(client, message) {
//
// {
// "channel": "balance",
// "reset": false,
// "data": {
// "USDT": {
// "available": "15",
// "total": "15"
// }
// }
// }
//
const messageHash = 'balance';
this.parseWSBalance(message);
client.resolve(this.balance, messageHash);
}
parseWSBalance(message) {
//
// {
// "channel": "balance",
// "reset": false,
// "data": {
// "USDT": {
// "available": "15",
// "total": "15"
// }
// }
// }
//
const reset = this.safeBool(message, 'reset', false);
const data = this.safeValue(message, 'data', {});
const currencyIds = Object.keys(data);
if (reset) {
this.balance = {};
}
for (let i = 0; i < currencyIds.length; i++) {
const currencyId = currencyIds[i];
const entry = data[currencyId];
const code = this.safeCurrencyCode(currencyId);
const account = this.account();
account['free'] = this.safeString(entry, 'available');
account['total'] = this.safeString(entry, 'total');
this.balance[code] = account;
}
this.balance = this.safeBalance(this.balance);
}
/**
* @method
* @name probit#watchTicker
* @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
* @see https://docs-en.probit.com/reference/marketdata
* @param {string} symbol unified symbol of the market to fetch the ticker for
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {int} [params.interval] Unit time to synchronize market information (ms). Available units: 100, 500
* @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure}
*/
async watchTicker(symbol, params = {}) {
const channel = 'ticker';
return await this.subscribePublic('watchTicker', symbol, 'ticker', channel, params);
}
handleTicker(client, message) {
//
// {
// "channel": "marketdata",
// "market_id": "BTC-USDT",
// "status": "ok",
// "lag": 0,
// "ticker": {
// "time": "2022-07-21T14:18:04.000Z",
// "last": "22591.3",
// "low": "22500.1",
// "high": "39790.7",
// "change": "-1224",
// "base_volume": "1002.32005445",
// "quote_volume": "23304489.385351021"
// },
// "reset": true
// }
//
const marketId = this.safeString(message, 'market_id');
const symbol = this.safeSymbol(marketId);
const ticker = this.safeValue(message, 'ticker', {});
const market = this.safeMarket(marketId);
const parsedTicker = this.parseTicker(ticker, market);
const messageHash = 'ticker:' + symbol;
this.tickers[symbol] = parsedTicker;
client.resolve(parsedTicker, messageHash);
}
/**
* @method
* @name probit#watchTrades
* @description get the list of most recent trades for a particular symbol
* @see https://docs-en.probit.com/reference/trade_history
* @param {string} symbol unified symbol of the market to fetch trades for
* @param {int} [since] timestamp in ms of the earliest trade to fetch
* @param {int} [limit] the maximum amount of trades to fetch
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {int} [params.interval] Unit time to synchronize market information (ms). Available units: 100, 500
* @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=public-trades}
*/
async watchTrades(symbol, since = undefined, limit = undefined, params = {}) {
const channel = 'recent_trades';
symbol = this.safeSymbol(symbol);
const trades = await this.subscribePublic('watchTrades', symbol, 'trades', channel, params);
if (this.newUpdates) {
limit = trades.getLimit(symbol, limit);
}
return this.filterBySymbolSinceLimit(trades, symbol, since, limit, true);
}
handleTrades(client, message) {
//
// {
// "channel": "marketdata",
// "market_id": "BTC-USDT",
// "status": "ok",
// "lag": 0,
// "recent_trades": [
// {
// "id": "BTC-USDT:8010233",
// "price": "22701.4",
// "quantity": "0.011011",
// "time": "2022-07-21T13:40:40.983Z",
// "side": "buy",
// "tick_direction": "up"
// }
// ...
// ]
// "reset": true
// }
//
const marketId = this.safeString(message, 'market_id');
const symbol = this.safeSymbol(marketId);
const market = this.safeMarket(marketId);
const trades = this.safeValue(message, 'recent_trades', []);
if (this.safeBool(message, 'reset', false)) {
return; // see comment in handleMessage
}
const messageHash = 'trades:' + symbol;
let stored = this.safeValue(this.trades, symbol);
if (stored === undefined) {
const limit = this.safeInteger(this.options, 'tradesLimit', 1000);
stored = new ArrayCache(limit);
this.trades[symbol] = stored;
}
for (let i = 0; i < trades.length; i++) {
const trade = trades[i];
const parsed = this.parseTrade(trade, market);
stored.append(parsed);
}
this.trades[symbol] = stored;
client.resolve(this.trades[symbol], messageHash);
}
/**
* @method
* @name probit#watchMyTrades
* @description get the list of trades associated with the user
* @see https://docs-en.probit.com/reference/trade_history
* @param {string} symbol unified symbol of the market to fetch trades for
* @param {int} [since] timestamp in ms of the earliest trade to fetch
* @param {int} [limit] the maximum amount of trades to fetch
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=public-trades}
*/
async watchMyTrades(symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
await this.authenticate(params);
let messageHash = 'trades';
if (symbol !== undefined) {
symbol = this.safeSymbol(symbol);
messageHash = messageHash + ':' + symbol;
}
const trades = await this.subscribePrivate(messageHash, 'trade_history', params);
if (this.newUpdates) {
limit = trades.getLimit(symbol, limit);
}
return this.filterBySymbolSinceLimit(trades, symbol, since, limit, true);
}
handleMyTrades(client, message) {
//
// {
// "channel": "trade_history",
// "reset": false,
// "data": [{
// "id": "BTC-USDT:8010722",
// "order_id": "4124999207",
// "side": "buy",
// "fee_amount": "0.0134999868096",
// "fee_currency_id": "USDT",
// "status": "settled",
// "price": "23136.7",
// "quantity": "0.00032416",
// "cost": "7.499992672",
// "time": "2022-07-21T17:09:33.056Z",
// "market_id": "BTC-USDT"
// }]
// }
//
const rawTrades = this.safeValue(message, 'data', []);
const length = rawTrades.length;
if (length === 0) {
return;
}
if (this.safeBool(message, 'reset', false)) {
return; // see comment in handleMessage
}
const messageHash = 'trades';
let stored = this.myTrades;
if (stored === undefined) {
const limit = this.safeInteger(this.options, 'tradesLimit', 1000);
stored = new ArrayCacheBySymbolById(limit);
this.myTrades = stored;
}
const trades = this.parseTrades(rawTrades);
const tradeSymbols = {};
for (let j = 0; j < trades.length; j++) {
const trade = trades[j];
// don't include 'executed' state, because it's just blanket state of the trade, emited before actual trade event
if (this.safeString(trade['info'], 'status') === 'executed') {
continue;
}
tradeSymbols[trade['symbol']] = true;
stored.append(trade);
}
const unique = Object.keys(tradeSymbols);
const uniqueLength = unique.length;
if (uniqueLength === 0) {
return;
}
for (let i = 0; i < unique.length; i++) {
const symbol = unique[i];
const symbolSpecificMessageHash = messageHash + ':' + symbol;
client.resolve(stored, symbolSpecificMessageHash);
}
client.resolve(stored, messageHash);
}
/**
* @method
* @name probit#watchOrders
* @description watches information on an order made by the user
* @see https://docs-en.probit.com/reference/open_order
* @param {string} symbol unified symbol of the market the order was made in
* @param {int} [since] timestamp in ms of the earliest order to watch
* @param {int} [limit] the maximum amount of orders to watch
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @param {string} [params.channel] choose what channel to use. Can open_order or order_history.
* @returns {object} An [order structure]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
async watchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.authenticate(params);
let messageHash = 'orders';
if (symbol !== undefined) {
symbol = this.safeSymbol(symbol);
messageHash = messageHash + ':' + symbol;
}
const orders = await this.subscribePrivate(messageHash, 'open_order', params);
if (this.newUpdates) {
limit = orders.getLimit(symbol, limit);
}
return this.filterBySymbolSinceLimit(orders, symbol, since, limit, true);
}
handleOrders(client, message) {
//
// {
// "channel": "order_history",
// "reset": true,
// "data": [{
// "id": "4124999207",
// "user_id": "633dc56a-621b-4680-8a4e-85a823499b6d",
// "market_id": "BTC-USDT",
// "type": "market",
// "side": "buy",
// "limit_price": "0",
// "time_in_force": "ioc",
// "filled_cost": "7.499992672",
// "filled_quantity": "0.00032416",
// "open_quantity": "0",
// "status": "filled",
// "time": "2022-07-21T17:09:33.056Z",
// "client_order_id": '',
// "cost": "7.5"
// },
// ...
// ]
// }
//
const rawOrders = this.safeValue(message, 'data', []);
const length = rawOrders.length;
if (length === 0) {
return;
}
const messageHash = 'orders';
const reset = this.safeBool(message, 'reset', false);
let stored = this.orders;
if (stored === undefined || reset) {
const limit = this.safeInteger(this.options, 'ordersLimit', 1000);
stored = new ArrayCacheBySymbolById(limit);
this.orders = stored;
}
const orderSymbols = {};
for (let i = 0; i < rawOrders.length; i++) {
const rawOrder = rawOrders[i];
const order = this.parseOrder(rawOrder);
orderSymbols[order['symbol']] = true;
stored.append(order);
}
const unique = Object.keys(orderSymbols);
for (let i = 0; i < unique.length; i++) {
const symbol = unique[i];
const symbolSpecificMessageHash = messageHash + ':' + symbol;
client.resolve(stored, symbolSpecificMessageHash);
}
client.resolve(stored, messageHash);
}
/**
* @method
* @name probit#watchOrderBook
* @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
* @see https://docs-en.probit.com/reference/marketdata
* @param {string} symbol unified symbol of the market to fetch the order book for
* @param {int} [limit] the maximum amount of order book entries to return
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols
*/
async watchOrderBook(symbol, limit = undefined, params = {}) {
let channel = undefined;
[channel, params] = this.handleOptionAndParams(params, 'watchOrderBook', 'filter', 'order_books');
const orderbook = await this.subscribePublic('watchOrderBook', symbol, 'orderbook', channel, params);
return orderbook.limit();
}
async subscribePrivate(messageHash, channel, params) {
const url = this.urls['api']['ws'];
const subscribe = {
'type': 'subscribe',
'channel': channel,
};
const request = this.extend(subscribe, params);
const subscribeHash = messageHash;
return await this.watch(url, messageHash, request, subscribeHash);
}
async subscribePublic(methodName, symbol, dataType, filter, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
symbol = market['symbol'];
const url = this.urls['api']['ws'];
const client = this.client(url);
const subscribeHash = 'marketdata:' + symbol;
const messageHash = dataType + ':' + symbol;
let filters = {};
if (subscribeHash in client.subscriptions) {
// already subscribed
filters = client.subscriptions[subscribeHash];
if (!(filter in filters)) {
// resubscribe
delete client.subscriptions[subscribeHash];
}
}
filters[filter] = true;
const keys = Object.keys(filters);
let interval = undefined;
[interval, params] = this.handleOptionAndParams(params, methodName, 'interval', 100);
let request = {
'type': 'subscribe',
'channel': 'marketdata',
'market_id': market['id'],
'filter': keys,
'interval': interval,
};
request = this.extend(request, params);
return await this.watch(url, messageHash, request, subscribeHash, filters);
}
handleOrderBook(client, message, orderBook) {
//
// {
// "channel": "marketdata",
// "market_id": "BTC-USDT",
// "status": "ok",
// "lag": 0,
// "order_books": [
// { side: "buy", price: '1420.7', quantity: "0.057" },
// ...
// ],
// "reset": true
// }
//
const marketId = this.safeString(message, 'market_id');
const symbol = this.safeSymbol(marketId);
const dataBySide = this.groupBy(orderBook, 'side');
const messageHash = 'orderbook:' + symbol;
// let orderbook = this.safeValue (this.orderbooks, symbol);
if (!(symbol in this.orderbooks)) {
this.orderbooks[symbol] = this.orderBook({});
}
const orderbook = this.orderbooks[symbol];
const reset = this.safeBool(message, 'reset', false);
if (reset) {
const snapshot = this.parseOrderBook(dataBySide, symbol, undefined, 'buy', 'sell', 'price', 'quantity');
orderbook.reset(snapshot);
}
else {
this.handleDelta(orderbook, dataBySide);
}
client.resolve(orderbook, messageHash);
}
handleBidAsks(bookSide, bidAsks) {
for (let i = 0; i < bidAsks.length; i++) {
const bidAsk = bidAsks[i];
const parsed = this.parseBidAsk(bidAsk, 'price', 'quantity');
bookSide.storeArray(parsed);
}
}
handleDelta(orderbook, delta) {
const storedBids = orderbook['bids'];
const storedAsks = orderbook['asks'];
const asks = this.safeValue(delta, 'sell', []);
const bids = this.safeValue(delta, 'buy', []);
this.handleBidAsks(storedBids, bids);
this.handleBidAsks(storedAsks, asks);
}
handleErrorMessage(client, message) {
//
// {
// "errorCode": "INVALID_ARGUMENT",
// "message": '',
// "details": {
// "interval": "invalid"
// }
// }
//
const code = this.safeString(message, 'errorCode');
const errMessage = this.safeString(message, 'message', '');
const details = this.safeValue(message, 'details');
const feedback = this.id + ' ' + code + ' ' + errMessage + ' ' + this.json(details);
if ('exact' in this.exceptions) {
this.throwExactlyMatchedException(this.exceptions['exact'], code, feedback);
}
if ('broad' in this.exceptions) {
this.throwBroadlyMatchedException(this.exceptions['broad'], errMessage, feedback);
}
throw new ExchangeError(feedback);
}
handleAuthenticate(client, message) {
//
// { type: "authorization", result: "ok" }
//
const result = this.safeString(message, 'result');
const future = client.subscriptions['authenticated'];
if (result === 'ok') {
const messageHash = 'authenticated';
client.resolve(message, messageHash);
}
else {
future.reject(message);
delete client.subscriptions['authenticated'];
}
}
handleMarketData(client, message) {
const ticker = this.safeValue(message, 'ticker');
if (ticker !== undefined) {
this.handleTicker(client, message);
}
const trades = this.safeValue(message, 'recent_trades', []);
const tradesLength = trades.length;
if (tradesLength) {
this.handleTrades(client, message);
}
const orderBook = this.safeValueN(message, ['order_books', 'order_books_l1', 'order_books_l2', 'order_books_l3', 'order_books_l4'], []);
const orderBookLength = orderBook.length;
if (orderBookLength) {
this.handleOrderBook(client, message, orderBook);
}
}
handleMessage(client, message) {
//
// {
// "errorCode": "INVALID_ARGUMENT",
// "message": '',
// "details": {
// "interval": "invalid"
// }
// }
//
// Note about 'reset' field
// 'reset': true field - it happens once after initial subscription, which just returns old items by the moment of subscription (like "fetchMyTrades" does)
//
const errorCode = this.safeString(message, 'errorCode');
if (errorCode !== undefined) {
this.handleErrorMessage(client, message);
return;
}
const type = this.safeString(message, 'type');
if (type === 'authorization') {
this.handleAuthenticate(client, message);
return;
}
const handlers = {
'marketdata': this.handleMarketData,
'balance': this.handleBalance,
'trade_history': this.handleMyTrades,
'open_order': this.handleOrders,
'order_history': this.handleOrders,
};
const channel = this.safeString(message, 'channel');
const handler = this.safeValue(handlers, channel);
if (handler !== undefined) {
handler.call(this, client, message);
return;
}
const error = new NotSupported(this.id + ' handleMessage: unknown message: ' + this.json(message));
client.reject(error);
}
async authenticate(params = {}) {
const url = this.urls['api']['ws'];
const client = this.client(url);
const messageHash = 'authenticated';
const expires = this.safeInteger(this.options, 'expires', 0);
let future = this.safeValue(client.subscriptions, messageHash);
if ((future === undefined) || (this.milliseconds() > expires)) {
const response = await this.signIn();
//
// {
// "access_token": "0ttDv/2hTTn3bLi8GP1gKaneiEQ6+0hOBenPrxNQt2s=",
// "token_type": "bearer",
// "expires_in": 900
// }
//
const accessToken = this.safeString(response, 'access_token');
const request = {
'type': 'authorization',
'token': accessToken,
};
future = await this.watch(url, messageHash, this.extend(request, params), messageHash);
client.subscriptions[messageHash] = future;
}
return future;
}
}