Price Feed WebSocket API Specs
PROGRAMMING GUIDE
Copyright © 2024
Introduction
WebSocket API for Market Data is a real-time data feed and used to trade at the system.
Through this document, it’s assumed that you have a general knowledge on WebSocket STOMP communication.
Technical Support
Please contact devcenter@koala.markets for all kind of technical support and troubleshooting inquiries.
General Considerations
Time Zone
The system uses Coordinated Universal Time (UTC). You should handle any time zone conversions that may be required on your side.
Payload Security
The host communicates using SSL-encrypted TCP sockets. No special SSL certificates needed for now. Client can use an SSL enabled FIX initiator/client or use SSL tunneling. The SSL-tunneling functionality is provided by freely available open source products. In case of additional support or consultancy regarding SSL tunneling, you can contact our technical support team.
IP Filtering
Clients are identified by their IP addresses at the system. To connect it, following prerequisites should be met:
- Static IP set of the client should be cleared at the host.
- Client should use provided host address and port.
Credentials
In order to connect to WebSocket API host, client must already have API user in order to obtain access token as Bearer in the Authorization header.
Instrument Identification
The system Supports below for identifying an instrument between client and host:
- Symbol: Common, "human understood" representation of the security.
- ISIN: International Securities Identification Number (ISIN) which uniquely identifies a security. Its structure is defined in ISO 6166
Please contact our technical support team for the latest list of instruments that’re available for trading.
Price Feed Session Times
Currently the system provides securities only from Börse Düsseldorf (Quotrix). Börse Düsseldorf working hours for price feeds and trading sessions are as follows:
Price Feed Sessions:
Monday – Friday: 07:00 – 22:05 Germany Time
Retrieve Price Feed Messages from WebSocket
Session Flow
- WebSocket API Client subscribes to price data topic for Market Data
- WebSocket API will respond initially with Market Data as a
PriceFeedQuoteobject - If a subscription request is supplied, a stream of market data refresh updates will be initiated.
- API Client can un-subscribe from the WebSocket of the pricing session.
Market Data (PriceFeedQuote)
Market Data is in the following object format:
export class PriceFeedQuote {
// ISIN of the instrument
public isinCode: string;
// The provider of this data
public provider: string;
// Whether this instrument is trading or not
public status: string;
// Bid price
public bid: number;
// Bid volume
public bidVolume: number;
// Ask price
public ask: number;
// Ask volume
public askVolume: number;
// DateTime of this price
public dateTime: string;
}
Subscribing to WebSocket Market Data Topic
Url for the PriceFeed WebSocket API:
wss://price-feed-test.koala.markets/web-socket
Topic to subscribe to retrieve PriceFeed Market Data in the PriceFeedQuote format
wss://price-feed-test.koala.markets/web-socket/topic/quotes/get
Example: Subscribing to PriceFeed WebSocket Market Data Topic - Angular
Dependencies in angular.json
"dependencies": {
...
"@angular/core": "~13.2.4",
...
"@stomp/ng2-stompjs": "^8.0.0",
"angular-auth-oidc-client": "12.0.3",
...
}
The following is an example component/page
import { Component, Injectable, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { Observable, Subscription } from "rxjs";
import { map } from 'rxjs/operators';
import { RxStompState } from '@stomp/rx-stomp';
import { Message } from '@stomp/stompjs';
// https://github.com/stomp-js/ng2-stompjs
import { RxStompService } from '@stomp/ng2-stompjs';
// https://github.com/damienbod/angular-auth-oidc-client
import { OidcSecurityService } from 'angular-auth-oidc-client';
@Injectable()
export class PriceFeedQuote {
// ISIN of the instrument
public isinCode: string;
// The provider of this data
public provider: string;
// Whether this instrument is trading or not
public status: string;
// Bid price
public bid: number;
// Bid volume
public bidVolume: number;
// Ask price
public ask: number;
// Ask volume
public askVolume: number;
// DateTime of this price
public dateTime: string;
}
// Define our WebSocket service configuration class
export const myRxStompConfig: InjectableRxStompConfig = {
// Which server?
brokerURL: "wss://price-feed-test.koala.markets/web-socket", // such as 'ws://localhost:8080/web-socket',
// Headers
// Typical keys: login, passcode, Authorization
connectHeaders: {}, // { Authorization: 'Bearer ' + 'my_jwt_access_token' },
// How often to heartbeat?
// Interval in milliseconds, set to 0 to disable
heartbeatIncoming: 0, // Typical value 0 - disabled
heartbeatOutgoing: 0, // Typical value 20000 - every 20 seconds
// Wait in milliseconds before attempting auto reconnect
// Set to 0 to disable
// Typical value 5000 (5 seconds)
reconnectDelay: 5000
// Will log diagnostics on console
// It can be quite verbose, not recommended in production
// Skip this key to stop logging to console
//debug: (msg: string): void => {
// console.log(new Date(), msg);
//}
};
@Component({
selector: 'app-trading-price-stock-with-stomp',
templateUrl: './price-stock-websocket-with-stomp.component.html',
styleUrls: ['./price-stock-websocket-with-stomp.component.sass']
})
export class TradingPriceStockWithStompComponent implements OnInit, OnDestroy {
AuthorizationHeader: string;
private topicSubscriptionPriceFeedQuotesGet: Subscription;
private topicSubscriptionPriceFeedQuotesCreated: Subscription;
private topicSubscriptionPricesGetBySymbol: Subscription;
public connectionStatus$: Observable<string>;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Instrument Price Feed
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
constructor(
private oidcSecurityService: OidcSecurityService,
public rxStompService: RxStompService
)
{
}
ngOnInit(): void {
// Price Feed WebSocket service activation
this.activateWebSocketWithStomp();
// Subscribe to PriceFeed WebSocket Market Data topic
this.subscribeToPriceFeedWebSocketTopics();
}
ngOnDestroy() {
// IMPORTANT: We have to un-subcribe from the topic and deactive the WebSocket connection
this.unSubscribeFromPriceFeedWebSocketTopicsAndDestroy();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Instrument Price Feed
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
activateWebSocketWithStomp() {
// https://pretagteam.com/question/is-it-possible-to-catch-unauthorized-error-through-websocket-via-stompjs-ng2stomp
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// initialize the rxStompService for connection with JWT access_token obtained from OIDC Authorization Server (IdentitiyServer) //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// create an empty headers object
const connectHeaders = {};
this.AuthorizationHeader = "Bearer" + " " + this.oidcSecurityService.getAccessToken();
// make that CSRF token look like a real header
connectHeaders["Authorization"] = this.AuthorizationHeader;
// put the CSRF header into the connectHeaders on the config
const config = { ...myRxStompConfig, connectHeaders: connectHeaders };
// configure the actual websocket service
this.rxStompService.configure(config);
// Activate the service
this.rxStompService.activate();
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
deActivateWebSocketWithStomp() {
this.rxStompService.deactivate();
}
subscribeToPriceFeedWebSocketTopics() {
// optional: subscribe connectionState status for showing on the UI
this.connectionStatus$ = this.rxStompService.connectionState$.pipe(
map(state => {
// convert numeric RxStompState to string
return RxStompState[state];
})
);
// subscribe to a topic, which will start a websocket connection if not connected
this.topicSubscriptionPriceFeedQuotesGet = this.rxStompService
.watch('/topic/quotes/get', { "Authorization": this.AuthorizationHeader })
.subscribe((message: Message) => {
// console.log("ngOnInit() /topic/quotes/get", message);
let incomingData: PriceFeedQuote[] = JSON.parse(message.body);
let priceFeedQuotesTemp: PriceFeedQuote[] = incomingData.sort((a: PriceFeedQuote, b: PriceFeedQuote) => a.isinCode.localeCompare(b.isinCode));
// use this data as you like
});
}
unSubscribeFromPriceFeedWebSocketTopicsAndDestroy() {
this.rxStompService.deactivate();
// unscubscribe from websocket topics
if (this.topicSubscriptionPriceFeedQuotesGet) {
this.topicSubscriptionPriceFeedQuotesGet.unsubscribe();
this.topicSubscriptionPriceFeedQuotesGet = undefined;
}
if (this.topicSubscriptionPricesGetBySymbol) {
this.topicSubscriptionPricesGetBySymbol.unsubscribe();
this.topicSubscriptionPricesGetBySymbol = undefined;
}
}
}