UNPKG

@umerx/alpaca

Version:

A TypeScript Node.js library for the https://alpaca.markets REST API and WebSocket streams.

186 lines (185 loc) 7.95 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AlpacaStream = void 0; const is_blob_1 = __importDefault(require("is-blob")); const parse_js_1 = __importDefault(require("./parse.cjs")); const isomorphic_ws_1 = __importDefault(require("isomorphic-ws")); const endpoints_js_1 = __importDefault(require("./endpoints.cjs")); const eventemitter3_1 = __importDefault(require("eventemitter3")); class AlpacaStream extends eventemitter3_1.default { constructor(params) { // construct EventEmitter super(); this.params = params; this.baseURLs = endpoints_js_1.default; // override endpoints if custom provided if ('endpoi.cts' in params) { this.baseURLs = Object.assign(endpoints_js_1.default, params.endpoints); } if ( // if not specified !('paper' in params.credentials) && // and live key isn't already provided !('key' in params.credentials && params.credentials.key.startsWith('A'))) { params.credentials['paper'] = true; } // assign the host we will connect to switch (params.type) { case 'account': this.host = params.credentials.paper ? this.baseURLs.websocket.account.replace('api.', 'paper-api.') : this.baseURLs.websocket.account; break; case 'market_data': this.host = this.baseURLs.websocket.market_data(this.params.source); break; default: this.host = 'unknown'; } this.connection = new isomorphic_ws_1.default(this.host); this.connection.onopen = () => { let message = {}; switch (this.params.type) { case 'account': message = { action: 'authenticate', data: { key_id: params.credentials.key, secret_key: params.credentials.secret, }, }; break; case 'market_data': // {"action":"auth","key":"PK*****","secret":"*************"} message = Object.assign({ action: 'auth' }, params.credentials); break; } this.connection.send(JSON.stringify(message)); // pass through this.emit('open', this); }; // pass through this.connection.onclose = () => this.emit('close', this); this.connection.onmessage = (event) => __awaiter(this, void 0, void 0, function* () { let data = event.data; if ((0, is_blob_1.default)(data)) { data = yield event.data.text(); } else if (data instanceof ArrayBuffer) { data = String.fromCharCode(...new Uint8Array(event.data)); } let parsed = JSON.parse(data), messages = this.params.type == 'account' ? [parsed] : parsed; messages.forEach((message) => { // pass the message this.emit('message', message); // pass authenticated event if ('T' in message && message.msg == 'authenticated') { this.authenticated = true; this.emit('authenticated', this); } else if ('stream' in message && message.stream == 'authorization') { if (message.data.status == 'authorized') { this.authenticated = true; this.emit('authenticated', this); } } // pass trade_updates event if ('stream' in message && message.stream == 'trade_updates') { this.emit('trade_updates', parse_js_1.default.trade_update(message.data)); } // pass trade, quote, bar event const x = { success: 'success', subscription: 'subscription', error: 'error', t: 'trade', q: 'quote', b: 'bar', }; if ('T' in message) { this.emit(x[message.T.split('.')[0]], message); } }); }); // pass the error this.connection.onerror = (err) => { this.emit('error', err); }; } /** * Retrieve the underlying WebSocket connection AlpacaStream uses. * Now callers can read and modify properties of the web socket * i.e., close the websocket with AlpacaStream.getConnection().close(). * @returns a WebSocket object */ getConnection() { return this.connection; } /** * Subscribe to an account or data stream channel. * @param channel trades, quotes, bars, trade_updates * @param symbols only use with data stream ex. [ "AAPL", "TSLA", ... ] */ subscribe(channel, symbols = []) { switch (this.params.type) { case 'account': // {"action":"listen","data":{"streams":["trade_updates"]}} this.send(JSON.stringify({ action: 'listen', data: { streams: [channel] } })); break; case 'market_data': // {"action":"subscribe","trades":["AAPL"],"quotes":["AMD","CLDR"],"bars":["AAPL","VOO"]} let message = { action: 'subscribe' }; message[channel] = symbols; this.send(JSON.stringify(message)); break; } return this; } /** * Unsubscribe to an account or data stream channel. * @param channel trades, quotes, bars, trade_updates * @param symbols only use with data stream ex. [ "AAPL", "TSLA", ... ] */ unsubscribe(channel, symbols = []) { switch (this.params.type) { case 'account': // {"action":"unlisten","data":{"streams":["trade_updates"]}} this.send(JSON.stringify({ action: 'unlisten', data: { streams: [channel] } })); break; case 'market_data': // {"action":"unsubscribe","trades":["AAPL"],"quotes":["AMD","CLDR"],"bars":["AAPL","VOO"]} let message = { action: 'unsubscribe' }; message[channel] = symbols; this.send(JSON.stringify(message)); break; } return this; } send(message) { // don't bother if we aren't authenticated if (!this.authenticated) { throw new Error('not authenticated'); } // if the message is in object form, stringify it for the user if (typeof message == 'object') { message = JSON.stringify(message); } // send it off this.connection.send(message); // chainable return return this; } } exports.AlpacaStream = AlpacaStream;