UNPKG

@ariva-mds/mds

Version:

Stock market data

490 lines (489 loc) 21.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MdsConnection = void 0; const reconnecting_websocket_1 = __importDefault(require("reconnecting-websocket")); const MdsConnectionState_1 = require("./MdsConnectionState"); const RunningRequest_1 = require("./RunningRequest"); const rxjs_1 = require("rxjs"); const MarketstateUpdate_1 = require("./MarketstateUpdate"); const models_1 = require("../models"); const lrucache_1 = require("../lrucache"); class MdsConnection { /** * construct an MdsConnection */ constructor(websocketUrl, authdataCallback, options = { debug: false }) { this.waitingForReconnect = []; this.state = new MdsConnectionState_1.MdsConnectionState(); this.waitingForAuthentification = []; this.nextGeneratedRequestId = 0; this.runningRequests = new Map(); this.isDebug = false; this.reconnectTimer = undefined; // ############################################################################################# /** * Sources * information about sources, access to other data that is linked to a source */ /** * gets the basic information about a source */ this.sourceCache = undefined; /** * gets the trading time for a source * */ this.tradingtimeCache = undefined; /** * gets the tradingtime for a source * */ this.tradingtimeAnalysisCache = undefined; // ############################################################################################# /** * Instruments * information about instruments, access to other data that is linked to an instrument */ /** * Returns one Instrument */ this.masterdataCache = undefined; this.authdataCallback = authdataCallback; this.isDebug = !!options.debug; this.websocket = new reconnecting_websocket_1.default(websocketUrl, [], options.wsOptions); const outer = this; // open-handler -> resend open requests from previous connection this.websocket.onopen = () => { outer.debug(`websocket open, resending ${outer.waitingForReconnect.length} items`); outer.state.connectionOpened(); for (const request of outer.waitingForReconnect) { outer.sendAsSoonAsAuthenticationPermits(request.request); } }; // close handler -> store open requests for resending on reconnect this.websocket.onclose = () => { outer.debug(`websocket closed, queuing ${outer.waitingForReconnect.length} items`); outer.state.connectionClosed(); outer.waitingForReconnect = Array.from(outer.runningRequests.values()); }; this.websocket.onmessage = (e) => { outer.processWebsocketMessageEvent(e); }; this.stayAuthenticated(); this.reconnectTimer = setInterval(() => { if (!outer.state.isTimedOut()) { outer.debug('websocket timed out, calling reconnect'); outer.state.forcedDisconnect(); outer.websocket.reconnect(); } }, 1000); } close() { if (this.reconnectTimer) { clearInterval(this.reconnectTimer); } this.websocket.close(); } /** * create a hearbeat observable that can be subscribed to */ heartbeat() { return this.observable({ heartbeat: {} }).pipe((0, rxjs_1.map)((x) => x.dataHeartbeat)); } /** * sets the priority for sources */ priority(sources) { return this.promise({ priority: { sources: sources } }); } source(sourceId) { if (this.sourceCache == undefined) { const outer = this; this.sourceCache = new lrucache_1.LruCache(sourceId => outer.promise({ getSource: { sourceId: sourceId } }).then(state => (0, models_1.SourcedataFromJSON)(state.dataSource)), 3000); } return this.sourceCache.get(sourceId); } tradingtime(sourceId) { if (this.tradingtimeCache == undefined) { const outer = this; this.tradingtimeCache = new lrucache_1.LruCache(sourceId => outer.promise({ getTradingtime: { sourceId: sourceId } }).then(state => (0, models_1.TradingtimeFromJSON)(state.dataTradingtime)), 3000); } return this.tradingtimeCache.get(sourceId); } tradingtimeAnalysis(sourceId, date) { if (this.tradingtimeAnalysisCache == undefined) { const outer = this; this.tradingtimeAnalysisCache = new lrucache_1.LruCache(parameter => outer.promise({ getTradingtimeAnalysis: { sourceId: parameter.sourceId, date: parameter.date ? new Date(Date.UTC(parameter.date.getFullYear(), parameter.date.getMonth(), parameter.date.getDate())) : undefined } }).then(state => (0, models_1.TradingtimeInfoReplyFromJSON)(state.dataTradingtimeAnalysis)), 3000); } return this.tradingtimeAnalysisCache.get({ sourceId: sourceId, date: date }); } /** * Returns a list or stream of the current trades from this source. * */ tradeticker(sourceId, quality, onlyWithTurnover, preloadSize) { return this.observable({ subscribeSourceTradeticker: { sourceId: sourceId, preloadSize: preloadSize, onlyWithTurnover: onlyWithTurnover, quality: quality } }).pipe((0, rxjs_1.map)(state => (0, models_1.TradetickerEventFromJSON)(state.dataTradeticker))); } masterdata(instrumentId) { if (this.masterdataCache == undefined) { const outer = this; this.masterdataCache = new lrucache_1.LruCache(instrumentId => outer.promise({ getInstrumentMasterdata: { instrumentId: instrumentId } }).then(state => (0, models_1.MasterdataMergedSummaryWithIdFromJSON)(state.dataMasterdata)), 3000); } return this.masterdataCache.get(instrumentId); } /** * Returns a list of all Listings for the Instrument * */ instrumentsListings(instrumentId, sourceId) { return this.observable({ listInstrumentListings: { instrumentId: instrumentId, sourceId: sourceId } }).pipe((0, rxjs_1.map)(state => (0, models_1.ListingdataWithIdAndQualityFromJSON)(state.dataListing))); } /** * Returns the AFU company profile */ afuCompanyProfile(instrumentId) { return this.promise({ getAfuCompanyProfile: { instrumentId: instrumentId } }).then(state => (0, models_1.AfuCompanyProfileFromJSON)(state.dataAfuCompanyProfile)); } // ############################################################################################# /** * Marketstates * information about listings, access to other data that is linked to a listing */ /** * create a MarketstateUpdate observable that can be subscribed to, contains both full state and delta for every marketstate */ marketstates(marketstateQueries, quality) { const full = new Map(); return this.marketstateUpdates(marketstateQueries, quality).pipe((0, rxjs_1.map)((update) => { let existingEntry = update.marketstateId ? full.get(update.marketstateId) : undefined; if (update.marketstateId) { if (existingEntry) { existingEntry = this.updateExistingMarketstateWithDeltaUpdate(existingEntry, update); } else { existingEntry = update; } full.set(update.marketstateId, existingEntry); } return new MarketstateUpdate_1.MarketstateUpdate(existingEntry, update); })); } /** * create a Marketstate for a snapshot request */ marketstateSnapshot(marketstateQueries, quality) { return this.observable({ listMarketstates: { marketstateQueries: marketstateQueries, quality: quality } }).pipe((0, rxjs_1.map)((x) => (0, models_1.MarketstateResponseSearchFromJSON)(x.dataMarketstate))); } /** * create a market state observable that can be subscribed to, contains only delta for every marketstate */ marketstateUpdates(marketstateQueries, quality) { return this.observable({ subscribeMarketstates: { marketstateQueries: marketstateQueries, quality: quality } }).pipe((0, rxjs_1.map)((x) => (0, models_1.MarketstateResponseSearchFromJSON)(x.dataMarketstate))); } /** * create a market state observable that can be subscribed to, contains only full state for every marketstate */ marketstatesStates(marketstateQueries, quality) { return this.marketstates(marketstateQueries, quality).pipe((0, rxjs_1.map)((x) => x.state)); } /** * Returns the Open-High-Low-Close Timeseries for the specified Instrument on the Source. * */ timeseries(marketstateId, resolution, start, end, cleanSplits, cleanDividends, cleanDistributions, cleanSubscriptions, quality) { return this.observable({ subscribeTimeseries: { resolution: resolution, marketstateId: marketstateId, start: start instanceof Date ? start?.toISOString() : start, end: end instanceof Date ? end?.toISOString() : end, cleanSplits: cleanSplits, cleanDividends: cleanDividends, cleanDistributions: cleanDistributions, cleanSubscriptions: cleanSubscriptions, quality: quality } }).pipe((0, rxjs_1.map)(state => (0, models_1.TimeseriesDataFromJSON)(state.dataTimeseries))); } /** * Returns the Open-High-Low-Close Timeseries for the specified Instrument on the Source. * */ timeseriesSnapshot(marketstateId, resolution, start, end, cleanSplits, cleanDividends, cleanDistributions, cleanSubscriptions, quality) { return this.observable({ listTimeseries: { resolution: resolution, marketstateId: marketstateId, start: start instanceof Date ? start?.toISOString() : start, end: end instanceof Date ? end?.toISOString() : end, cleanSplits: cleanSplits, cleanDividends: cleanDividends, cleanDistributions: cleanDistributions, cleanSubscriptions: cleanSubscriptions, quality: quality } }).pipe((0, rxjs_1.map)(state => (0, models_1.TimeseriesDataFromJSON)(state.dataTimeseries))); } /** * Returns the Orderbook for the specified Marketstate. * */ marketdepth(marketstateId, quality, orderBookVariant) { return this.observable({ subscribeMarketdepth: { marketstateId: marketstateId, quality: quality, orderBookVariant: orderBookVariant } }).pipe((0, rxjs_1.map)(state => (0, models_1.MarketdepthWithIdFromJSON)(state.dataMarketdepth))); } /** * Returns the performance for a specified listing of an instrument. */ performance(marketstateId, quality, ...timeRanges) { return this.observable({ listPerformance: { marketstateId: marketstateId, timeRange: timeRanges, quality: quality } }).pipe((0, rxjs_1.map)(state => (0, models_1.PerformanceDataForTimeRangeFromJSON)(state.dataPerformance))); } /** * Returns the bid/ask history for the specified Instrument on the Source. */ bidAskHistory(marketstateId, start, end, quality, offset, limit) { return this.observable({ listBidAskHistory: { marketstateId: marketstateId, start: start instanceof Date ? start?.toISOString() : start, end: end instanceof Date ? end?.toISOString() : end, quality: quality, offset: offset, limit: limit } }).pipe((0, rxjs_1.map)(state => { const bidaskHistory = (0, models_1.BidAskHistoryDataFromJSON)(state.dataBidAskHistory); bidaskHistory.totalResultCount = state.totalResultCount; return bidaskHistory; })); } /** * Returns the tick history for the specified Instrument on the Source. */ tickHistory(marketstateId, start, end, filterByVolume, quality, offset, limit) { return this.observable({ listTickHistory: { marketstateId: marketstateId, start: start instanceof Date ? start?.toISOString() : start, end: end instanceof Date ? end?.toISOString() : end, filterByVolume: filterByVolume, quality: quality, offset: offset, limit: limit } }).pipe((0, rxjs_1.map)(state => { const tickHistory = (0, models_1.TickHistoryDataFromJSON)(state.dataTickHistory); tickHistory.totalResultCount = state.totalResultCount; return tickHistory; })); } timeseriesBidAsk(marketstateId, resolution, start, end, quality) { return this.observable({ subscribeTimeseriesBidAsk: { resolution: resolution, marketstateId: marketstateId, start: start instanceof Date ? start?.toISOString() : start, end: end instanceof Date ? end?.toISOString() : end, quality: quality } }).pipe((0, rxjs_1.map)(state => (0, models_1.TimeseriesBidAskDataFromJSON)(state.dataTimeseriesBidAsk))); } /** * Returns the Open-High-Low-Close Timeseries for the specified Instrument on the Source. * */ timeseriesBidAskSnapshot(marketstateId, resolution, start, end, quality) { return this.observable({ listTimeseriesBidAsk: { resolution: resolution, marketstateId: marketstateId, start: start instanceof Date ? start?.toISOString() : start, end: end instanceof Date ? end?.toISOString() : end, quality: quality } }).pipe((0, rxjs_1.map)(state => (0, models_1.TimeseriesBidAskDataFromJSON)(state.dataTimeseriesBidAsk))); } /** * Returns the Orderbook for the specified Marketstate. * */ topflop(listId, sourcePriorities, quality, topSize, flopSize) { return this.observable({ subscribeListTopflop: { listId: listId, sourcePriorities: sourcePriorities, topSize: topSize, flopSize: flopSize, quality: quality } }).pipe((0, rxjs_1.map)(state => (0, models_1.TopflopListFromJSON)(state.dataTopflopList))); } // ############################################################################################# updateExistingMarketstateWithDeltaUpdate(existingEntry, update) { const existingJson = (0, models_1.MarketstateResponseSearchToJSON)(existingEntry); const updateJson = (0, models_1.MarketstateResponseSearchToJSON)(update); for (const key in updateJson) { if (updateJson[key]) { existingJson[key] = updateJson[key]; } } return (0, models_1.MarketstateResponseSearchFromJSON)(existingJson); } sendAsSoonAsAuthenticationPermits(request) { if (this.state.isAuthenticated || request.subscribeAuthentication) { this.websocket.send(JSON.stringify(request)); } else { this.waitingForAuthentification.push(request); } } stayAuthenticated() { const outer = this; new rxjs_1.Observable((subscriber) => { this.authdataCallback().then((mdsAuthdata) => outer.observable({ 'subscribeAuthentication': mdsAuthdata })).then((observable) => observable.subscribe({ next(x) { const seconds = x?.dataAuthentication?.secondsToLive; if (seconds && seconds > 30) { subscriber.next(x); for (const requestParameter of outer.waitingForAuthentification) { outer.websocket.send(JSON.stringify(requestParameter)); } outer.waitingForAuthentification = []; outer.state.authenticationAccepted(); } }, complete() { outer.state.authenticationEnded(); subscriber.complete(); }, error(e) { outer.error(e); outer.state.authenticationEnded(); subscriber.complete(); } })).catch((error) => { outer.error(error); outer.state.authenticationEnded(); subscriber.error(error); }); }).pipe((0, rxjs_1.retry)({ delay: 2000 }), (0, rxjs_1.repeat)({ delay: 2000 })).subscribe({ next(n) { outer.debug(`auth outer ${JSON.stringify(n)}`); }, error(e) { outer.error(`auth error ${JSON.stringify(e)}`); }, complete() { outer.debug('auth completed'); } }); } generateNextRequestId() { return 'request-' + this.nextGeneratedRequestId++; } observable(req) { const requestId = this.generateNextRequestId(); req.requestId = requestId; const outer = this; return new rxjs_1.Observable((subscriber) => { outer.runningRequests.set(requestId, RunningRequest_1.RunningRequest.withObservable(req, subscriber)); outer.sendAsSoonAsAuthenticationPermits(req); // Provide a way of canceling and disposing the resources return function unsubscribe() { const runningRequest = outer.runningRequests.get(requestId); if (runningRequest?.observableSubscriber) { outer.runningRequests.delete(requestId); outer.websocket.send(JSON.stringify({ cancel: { requestId: requestId } })); } }; }); } promise(req) { const requestId = this.generateNextRequestId(); req.requestId = requestId; const outer = this; return new Promise((resolve, reject) => { outer.runningRequests.set(requestId, RunningRequest_1.RunningRequest.withPromise(req, resolve, reject)); outer.sendAsSoonAsAuthenticationPermits(req); }); } processWebsocketMessageEvent(e) { if (e?.data) { const msg = JSON.parse(e.data); this.debug('we received ' + JSON.stringify(msg)); this.state.messageReceived(); if (msg.requestId) { const request = this.runningRequests.get(msg.requestId); if (request) { if (request.promiseResolve && request.promiseReject) { if (msg.isError) { request.promiseReject(msg.errorMessage); } else if (msg.isComplete) { request.promiseResolve(msg); } else { request.promiseReject(msg.errorMessage); } this.runningRequests.delete(msg.requestId); } else if (request.observableSubscriber) { if (msg.isError) { this.runningRequests.delete(msg.requestId); request.observableSubscriber.error(msg.errorMessage); } else if (msg.isComplete) { this.runningRequests.delete(msg.requestId); request.observableSubscriber.complete(); } else { request.observableSubscriber.next(msg); } } } } } } debug(s) { if (this.isDebug) console.log(s); } error(s) { console.error(s); } } exports.MdsConnection = MdsConnection;