@proton/ccxt
Version:
A JavaScript / TypeScript / Python / C# / PHP cryptocurrency trading library with support for 130+ exchanges
549 lines (546 loc) • 22.4 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 bitstampRest from '../bitstamp.js';
import { ArgumentsRequired, AuthenticationError } from '../base/errors.js';
import { ArrayCache, ArrayCacheBySymbolById } from '../base/ws/Cache.js';
// ---------------------------------------------------------------------------
export default class bitstamp extends bitstampRest {
describe() {
return this.deepExtend(super.describe(), {
'has': {
'ws': true,
'watchOrderBook': true,
'watchOrders': true,
'watchTrades': true,
'watchOHLCV': false,
'watchTicker': false,
'watchTickers': false,
},
'urls': {
'api': {
'ws': 'wss://ws.bitstamp.net',
},
},
'options': {
'expiresIn': '',
'userId': '',
'wsSessionToken': '',
'watchOrderBook': {
'snapshotDelay': 6,
'maxRetries': 3,
},
'tradesLimit': 1000,
'OHLCVLimit': 1000,
},
'exceptions': {
'exact': {
'4009': AuthenticationError,
},
},
});
}
async watchOrderBook(symbol, limit = undefined, params = {}) {
/**
* @method
* @name bitstamp#watchOrderBook
* @description watches 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 bitstamp api endpoint
* @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols
*/
await this.loadMarkets();
const market = this.market(symbol);
symbol = market['symbol'];
const messageHash = 'orderbook:' + symbol;
const channel = 'diff_order_book_' + market['id'];
const url = this.urls['api']['ws'];
const request = {
'event': 'bts:subscribe',
'data': {
'channel': channel,
},
};
const message = this.extend(request, params);
const orderbook = await this.watch(url, messageHash, message, messageHash);
return orderbook.limit();
}
handleOrderBook(client, message) {
//
// initial snapshot is fetched with ccxt's fetchOrderBook
// the feed does not include a snapshot, just the deltas
//
// {
// data: {
// timestamp: '1583656800',
// microtimestamp: '1583656800237527',
// bids: [
// ["8732.02", "0.00002478", "1207590500704256"],
// ["8729.62", "0.01600000", "1207590502350849"],
// ["8727.22", "0.01800000", "1207590504296448"],
// ],
// asks: [
// ["8735.67", "2.00000000", "1207590693249024"],
// ["8735.67", "0.01700000", "1207590693634048"],
// ["8735.68", "1.53294500", "1207590692048896"],
// ],
// },
// event: 'data',
// channel: 'diff_order_book_btcusd'
// }
//
const channel = this.safeString(message, 'channel');
const parts = channel.split('_');
const marketId = this.safeString(parts, 3);
const symbol = this.safeSymbol(marketId);
const storedOrderBook = this.safeValue(this.orderbooks, symbol);
const nonce = this.safeValue(storedOrderBook, 'nonce');
const delta = this.safeValue(message, 'data');
const deltaNonce = this.safeInteger(delta, 'microtimestamp');
const messageHash = 'orderbook:' + symbol;
if (nonce === undefined) {
const cacheLength = storedOrderBook.cache.length;
// the rest API is very delayed
// usually it takes at least 4-5 deltas to resolve
const snapshotDelay = this.handleOption('watchOrderBook', 'snapshotDelay', 6);
if (cacheLength === snapshotDelay) {
this.spawn(this.loadOrderBook, client, messageHash, symbol);
}
storedOrderBook.cache.push(delta);
return;
}
else if (nonce >= deltaNonce) {
return;
}
this.handleDelta(storedOrderBook, delta);
client.resolve(storedOrderBook, messageHash);
}
handleDelta(orderbook, delta) {
const timestamp = this.safeTimestamp(delta, 'timestamp');
orderbook['timestamp'] = timestamp;
orderbook['datetime'] = this.iso8601(timestamp);
orderbook['nonce'] = this.safeInteger(delta, 'microtimestamp');
const bids = this.safeValue(delta, 'bids', []);
const asks = this.safeValue(delta, 'asks', []);
const storedBids = orderbook['bids'];
const storedAsks = orderbook['asks'];
this.handleBidAsks(storedBids, bids);
this.handleBidAsks(storedAsks, asks);
}
handleBidAsks(bookSide, bidAsks) {
for (let i = 0; i < bidAsks.length; i++) {
const bidAsk = this.parseBidAsk(bidAsks[i]);
bookSide.storeArray(bidAsk);
}
}
getCacheIndex(orderbook, deltas) {
// we will consider it a fail
const firstElement = deltas[0];
const firstElementNonce = this.safeInteger(firstElement, 'microtimestamp');
const nonce = this.safeInteger(orderbook, 'nonce');
if (nonce < firstElementNonce) {
return -1;
}
for (let i = 0; i < deltas.length; i++) {
const delta = deltas[i];
const deltaNonce = this.safeInteger(delta, 'microtimestamp');
if (deltaNonce === nonce) {
return i + 1;
}
}
return deltas.length;
}
async watchTrades(symbol, since = undefined, limit = undefined, params = {}) {
/**
* @method
* @name bitstamp#watchTrades
* @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 bitstamp 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);
symbol = market['symbol'];
const messageHash = 'trades:' + symbol;
const url = this.urls['api']['ws'];
const channel = 'live_trades_' + market['id'];
const request = {
'event': 'bts:subscribe',
'data': {
'channel': channel,
},
};
const message = this.extend(request, params);
const trades = await this.watch(url, messageHash, message, messageHash);
if (this.newUpdates) {
limit = trades.getLimit(symbol, limit);
}
return this.filterBySinceLimit(trades, since, limit, 'timestamp', true);
}
parseWsTrade(trade, market = undefined) {
//
// {
// buy_order_id: 1211625836466176,
// amount_str: '1.08000000',
// timestamp: '1584642064',
// microtimestamp: '1584642064685000',
// id: 108637852,
// amount: 1.08,
// sell_order_id: 1211625840754689,
// price_str: '6294.77',
// type: 1,
// price: 6294.77
// }
//
const microtimestamp = this.safeInteger(trade, 'microtimestamp');
const id = this.safeString(trade, 'id');
const timestamp = this.parseToInt(microtimestamp / 1000);
const price = this.safeString(trade, 'price');
const amount = this.safeString(trade, 'amount');
const symbol = market['symbol'];
const sideRaw = this.safeInteger(trade, 'type');
const side = (sideRaw === 0) ? 'buy' : 'sell';
return this.safeTrade({
'info': trade,
'timestamp': timestamp,
'datetime': this.iso8601(timestamp),
'symbol': symbol,
'id': id,
'order': undefined,
'type': undefined,
'takerOrMaker': undefined,
'side': side,
'price': price,
'amount': amount,
'cost': undefined,
'fee': undefined,
}, market);
}
handleTrade(client, message) {
//
// {
// data: {
// buy_order_id: 1207733769326592,
// amount_str: "0.14406384",
// timestamp: "1583691851",
// microtimestamp: "1583691851934000",
// id: 106833903,
// amount: 0.14406384,
// sell_order_id: 1207733765476352,
// price_str: "8302.92",
// type: 0,
// price: 8302.92
// },
// event: "trade",
// channel: "live_trades_btcusd"
// }
//
// the trade streams push raw trade information in real-time
// each trade has a unique buyer and seller
const channel = this.safeString(message, 'channel');
const parts = channel.split('_');
const marketId = this.safeString(parts, 2);
const market = this.safeMarket(marketId);
const symbol = market['symbol'];
const messageHash = 'trades:' + symbol;
const data = this.safeValue(message, 'data');
const trade = this.parseWsTrade(data, market);
let tradesArray = this.safeValue(this.trades, symbol);
if (tradesArray === undefined) {
const limit = this.safeInteger(this.options, 'tradesLimit', 1000);
tradesArray = new ArrayCache(limit);
this.trades[symbol] = tradesArray;
}
tradesArray.append(trade);
client.resolve(tradesArray, messageHash);
}
async watchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
/**
* @method
* @name bitstamp#watchOrders
* @description watches information on multiple orders made by the user
* @param {string|undefined} 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 bitstamp api endpoint
* @returns {[object]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
if (symbol === undefined) {
throw new ArgumentsRequired(this.id + ' watchOrders requires a symbol argument');
}
await this.loadMarkets();
const market = this.market(symbol);
symbol = market['symbol'];
const channel = 'private-my_orders';
const messageHash = channel + '_' + market['id'];
const subscription = {
'symbol': symbol,
'limit': limit,
'type': channel,
'params': params,
};
const orders = await this.subscribePrivate(subscription, messageHash, params);
if (this.newUpdates) {
limit = orders.getLimit(symbol, limit);
}
return this.filterBySinceLimit(orders, since, limit, 'timestamp', true);
}
handleOrders(client, message) {
//
// {
// "data":{
// "id":"1463471322288128",
// "id_str":"1463471322288128",
// "order_type":1,
// "datetime":"1646127778",
// "microtimestamp":"1646127777950000",
// "amount":0.05,
// "amount_str":"0.05000000",
// "price":1000,
// "price_str":"1000.00"
// },
// "channel":"private-my_orders_ltcusd-4848701",
// }
//
const channel = this.safeString(message, 'channel');
const order = this.safeValue(message, 'data', {});
const limit = this.safeInteger(this.options, 'ordersLimit', 1000);
if (this.orders === undefined) {
this.orders = new ArrayCacheBySymbolById(limit);
}
const stored = this.orders;
const subscription = this.safeValue(client.subscriptions, channel);
const symbol = this.safeString(subscription, 'symbol');
const market = this.market(symbol);
const parsed = this.parseWsOrder(order, market);
stored.append(parsed);
client.resolve(this.orders, channel);
}
parseWsOrder(order, market = undefined) {
//
// {
// "id":"1463471322288128",
// "id_str":"1463471322288128",
// "order_type":1,
// "datetime":"1646127778",
// "microtimestamp":"1646127777950000",
// "amount":0.05,
// "amount_str":"0.05000000",
// "price":1000,
// "price_str":"1000.00"
// }
//
const id = this.safeString(order, 'id_str');
const orderType = this.safeStringLower(order, 'order_type');
const price = this.safeString(order, 'price_str');
const amount = this.safeString(order, 'amount_str');
const side = (orderType === '1') ? 'sell' : 'buy';
const timestamp = this.safeTimestamp(order, 'datetime');
market = this.safeMarket(undefined, market);
const symbol = market['symbol'];
return this.safeOrder({
'info': order,
'symbol': symbol,
'id': id,
'clientOrderId': undefined,
'timestamp': timestamp,
'datetime': this.iso8601(timestamp),
'lastTradeTimestamp': undefined,
'type': undefined,
'timeInForce': undefined,
'postOnly': undefined,
'side': side,
'price': price,
'stopPrice': undefined,
'triggerPrice': undefined,
'amount': amount,
'cost': undefined,
'average': undefined,
'filled': undefined,
'remaining': undefined,
'status': undefined,
'fee': undefined,
'trades': undefined,
}, market);
}
handleOrderBookSubscription(client, message) {
const channel = this.safeString(message, 'channel');
const parts = channel.split('_');
const marketId = this.safeString(parts, 3);
const symbol = this.safeSymbol(marketId);
this.orderbooks[symbol] = this.orderBook();
}
handleSubscriptionStatus(client, message) {
//
// {
// 'event': "bts:subscription_succeeded",
// 'channel': "detail_order_book_btcusd",
// 'data': {},
// }
// {
// event: 'bts:subscription_succeeded',
// channel: 'private-my_orders_ltcusd-4848701',
// data: {}
// }
//
const channel = this.safeString(message, 'channel');
if (channel.indexOf('order_book') > -1) {
this.handleOrderBookSubscription(client, message);
}
}
handleSubject(client, message) {
//
// {
// data: {
// timestamp: '1583656800',
// microtimestamp: '1583656800237527',
// bids: [
// ["8732.02", "0.00002478", "1207590500704256"],
// ["8729.62", "0.01600000", "1207590502350849"],
// ["8727.22", "0.01800000", "1207590504296448"],
// ],
// asks: [
// ["8735.67", "2.00000000", "1207590693249024"],
// ["8735.67", "0.01700000", "1207590693634048"],
// ["8735.68", "1.53294500", "1207590692048896"],
// ],
// },
// event: 'data',
// channel: 'detail_order_book_btcusd'
// }
//
// private order
// {
// "data":{
// "id":"1463471322288128",
// "id_str":"1463471322288128",
// "order_type":1,
// "datetime":"1646127778",
// "microtimestamp":"1646127777950000",
// "amount":0.05,
// "amount_str":"0.05000000",
// "price":1000,
// "price_str":"1000.00"
// },
// "channel":"private-my_orders_ltcusd-4848701",
// }
//
const channel = this.safeString(message, 'channel');
const methods = {
'live_trades': this.handleTrade,
'diff_order_book': this.handleOrderBook,
'private-my_orders': this.handleOrders,
};
const keys = Object.keys(methods);
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
if (channel.indexOf(key) > -1) {
const method = methods[key];
method.call(this, client, message);
}
}
}
handleErrorMessage(client, message) {
// {
// event: 'bts:error',
// channel: '',
// data: { code: 4009, message: 'Connection is unauthorized.' }
// }
const event = this.safeString(message, 'event');
if (event === 'bts:error') {
const feedback = this.id + ' ' + this.json(message);
const data = this.safeValue(message, 'data', {});
const code = this.safeNumber(data, 'code');
this.throwExactlyMatchedException(this.exceptions['exact'], code, feedback);
}
return message;
}
handleMessage(client, message) {
if (!this.handleErrorMessage(client, message)) {
return;
}
//
// {
// 'event': "bts:subscription_succeeded",
// 'channel': "detail_order_book_btcusd",
// 'data': {},
// }
//
// {
// data: {
// timestamp: '1583656800',
// microtimestamp: '1583656800237527',
// bids: [
// ["8732.02", "0.00002478", "1207590500704256"],
// ["8729.62", "0.01600000", "1207590502350849"],
// ["8727.22", "0.01800000", "1207590504296448"],
// ],
// asks: [
// ["8735.67", "2.00000000", "1207590693249024"],
// ["8735.67", "0.01700000", "1207590693634048"],
// ["8735.68", "1.53294500", "1207590692048896"],
// ],
// },
// event: 'data',
// channel: 'detail_order_book_btcusd'
// }
//
// {
// event: 'bts:subscription_succeeded',
// channel: 'private-my_orders_ltcusd-4848701',
// data: {}
// }
//
const event = this.safeString(message, 'event');
if (event === 'bts:subscription_succeeded') {
return this.handleSubscriptionStatus(client, message);
}
else {
return this.handleSubject(client, message);
}
}
async authenticate(params = {}) {
this.checkRequiredCredentials();
const time = this.milliseconds();
const expiresIn = this.safeInteger(this.options, 'expiresIn');
if ((expiresIn === undefined) || (time > expiresIn)) {
const response = await this.privatePostWebsocketsToken(params);
//
// {
// "valid_sec":60,
// "token":"siPaT4m6VGQCdsDCVbLBemiphHQs552e",
// "user_id":4848701
// }
//
const sessionToken = this.safeString(response, 'token');
if (sessionToken !== undefined) {
const userId = this.safeNumber(response, 'user_id');
const validity = this.safeIntegerProduct(response, 'valid_sec', 1000);
this.options['expiresIn'] = this.sum(time, validity);
this.options['userId'] = userId;
this.options['wsSessionToken'] = sessionToken;
return response;
}
}
}
async subscribePrivate(subscription, messageHash, params = {}) {
const url = this.urls['api']['ws'];
await this.authenticate();
messageHash += '-' + this.options['userId'];
const request = {
'event': 'bts:subscribe',
'data': {
'channel': messageHash,
'auth': this.options['wsSessionToken'],
},
};
subscription['messageHash'] = messageHash;
return await this.watch(url, messageHash, this.extend(request, params), messageHash, subscription);
}
}