UNPKG

ccxt

Version:

A JavaScript / TypeScript / Python / C# / PHP cryptocurrency trading library with support for 100+ exchanges

725 lines (722 loc) • 29.1 kB
// ---------------------------------------------------------------------------- // 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 alpacaRest from '../alpaca.js'; import { ExchangeError, AuthenticationError } from '../base/errors.js'; import { ArrayCache, ArrayCacheBySymbolById, ArrayCacheByTimestamp } from '../base/ws/Cache.js'; // --------------------------------------------------------------------------- export default class alpaca extends alpacaRest { describe() { return this.deepExtend(super.describe(), { 'has': { 'ws': true, 'watchBalance': false, 'watchMyTrades': true, 'watchOHLCV': true, 'watchOrderBook': true, 'watchOrders': true, 'watchTicker': true, 'watchTickers': false, 'watchTrades': true, 'watchPosition': false, }, 'urls': { 'api': { 'ws': { 'crypto': 'wss://stream.data.alpaca.markets/v1beta2/crypto', 'trading': 'wss://api.alpaca.markets/stream', }, }, 'test': { 'ws': { 'crypto': 'wss://stream.data.alpaca.markets/v1beta2/crypto', 'trading': 'wss://paper-api.alpaca.markets/stream', }, }, }, 'options': {}, 'streaming': {}, 'exceptions': { 'ws': { 'exact': {}, }, }, }); } /** * @method * @name alpaca#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.alpaca.markets/docs/real-time-crypto-pricing-data#quotes * @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 = {}) { const url = this.urls['api']['ws']['crypto']; await this.authenticate(url); await this.loadMarkets(); const market = this.market(symbol); const messageHash = 'ticker:' + market['symbol']; const request = { 'action': 'subscribe', 'quotes': [market['id']], }; return await this.watch(url, messageHash, this.extend(request, params), messageHash); } handleTicker(client, message) { // // { // "T": "q", // "S": "BTC/USDT", // "bp": 17394.44, // "bs": 0.021981, // "ap": 17397.99, // "as": 0.02, // "t": "2022-12-16T06:07:56.611063286Z" // ] // const ticker = this.parseTicker(message); const symbol = ticker['symbol']; const messageHash = 'ticker:' + symbol; this.tickers[symbol] = ticker; client.resolve(this.tickers[symbol], messageHash); } parseTicker(ticker, market = undefined) { // // { // "T": "q", // "S": "BTC/USDT", // "bp": 17394.44, // "bs": 0.021981, // "ap": 17397.99, // "as": 0.02, // "t": "2022-12-16T06:07:56.611063286Z" // } // const marketId = this.safeString(ticker, 'S'); const datetime = this.safeString(ticker, 't'); return this.safeTicker({ 'symbol': this.safeSymbol(marketId, market), 'timestamp': this.parse8601(datetime), 'datetime': datetime, 'high': undefined, 'low': undefined, 'bid': this.safeString(ticker, 'bp'), 'bidVolume': this.safeString(ticker, 'bs'), 'ask': this.safeString(ticker, 'ap'), 'askVolume': this.safeString(ticker, 'as'), 'vwap': undefined, 'open': undefined, 'close': undefined, 'last': undefined, 'previousClose': undefined, 'change': undefined, 'percentage': undefined, 'average': undefined, 'baseVolume': undefined, 'quoteVolume': undefined, 'info': ticker, }, market); } /** * @method * @name alpaca#watchOHLCV * @description watches historical candlestick data containing the open, high, low, and close price, and the volume of a market * @see https://docs.alpaca.markets/docs/real-time-crypto-pricing-data#bars * @param {string} symbol unified symbol of the market to fetch OHLCV data for * @param {string} timeframe the length of time each candle represents * @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 = {}) { const url = this.urls['api']['ws']['crypto']; await this.authenticate(url); await this.loadMarkets(); const market = this.market(symbol); symbol = market['symbol']; const request = { 'action': 'subscribe', 'bars': [market['id']], }; const messageHash = 'ohlcv:' + symbol; const ohlcv = await this.watch(url, messageHash, this.extend(request, params), messageHash); if (this.newUpdates) { limit = ohlcv.getLimit(symbol, limit); } return this.filterBySinceLimit(ohlcv, since, limit, 0, true); } handleOHLCV(client, message) { // // { // "T": "b", // "S": "BTC/USDT", // "o": 17416.39, // "h": 17424.82, // "l": 17416.39, // "c": 17424.82, // "v": 1.341054, // "t": "2022-12-16T06:53:00Z", // "n": 21, // "vw": 17421.9529234915 // } // const marketId = this.safeString(message, 'S'); const symbol = this.safeSymbol(marketId); let stored = this.safeValue(this.ohlcvs, symbol); if (stored === undefined) { const limit = this.safeInteger(this.options, 'OHLCVLimit', 1000); stored = new ArrayCacheByTimestamp(limit); this.ohlcvs[symbol] = stored; } const parsed = this.parseOHLCV(message); stored.append(parsed); const messageHash = 'ohlcv:' + symbol; client.resolve(stored, messageHash); } /** * @method * @name alpaca#watchOrderBook * @description watches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @see https://docs.alpaca.markets/docs/real-time-crypto-pricing-data#orderbooks * @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 = {}) { const url = this.urls['api']['ws']['crypto']; await this.authenticate(url); await this.loadMarkets(); const market = this.market(symbol); symbol = market['symbol']; const messageHash = 'orderbook' + ':' + symbol; const request = { 'action': 'subscribe', 'orderbooks': [market['id']], }; const orderbook = await this.watch(url, messageHash, this.extend(request, params), messageHash); return orderbook.limit(); } handleOrderBook(client, message) { // // snapshot // { // "T": "o", // "S": "BTC/USDT", // "t": "2022-12-16T06:35:31.585113205Z", // "b": [{ // "p": 17394.37, // "s": 0.015499, // }, // ... // ], // "a": [{ // "p": 17398.8, // "s": 0.042919, // }, // ... // ], // "r": true, // } // const marketId = this.safeString(message, 'S'); const symbol = this.safeSymbol(marketId); const datetime = this.safeString(message, 't'); const timestamp = this.parse8601(datetime); const isSnapshot = this.safeBool(message, 'r', false); if (!(symbol in this.orderbooks)) { this.orderbooks[symbol] = this.orderBook(); } const orderbook = this.orderbooks[symbol]; if (isSnapshot) { const snapshot = this.parseOrderBook(message, symbol, timestamp, 'b', 'a', 'p', 's'); orderbook.reset(snapshot); } else { const asks = this.safeList(message, 'a', []); const bids = this.safeList(message, 'b', []); this.handleDeltas(orderbook['asks'], asks); this.handleDeltas(orderbook['bids'], bids); orderbook['timestamp'] = timestamp; orderbook['datetime'] = datetime; } const messageHash = 'orderbook' + ':' + symbol; this.orderbooks[symbol] = orderbook; client.resolve(orderbook, messageHash); } handleDelta(bookside, delta) { const bidAsk = this.parseBidAsk(delta, 'p', 's'); bookside.storeArray(bidAsk); } handleDeltas(bookside, deltas) { for (let i = 0; i < deltas.length; i++) { this.handleDelta(bookside, deltas[i]); } } /** * @method * @name alpaca#watchTrades * @description watches information on multiple trades made in a market * @see https://docs.alpaca.markets/docs/real-time-crypto-pricing-data#trades * @param {string} symbol unified market symbol of the market trades were made in * @param {int} [since] the earliest time in ms to fetch orders for * @param {int} [limit] the maximum number of trade structures to retrieve * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=trade-structure} */ async watchTrades(symbol, since = undefined, limit = undefined, params = {}) { const url = this.urls['api']['ws']['crypto']; await this.authenticate(url); await this.loadMarkets(); const market = this.market(symbol); symbol = market['symbol']; const messageHash = 'trade:' + symbol; const request = { 'action': 'subscribe', 'trades': [market['id']], }; const trades = await this.watch(url, messageHash, this.extend(request, params), messageHash); if (this.newUpdates) { limit = trades.getLimit(symbol, limit); } return this.filterBySinceLimit(trades, since, limit, 'timestamp', true); } handleTrades(client, message) { // // { // "T": "t", // "S": "BTC/USDT", // "p": 17408.8, // "s": 0.042919, // "t": "2022-12-16T06:43:18.327Z", // "i": 16585162, // "tks": "B" // ] // const marketId = this.safeString(message, 'S'); const symbol = this.safeSymbol(marketId); 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.parseTrade(message); stored.append(parsed); const messageHash = 'trade' + ':' + symbol; client.resolve(stored, messageHash); } /** * @method * @name alpaca#watchMyTrades * @description watches information on multiple trades made by the user * @see https://docs.alpaca.markets/docs/websocket-streaming#trade-updates * @param {string} symbol unified market symbol of the market trades were made in * @param {int} [since] the earliest time in ms to fetch trades for * @param {int} [limit] the maximum number of trade structures to retrieve * @param {object} [params] extra parameters specific to the exchange API endpoint * @param {boolean} [params.unifiedMargin] use unified margin account * @returns {object[]} a list of [trade structures]{@link https://docs.ccxt.com/#/?id=trade-structure} */ async watchMyTrades(symbol = undefined, since = undefined, limit = undefined, params = {}) { const url = this.urls['api']['ws']['trading']; await this.authenticate(url); let messageHash = 'myTrades'; await this.loadMarkets(); if (symbol !== undefined) { symbol = this.symbol(symbol); messageHash += ':' + symbol; } const request = { 'action': 'listen', 'data': { 'streams': ['trade_updates'], }, }; const trades = await this.watch(url, messageHash, this.extend(request, params), messageHash); if (this.newUpdates) { limit = trades.getLimit(symbol, limit); } return this.filterBySinceLimit(trades, since, limit, 'timestamp', true); } /** * @method * @name alpaca#watchOrders * @description watches information on multiple orders made by the user * @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 = {}) { const url = this.urls['api']['ws']['trading']; await this.authenticate(url); await this.loadMarkets(); let messageHash = 'orders'; if (symbol !== undefined) { const market = this.market(symbol); symbol = market['symbol']; messageHash = 'orders:' + symbol; } const request = { 'action': 'listen', 'data': { 'streams': ['trade_updates'], }, }; const orders = await this.watch(url, messageHash, this.extend(request, params), messageHash); if (this.newUpdates) { limit = orders.getLimit(symbol, limit); } return this.filterBySymbolSinceLimit(orders, symbol, since, limit, true); } handleTradeUpdate(client, message) { this.handleOrder(client, message); this.handleMyTrade(client, message); } handleOrder(client, message) { // // { // "stream": "trade_updates", // "data": { // "event": "new", // "timestamp": "2022-12-16T07:28:51.67621869Z", // "order": { // "id": "c2470331-8993-4051-bf5d-428d5bdc9a48", // "client_order_id": "0f1f3764-107a-4d09-8b9a-d75a11738f5c", // "created_at": "2022-12-16T02:28:51.673531798-05:00", // "updated_at": "2022-12-16T02:28:51.678736847-05:00", // "submitted_at": "2022-12-16T02:28:51.673015558-05:00", // "filled_at": null, // "expired_at": null, // "cancel_requested_at": null, // "canceled_at": null, // "failed_at": null, // "replaced_at": null, // "replaced_by": null, // "replaces": null, // "asset_id": "276e2673-764b-4ab6-a611-caf665ca6340", // "symbol": "BTC/USD", // "asset_class": "crypto", // "notional": null, // "qty": "0.01", // "filled_qty": "0", // "filled_avg_price": null, // "order_class": '', // "order_type": "market", // "type": "market", // "side": "buy", // "time_in_force": "gtc", // "limit_price": null, // "stop_price": null, // "status": "new", // "extended_hours": false, // "legs": null, // "trail_percent": null, // "trail_price": null, // "hwm": null // }, // "execution_id": "5f781a30-b9a3-4c86-b466-2175850cf340" // } // } // const data = this.safeValue(message, 'data', {}); const rawOrder = this.safeValue(data, 'order', {}); if (this.orders === undefined) { const limit = this.safeInteger(this.options, 'ordersLimit', 1000); this.orders = new ArrayCacheBySymbolById(limit); } const orders = this.orders; const order = this.parseOrder(rawOrder); orders.append(order); let messageHash = 'orders'; client.resolve(orders, messageHash); messageHash = 'orders:' + order['symbol']; client.resolve(orders, messageHash); } handleMyTrade(client, message) { // // { // "stream": "trade_updates", // "data": { // "event": "new", // "timestamp": "2022-12-16T07:28:51.67621869Z", // "order": { // "id": "c2470331-8993-4051-bf5d-428d5bdc9a48", // "client_order_id": "0f1f3764-107a-4d09-8b9a-d75a11738f5c", // "created_at": "2022-12-16T02:28:51.673531798-05:00", // "updated_at": "2022-12-16T02:28:51.678736847-05:00", // "submitted_at": "2022-12-16T02:28:51.673015558-05:00", // "filled_at": null, // "expired_at": null, // "cancel_requested_at": null, // "canceled_at": null, // "failed_at": null, // "replaced_at": null, // "replaced_by": null, // "replaces": null, // "asset_id": "276e2673-764b-4ab6-a611-caf665ca6340", // "symbol": "BTC/USD", // "asset_class": "crypto", // "notional": null, // "qty": "0.01", // "filled_qty": "0", // "filled_avg_price": null, // "order_class": '', // "order_type": "market", // "type": "market", // "side": "buy", // "time_in_force": "gtc", // "limit_price": null, // "stop_price": null, // "status": "new", // "extended_hours": false, // "legs": null, // "trail_percent": null, // "trail_price": null, // "hwm": null // }, // "execution_id": "5f781a30-b9a3-4c86-b466-2175850cf340" // } // } // const data = this.safeValue(message, 'data', {}); const event = this.safeString(data, 'event'); if (event !== 'fill' && event !== 'partial_fill') { return; } const rawOrder = this.safeValue(data, 'order', {}); let myTrades = this.myTrades; if (myTrades === undefined) { const limit = this.safeInteger(this.options, 'tradesLimit', 1000); myTrades = new ArrayCacheBySymbolById(limit); } const trade = this.parseMyTrade(rawOrder); myTrades.append(trade); let messageHash = 'myTrades:' + trade['symbol']; client.resolve(myTrades, messageHash); messageHash = 'myTrades'; client.resolve(myTrades, messageHash); } parseMyTrade(trade, market = undefined) { // // { // "id": "c2470331-8993-4051-bf5d-428d5bdc9a48", // "client_order_id": "0f1f3764-107a-4d09-8b9a-d75a11738f5c", // "created_at": "2022-12-16T02:28:51.673531798-05:00", // "updated_at": "2022-12-16T02:28:51.678736847-05:00", // "submitted_at": "2022-12-16T02:28:51.673015558-05:00", // "filled_at": null, // "expired_at": null, // "cancel_requested_at": null, // "canceled_at": null, // "failed_at": null, // "replaced_at": null, // "replaced_by": null, // "replaces": null, // "asset_id": "276e2673-764b-4ab6-a611-caf665ca6340", // "symbol": "BTC/USD", // "asset_class": "crypto", // "notional": null, // "qty": "0.01", // "filled_qty": "0", // "filled_avg_price": null, // "order_class": '', // "order_type": "market", // "type": "market", // "side": "buy", // "time_in_force": "gtc", // "limit_price": null, // "stop_price": null, // "status": "new", // "extended_hours": false, // "legs": null, // "trail_percent": null, // "trail_price": null, // "hwm": null // } // const marketId = this.safeString(trade, 'symbol'); const datetime = this.safeString(trade, 'filled_at'); let type = this.safeString(trade, 'type'); if (type.indexOf('limit') >= 0) { // might be limit or stop-limit type = 'limit'; } return this.safeTrade({ 'id': this.safeString(trade, 'i'), 'info': trade, 'timestamp': this.parse8601(datetime), 'datetime': datetime, 'symbol': this.safeSymbol(marketId, undefined, '/'), 'order': this.safeString(trade, 'id'), 'type': type, 'side': this.safeString(trade, 'side'), 'takerOrMaker': (type === 'market') ? 'taker' : 'maker', 'price': this.safeString(trade, 'filled_avg_price'), 'amount': this.safeString(trade, 'filled_qty'), 'cost': undefined, 'fee': undefined, }, market); } async authenticate(url, params = {}) { this.checkRequiredCredentials(); const messageHash = 'authenticated'; const client = this.client(url); const future = client.future(messageHash); const authenticated = this.safeValue(client.subscriptions, messageHash); if (authenticated === undefined) { let request = { 'action': 'auth', 'key': this.apiKey, 'secret': this.secret, }; if (url === this.urls['api']['ws']['trading']) { // this auth request is being deprecated in test environment request = { 'action': 'authenticate', 'data': { 'key_id': this.apiKey, 'secret_key': this.secret, }, }; } this.watch(url, messageHash, request, messageHash, future); } return await future; } handleErrorMessage(client, message) { // // { // "T": "error", // "code": 400, // "msg": "invalid syntax" // } // const code = this.safeString(message, 'code'); const msg = this.safeValue(message, 'msg', {}); throw new ExchangeError(this.id + ' code: ' + code + ' message: ' + msg); } handleConnected(client, message) { // // { // "T": "success", // "msg": "connected" // } // return message; } handleCryptoMessage(client, message) { for (let i = 0; i < message.length; i++) { const data = message[i]; const T = this.safeString(data, 'T'); const msg = this.safeString(data, 'msg'); if (T === 'subscription') { this.handleSubscription(client, data); return; } if (T === 'success' && msg === 'connected') { this.handleConnected(client, data); return; } if (T === 'success' && msg === 'authenticated') { this.handleAuthenticate(client, data); return; } const methods = { 'error': this.handleErrorMessage, 'b': this.handleOHLCV, 'q': this.handleTicker, 't': this.handleTrades, 'o': this.handleOrderBook, }; const method = this.safeValue(methods, T); if (method !== undefined) { method.call(this, client, data); } } } handleTradingMessage(client, message) { const stream = this.safeString(message, 'stream'); const methods = { 'authorization': this.handleAuthenticate, 'listening': this.handleSubscription, 'trade_updates': this.handleTradeUpdate, }; const method = this.safeValue(methods, stream); if (method !== undefined) { method.call(this, client, message); } } handleMessage(client, message) { if (Array.isArray(message)) { this.handleCryptoMessage(client, message); return; } this.handleTradingMessage(client, message); } handleAuthenticate(client, message) { // // crypto // { // "T": "success", // "msg": "connected" // ] // // trading // { // "stream": "authorization", // "data": { // "status": "authorized", // "action": "authenticate" // } // } // error // { // "stream": "authorization", // "data": { // "action": "authenticate", // "message": "access key verification failed", // "status": "unauthorized" // } // } // const T = this.safeString(message, 'T'); const data = this.safeValue(message, 'data', {}); const status = this.safeString(data, 'status'); if (T === 'success' || status === 'authorized') { const promise = client.futures['authenticated']; promise.resolve(message); return; } throw new AuthenticationError(this.id + ' failed to authenticate.'); } handleSubscription(client, message) { // // crypto // { // "T": "subscription", // "trades": [], // "quotes": [ "BTC/USDT" ], // "orderbooks": [], // "bars": [], // "updatedBars": [], // "dailyBars": [] // } // trading // { // "stream": "listening", // "data": { // "streams": ["trade_updates"] // } // } // return message; } }