@ariva-mds/mds
Version:
Stock market data
490 lines (489 loc) • 21.1 kB
JavaScript
"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;