ccxt
Version:
778 lines (775 loc) • 31.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 blockchaincomRest from '../blockchaincom.js';
import { NotSupported, AuthenticationError, ExchangeError } from '../base/errors.js';
import { ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp } from '../base/ws/Cache.js';
// ---------------------------------------------------------------------------
export default class blockchaincom extends blockchaincomRest {
describe() {
return this.deepExtend(super.describe(), {
'has': {
'ws': true,
'watchBalance': true,
'watchTicker': true,
'watchTickers': false,
'watchTrades': true,
'watchTradesForSymbols': false,
'watchMyTrades': false,
'watchOrders': true,
'watchOrderBook': true,
'watchOHLCV': true,
},
'urls': {
'api': {
'ws': 'wss://ws.blockchain.info/mercury-gateway/v1/ws',
},
},
'options': {
'ws': {
'options': {
'headers': {
'Origin': 'https://exchange.blockchain.com',
},
},
'noOriginHeader': false,
},
},
'streaming': {},
'exceptions': {},
'timeframes': {
'1m': '60',
'5m': '300',
'15m': '900',
'1h': '3600',
'6h': '21600',
'1d': '86400',
},
});
}
/**
* @method
* @name blockchaincom#watchBalance
* @description watch balance and get the amount of funds available for trading or funds locked in orders
* @see https://exchange.blockchain.com/api/#balances
* @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';
const url = this.urls['api']['ws'];
const subscribe = {
'action': 'subscribe',
'channel': 'balances',
};
const request = this.deepExtend(subscribe, params);
return await this.watch(url, messageHash, request, messageHash, request);
}
handleBalance(client, message) {
//
// subscribed
// {
// "seqnum": 1,
// "event": "subscribed",
// "channel": "balances",
// "local_currency": "USD",
// "batching": false
// }
// snapshot
// {
// "seqnum": 2,
// "event": "snapshot",
// "channel": "balances",
// "balances": [
// {
// "currency": "BTC",
// "balance": 0.00366963,
// "available": 0.00266963,
// "balance_local": 38.746779155,
// "available_local": 28.188009155,
// "rate": 10558.77
// },
// ...
// ],
// "total_available_local": 65.477864168,
// "total_balance_local": 87.696634168
// }
//
const event = this.safeString(message, 'event');
if (event === 'subscribed') {
return;
}
const result = { 'info': message };
const balances = this.safeValue(message, 'balances', []);
for (let i = 0; i < balances.length; i++) {
const entry = balances[i];
const currencyId = this.safeString(entry, 'currency');
const code = this.safeCurrencyCode(currencyId);
const account = this.account();
account['free'] = this.safeString(entry, 'available');
account['total'] = this.safeString(entry, 'balance');
result[code] = account;
}
const messageHash = 'balance';
this.balance = this.safeBalance(result);
client.resolve(this.balance, messageHash);
}
/**
* @method
* @name blockchaincom#watchOHLCV
* @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market.
* @see https://exchange.blockchain.com/api/#prices
* @param {string} symbol unified symbol of the market to fetch OHLCV data for
* @param {string} timeframe the length of time each candle represents. Allows '1m', '5m', '15m', '1h', '6h' '1d'. Can only watch one timeframe per symbol.
* @param {int} [since] timestamp in ms of the earliest candle to fetch
* @param {int} [limit] the maximum amount of candles to fetch
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {int[][]} A list of candles ordered as timestamp, open, high, low, close, volume
*/
async watchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
symbol = market['symbol'];
const interval = this.safeString(this.timeframes, timeframe, timeframe);
const messageHash = 'ohlcv:' + symbol;
let request = {
'action': 'subscribe',
'channel': 'prices',
'symbol': market['id'],
'granularity': this.parseNumber(interval),
};
request = this.deepExtend(request, params);
const url = this.urls['api']['ws'];
const ohlcv = await this.watch(url, messageHash, request, messageHash, request);
if (this.newUpdates) {
limit = ohlcv.getLimit(symbol, limit);
}
return this.filterBySinceLimit(ohlcv, since, limit, 0, true);
}
handleOHLCV(client, message) {
//
// subscribed
// {
// "seqnum": 0,
// "event": "subscribed",
// "channel": "prices",
// "symbol": "BTC-USDT",
// "granularity": 60
// }
//
// updated
// {
// "seqnum": 1,
// "event": "updated",
// "channel": "prices",
// "symbol": "BTC-USD",
// "price": [ 1660085580000, 23185.215, 23185.935, 23164.79, 23169.97, 0 ]
// }
//
const event = this.safeString(message, 'event');
if (event === 'rejected') {
const jsonMessage = this.json(message);
throw new ExchangeError(this.id + ' ' + jsonMessage);
}
else if (event === 'updated') {
const marketId = this.safeString(message, 'symbol');
const symbol = this.safeSymbol(marketId, undefined, '-');
const messageHash = 'ohlcv:' + symbol;
const request = this.safeValue(client.subscriptions, messageHash);
const timeframeId = this.safeNumber(request, 'granularity');
const timeframe = this.findTimeframe(timeframeId);
const ohlcv = this.safeValue(message, 'price', []);
this.ohlcvs[symbol] = this.safeValue(this.ohlcvs, symbol, {});
let stored = this.safeValue(this.ohlcvs[symbol], timeframe);
if (stored === undefined) {
const limit = this.safeInteger(this.options, 'OHLCVLimit', 1000);
stored = new ArrayCacheByTimestamp(limit);
this.ohlcvs[symbol][timeframe] = stored;
}
stored.append(ohlcv);
client.resolve(stored, messageHash);
}
else if (event !== 'subscribed') {
throw new NotSupported(this.id + ' ' + this.json(message));
}
}
/**
* @method
* @name blockchaincom#watchTicker
* @description watches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
* @see https://exchange.blockchain.com/api/#ticker
* @param {string} symbol unified symbol of the market to fetch the ticker for
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure}
*/
async watchTicker(symbol, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
symbol = market['symbol'];
const url = this.urls['api']['ws'];
const messageHash = 'ticker:' + symbol;
let request = {
'action': 'subscribe',
'channel': 'ticker',
'symbol': market['id'],
};
request = this.deepExtend(request, params);
return await this.watch(url, messageHash, request, messageHash);
}
handleTicker(client, message) {
//
// subscribed
// {
// "seqnum": 0,
// "event": "subscribed",
// "channel": "ticker",
// "symbol": "BTC-USD"
// }
// snapshot
// {
// "seqnum": 1,
// "event": "snapshot",
// "channel": "ticker",
// "symbol": "BTC-USD",
// "price_24h": 23071.4,
// "volume_24h": 236.28398636,
// "last_trade_price": 23936.4,
// "mark_price": 23935.335240262
// }
// update
// {
// "seqnum": 2,
// "event": "updated",
// "channel": "ticker",
// "symbol": "BTC-USD",
// "mark_price": 23935.242443617
// }
//
const event = this.safeString(message, 'event');
const marketId = this.safeString(message, 'symbol');
const market = this.safeMarket(marketId);
const symbol = market['symbol'];
let ticker = undefined;
if (event === 'subscribed') {
return;
}
else if (event === 'snapshot') {
ticker = this.parseTicker(message, market);
}
else if (event === 'updated') {
const lastTicker = this.safeValue(this.tickers, symbol);
ticker = this.parseWsUpdatedTicker(message, lastTicker, market);
}
const messageHash = 'ticker:' + symbol;
this.tickers[symbol] = ticker;
client.resolve(ticker, messageHash);
}
parseWsUpdatedTicker(ticker, lastTicker = undefined, market = undefined) {
//
// {
// "seqnum": 2,
// "event": "updated",
// "channel": "ticker",
// "symbol": "BTC-USD",
// "mark_price": 23935.242443617
// }
//
const marketId = this.safeString(ticker, 'symbol');
const symbol = this.safeSymbol(marketId, undefined, '-');
const last = this.safeString(ticker, 'mark_price');
return this.safeTicker({
'symbol': symbol,
'timestamp': undefined,
'datetime': undefined,
'high': undefined,
'low': undefined,
'bid': undefined,
'bidVolume': undefined,
'ask': undefined,
'askVolume': undefined,
'vwap': undefined,
'open': this.safeString(lastTicker, 'open'),
'close': undefined,
'last': last,
'previousClose': this.safeString(lastTicker, 'close'),
'change': undefined,
'percentage': undefined,
'average': undefined,
'baseVolume': this.safeString(lastTicker, 'baseVolume'),
'quoteVolume': undefined,
'info': this.extend(this.safeValue(lastTicker, 'info', {}), ticker),
}, market);
}
/**
* @method
* @name blockchaincom#watchTrades
* @description get the list of most recent trades for a particular symbol
* @see https://exchange.blockchain.com/api/#trades
* @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 watchTrades(symbol, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
const market = this.market(symbol);
symbol = market['symbol'];
const url = this.urls['api']['ws'];
const messageHash = 'trades:' + symbol;
let request = {
'action': 'subscribe',
'channel': 'trades',
'symbol': market['id'],
};
request = this.deepExtend(request, params);
const trades = await this.watch(url, messageHash, request, messageHash, request);
return this.filterBySinceLimit(trades, since, limit, 'timestamp', true);
}
handleTrades(client, message) {
//
// subscribed
// {
// "seqnum": 0,
// "event": "subscribed",
// "channel": "trades",
// "symbol": "BTC-USDT"
// }
// updates
// {
// "seqnum": 1,
// "event": "updated",
// "channel": "trades",
// "symbol": "BTC-USDT",
// "timestamp": "2022-08-08T17:23:48.163096Z",
// "side": "sell",
// "qty": 0.083523,
// "price": 23940.67,
// "trade_id": "563078810223444"
// }
//
const event = this.safeString(message, 'event');
if (event !== 'updated') {
return;
}
const marketId = this.safeString(message, 'symbol');
const symbol = this.safeSymbol(marketId);
const market = this.safeMarket(marketId);
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;
}
const parsed = this.parseWsTrade(message, market);
stored.append(parsed);
this.trades[symbol] = stored;
client.resolve(this.trades[symbol], messageHash);
}
parseWsTrade(trade, market = undefined) {
//
// {
// "seqnum": 1,
// "event": "updated",
// "channel": "trades",
// "symbol": "BTC-USDT",
// "timestamp": "2022-08-08T17:23:48.163096Z",
// "side": "sell",
// "qty": 0.083523,
// "price": 23940.67,
// "trade_id": "563078810223444"
// }
//
const marketId = this.safeString(trade, 'symbol');
const datetime = this.safeString(trade, 'timestamp');
return this.safeTrade({
'id': this.safeString(trade, 'trade_id'),
'timestamp': this.parse8601(datetime),
'datetime': datetime,
'symbol': this.safeSymbol(marketId, market, '-'),
'order': undefined,
'type': undefined,
'side': this.safeString(trade, 'side'),
'takerOrMaker': undefined,
'price': this.safeString(trade, 'price'),
'amount': this.safeString(trade, 'qty'),
'cost': undefined,
'fee': undefined,
'info': trade,
}, market);
}
/**
* @method
* @name blockchaincom#fetchOrders
* @description watches information on multiple orders made by the user
* @see https://exchange.blockchain.com/api/#mass-order-status-request-ordermassstatusrequest
* @param {string} symbol unified market symbol of the market orders were made in
* @param {int} [since] the earliest time in ms to fetch orders for
* @param {int} [limit] the maximum number of order structures to retrieve
* @param {object} [params] extra parameters specific to the exchange API endpoint
* @returns {object[]} a list of [order structures]{@link https://docs.ccxt.com/#/?id=order-structure}
*/
async watchOrders(symbol = undefined, since = undefined, limit = undefined, params = {}) {
await this.loadMarkets();
await this.authenticate();
if (symbol !== undefined) {
const market = this.market(symbol);
symbol = market['symbol'];
}
const url = this.urls['api']['ws'];
const message = {
'action': 'subscribe',
'channel': 'trading',
};
const messageHash = 'orders';
const request = this.deepExtend(message, params);
const orders = await this.watch(url, messageHash, request, messageHash);
if (this.newUpdates) {
limit = orders.getLimit(symbol, limit);
}
return this.filterBySymbolSinceLimit(orders, symbol, since, limit, true);
}
handleOrders(client, message) {
//
// {
// "seqnum": 1,
// "event": "rejected",
// "channel": "trading",
// "text": "Not subscribed to channel"
// }
// snapshot
// {
// "seqnum": 2,
// "event": "snapshot",
// "channel": "trading",
// "orders": [
// {
// "orderID": "562965341621940",
// "gwOrderId": 181011136260,
// "clOrdID": "016caf67f7a94508webd",
// "symbol": "BTC-USD",
// "side": "sell",
// "ordType": "limit",
// "orderQty": 0.000675,
// "leavesQty": 0.000675,
// "cumQty": 0,
// "avgPx": 0,
// "ordStatus": "open",
// "timeInForce": "GTC",
// "text": "New order",
// "execType": "0",
// "execID": "21415965325",
// "transactTime": "2022-08-08T23:31:00.550795Z",
// "msgType": 8,
// "lastPx": 0,
// "lastShares": 0,
// "tradeId": "0",
// "fee": 0,
// "price": 30000,
// "marginOrder": false,
// "closePositionOrder": false
// }
// ],
// "positions": []
// }
// update
// {
// "seqnum": 3,
// "event": "updated",
// "channel": "trading",
// "orderID": "562965341621940",
// "gwOrderId": 181011136260,
// "clOrdID": "016caf67f7a94508webd",
// "symbol": "BTC-USD",
// "side": "sell",
// "ordType": "limit",
// "orderQty": 0.000675,
// "leavesQty": 0.000675,
// "cumQty": 0,
// "avgPx": 0,
// "ordStatus": "cancelled",
// "timeInForce": "GTC",
// "text": "Canceled by User",
// "execType": "4",
// "execID": "21416034921",
// "transactTime": "2022-08-08T23:33:25.727785Z",
// "msgType": 8,
// "lastPx": 0,
// "lastShares": 0,
// "tradeId": "0",
// "fee": 0,
// "price": 30000,
// "marginOrder": false,
// "closePositionOrder": false
// }
//
const event = this.safeString(message, 'event');
const messageHash = 'orders';
const cachedOrders = this.orders;
if (cachedOrders === undefined) {
const limit = this.safeInteger(this.options, 'ordersLimit', 1000);
this.orders = new ArrayCacheBySymbolById(limit);
}
if (event === 'subscribed') {
return;
}
else if (event === 'rejected') {
throw new ExchangeError(this.id + ' ' + this.json(message));
}
else if (event === 'snapshot') {
const orders = this.safeValue(message, 'orders', []);
for (let i = 0; i < orders.length; i++) {
const order = orders[i];
const parsedOrder = this.parseWsOrder(order);
cachedOrders.append(parsedOrder);
}
}
else if (event === 'updated') {
const parsedOrder = this.parseWsOrder(message);
cachedOrders.append(parsedOrder);
}
this.orders = cachedOrders;
client.resolve(this.orders, messageHash);
}
parseWsOrder(order, market = undefined) {
//
// {
// "seqnum": 3,
// "event": "updated",
// "channel": "trading",
// "orderID": "562965341621940",
// "gwOrderId": 181011136260,
// "clOrdID": "016caf67f7a94508webd",
// "symbol": "BTC-USD",
// "side": "sell",
// "ordType": "limit",
// "orderQty": 0.000675,
// "leavesQty": 0.000675,
// "cumQty": 0,
// "avgPx": 0,
// "ordStatus": "cancelled",
// "timeInForce": "GTC",
// "text": "Canceled by User",
// "execType": "4",
// "execID": "21416034921",
// "transactTime": "2022-08-08T23:33:25.727785Z",
// "msgType": 8,
// "lastPx": 0,
// "lastShares": 0,
// "tradeId": "0",
// "fee": 0,
// "price": 30000,
// "marginOrder": false,
// "closePositionOrder": false
// }
//
const datetime = this.safeString(order, 'transactTime');
const status = this.safeString(order, 'ordStatus');
const marketId = this.safeString(order, 'symbol');
market = this.safeMarket(marketId, market);
const tradeId = this.safeString(order, 'tradeId');
const trades = [];
if (tradeId !== '0') {
trades.push({ 'id': tradeId });
}
return this.safeOrder({
'id': this.safeString(order, 'orderID'),
'clientOrderId': this.safeString(order, 'clOrdID'),
'datetime': datetime,
'timestamp': this.parse8601(datetime),
'status': this.parseWsOrderStatus(status),
'symbol': this.safeSymbol(marketId, market),
'type': this.safeString(order, 'ordType'),
'timeInForce': this.safeString(order, 'timeInForce'),
'postOnly': this.safeString(order, 'execInst') === 'ALO',
'side': this.safeString(order, 'side'),
'price': this.safeString(order, 'price'),
'stopPrice': this.safeString(order, 'stopPx'),
'cost': undefined,
'amount': this.safeString(order, 'orderQty'),
'filled': this.safeString(order, 'cumQty'),
'remaining': this.safeString(order, 'leavesQty'),
'trades': trades,
'fee': {
'rate': undefined,
'cost': this.safeNumber(order, 'fee'),
'currency': this.safeString(market, 'quote'),
},
'info': order,
'lastTradeTimestamp': undefined,
'average': this.safeString(order, 'avgPx'),
}, market);
}
parseWsOrderStatus(status) {
const statuses = {
'pending': 'open',
'open': 'open',
'rejected': 'rejected',
'cancelled': 'canceled',
'filled': 'closed',
'partial': 'open',
'expired': 'expired',
};
return this.safeString(statuses, status, status);
}
/**
* @method
* @name blockchaincom#watchOrderBook
* @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
* @see https://exchange.blockchain.com/api/#l2-order-book
* @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 {objectConstructor} [params] extra parameters specific to the exchange API endpoint
* @param {string} [params.type] accepts l2 or l3 for level 2 or level 3 order book
* @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 = {}) {
await this.loadMarkets();
const market = this.market(symbol);
const url = this.urls['api']['ws'];
const type = this.safeString(params, 'type', 'l2');
params = this.omit(params, 'type');
const messageHash = 'orderbook:' + symbol + ':' + type;
const subscribe = {
'action': 'subscribe',
'channel': type,
'symbol': market['id'],
};
const request = this.deepExtend(subscribe, params);
const orderbook = await this.watch(url, messageHash, request, messageHash);
return orderbook.limit();
}
handleOrderBook(client, message) {
//
// subscribe
// {
// "seqnum": 0,
// "event": "subscribed",
// "channel": "l2",
// "symbol": "BTC-USDT",
// "batching": false
// }
// snapshot
// {
// "seqnum": 1,
// "event": "snapshot",
// "channel": "l2",
// "symbol": "BTC-USDT",
// "bids": [
// { num: 1, px: 0.01, qty: 22 },
// ],
// "asks": [
// { num: 1, px: 23840.26, qty: 0.25 },
// ],
// "timestamp": "2022-08-08T22:03:19.071870Z"
// }
// update
// {
// "seqnum": 2,
// "event": "updated",
// "channel": "l2",
// "symbol": "BTC-USDT",
// "bids": [],
// "asks": [ { num: 1, px: 23855.06, qty: 1.04786347 } ],
// "timestamp": "2022-08-08T22:03:19.014680Z"
// }
//
const event = this.safeString(message, 'event');
if (event === 'subscribed') {
return;
}
const type = this.safeString(message, 'channel');
const marketId = this.safeString(message, 'symbol');
const symbol = this.safeSymbol(marketId);
const messageHash = 'orderbook:' + symbol + ':' + type;
const datetime = this.safeString(message, 'timestamp');
const timestamp = this.parse8601(datetime);
if (this.safeValue(this.orderbooks, symbol) === undefined) {
this.orderbooks[symbol] = this.countedOrderBook();
}
const orderbook = this.orderbooks[symbol];
if (event === 'snapshot') {
const snapshot = this.parseOrderBook(message, symbol, timestamp, 'bids', 'asks', 'px', 'qty', 'num');
orderbook.reset(snapshot);
}
else if (event === 'updated') {
const asks = this.safeList(message, 'asks', []);
const bids = this.safeList(message, 'bids', []);
this.handleDeltas(orderbook['asks'], asks);
this.handleDeltas(orderbook['bids'], bids);
orderbook['timestamp'] = timestamp;
orderbook['datetime'] = datetime;
}
else {
throw new NotSupported(this.id + ' watchOrderBook() does not support ' + event + ' yet');
}
client.resolve(orderbook, messageHash);
}
handleDelta(bookside, delta) {
const bookArray = this.parseBidAsk(delta, 'px', 'qty', 'num');
bookside.storeArray(bookArray);
}
handleDeltas(bookside, deltas) {
for (let i = 0; i < deltas.length; i++) {
this.handleDelta(bookside, deltas[i]);
}
}
handleMessage(client, message) {
const channel = this.safeString(message, 'channel');
const handlers = {
'ticker': this.handleTicker,
'trades': this.handleTrades,
'prices': this.handleOHLCV,
'l2': this.handleOrderBook,
'l3': this.handleOrderBook,
'auth': this.handleAuthenticationMessage,
'balances': this.handleBalance,
'trading': this.handleOrders,
};
const handler = this.safeValue(handlers, channel);
if (handler !== undefined) {
handler.call(this, client, message);
return;
}
throw new NotSupported(this.id + ' received an unsupported message: ' + this.json(message));
}
handleAuthenticationMessage(client, message) {
//
// {
// "seqnum": 0,
// "event": "subscribed",
// "channel": "auth",
// "readOnly": false
// }
//
const event = this.safeString(message, 'event');
if (event !== 'subscribed') {
throw new AuthenticationError(this.id + ' received an authentication error: ' + this.json(message));
}
const future = this.safeValue(client.futures, 'authenticated');
if (future !== undefined) {
future.resolve(true);
}
}
async authenticate(params = {}) {
const url = this.urls['api']['ws'];
const client = this.client(url);
const messageHash = 'authenticated';
const future = client.future(messageHash);
const isAuthenticated = this.safeValue(client.subscriptions, messageHash);
if (isAuthenticated === undefined) {
this.checkRequiredCredentials();
const request = {
'action': 'subscribe',
'channel': 'auth',
'token': this.secret,
};
return this.watch(url, messageHash, this.extend(request, params), messageHash);
}
return await future;
}
}