Download the tutorial source code |
Click here to download |
---|---|
Last update | July 2024 |
Environment | Windows |
Compilers | Tutorial demonstration: Visual Studio 2022 |
Prerequisite | ETA Consumer - Retrieving and decoding level 1 content RTO Authentication Version 2 credentials |
Important Note: If you are using the Wealth solution (Pricing Streaming Optimized Service or Pricing Streaming Service), the products currently support Authentication Version 1 only (As of May 2024). Please contact your LSEG representative to verify if you are using Wealth or RTO solution.
This tutorial assumes you are familiar with consuming MarketPrice data from RTDS (LSEG Real-Time Distribution System) using ETA C. In this tutorial, we will modify the code in the ETA Consumer - Retrieving and decoding level 1 content to connect to LSEG Real-Time Optimized (RTO) using the Authentication Version 2 (aka Customer Identity and Access Management - CIAM, or Service Account) and consume MarketPrice data.
For further details about Migrating a ETA C API application from the Authentication Version 1 to Version 2, please check out the ETA C: LSEG Real-Time Optimized Version 2 Authentication Migration Guide article.
LSEG Real-Time – Optimized is LSEG real-time content, hosted in the public cloud. It provides fast and simple access to our unparalleled content from hundreds of exchanges and OTC markets around the world. The existing consuming applications can be easily modified to connect to RTO and retrieve market data.
To connect the LSEG Real-Time Optimized on Cloud, the service account (v2 authentication) is required. The service account contains:
To get the service account (v2 authentication), please contact the LSEG representative directly.
You can find more information about the Authentication Version 2 from the following resources:
There are three steps to modify and configure the existing ETA C applications to connect to RTO and retrieve market data.
A client Id and client secret must be set in the RsslReactorOMMConsumerRole structure.
First, the clientId, and clientSecret variables are specified in the basicConsumer.c to store the Client ID, and Client Secret respectively. The RTDS_SERVER_NAME and RTDS_SERVER_PORT preprocessor directives are removed.
/* Server/Provider connection details */
//Remove for RTO V2
//#define RTDS_SERVER_NAME "elektron"
//#define RTDS_SERVER_PORT "14002"
//RTO V2
char clientId[] = "<Client ID>";
char clientSecret[] = "<Client Secret>";
Then, in the init() function, declare the RsslReactorOAuthCredential’s variable that represents the OAuth credential for authorization with the token service. Next, declare the RsslBuffer variables to store the Client ID and Client Secret.
void init()
{
RsslError error;
RsslReactorOMMConsumerRole consumerRole;
RsslCreateReactorOptions reactorOpts;
RsslErrorInfo rsslErrorInfo;
//RTO V2
RsslReactorOAuthCredential oauthCredential;
RsslBuffer clientIdBuffer = RSSL_INIT_BUFFER;
RsslBuffer clientSecretBuffer = RSSL_INIT_BUFFER;
rsslClearReactorOAuthCredential(&oauthCredential);
clientIdBuffer.data = clientId;
clientIdBuffer.length = strlen(clientId);
clientSecretBuffer.data = clientSecret;
clientSecretBuffer.length = strlen(clientSecret);
...
...
Finally, set the RsslBuffer of clientId, and clientSecret to the RsslReactorOAuthCredential and then set the RsslReactorOAuthCredential to the RsslReactorOMMConsumerRole.
void init()
{
…
/* Load Dictionaries - local or server */
consumerRole.dictionaryMsgCallback = loadLocalDictionary() ? NULL : dictionaryMsgCallback;
consumerRole.dictionaryDownloadMode = (consumerRole.dictionaryMsgCallback ? RSSL_RC_DICTIONARY_DOWNLOAD_FIRST_AVAILABLE : RSSL_RC_DICTIONARY_DOWNLOAD_NONE);
//RTO V2
oauthCredential.clientId = clientIdBuffer;
oauthCredential.clientSecret = clientSecretBuffer;
consumerRole.pOAuthCredential = &oauthCredential;
...
...
RTO servers are encrypted servers available on cloud in different regions. Therefore, the location variable is specified in the basicConsumer.c to store the location.
//RTO V2
char clientId[] = "<Client ID>";
char clientSecret[] = "<Client Secret>";
char location[] = "ap-northeast-1";
The above code uses an "ap-northeast-1" region as an example location. You can optionally change this to test other regions. Please check with your LSEG representative. To retrieve a valid list of the supported regions, refer to the Currently Deployed Sites section outlined in the LSEG Real-Time - Optimized Install and Config Guide document.
Then, in the init() function, declare a RsslBuffer variable to store the location.
void init()
{
…
//RTO V2
…
RsslBuffer clientIdBuffer = RSSL_INIT_BUFFER;
RsslBuffer clientSecretBuffer = RSSL_INIT_BUFFER;
RsslBuffer locationBuffer = RSSL_INIT_BUFFER;
…
locationBuffer.data = location;
locationBuffer.length = strlen(location);
…
Next, modify the reactor connection options to use the RsslReactorConnectInfo, and then specify the location and encrypted socket connection type to the RsslReactorConnectInfo.
void init()
{
…
//RTO V2
RsslReactorOAuthCredential oauthCredential;
RsslBuffer clientIdBuffer = RSSL_INIT_BUFFER;
RsslBuffer clientSecretBuffer = RSSL_INIT_BUFFER;
RsslBuffer locationBuffer = RSSL_INIT_BUFFER;
RsslReactorConnectInfo cInfo;
rsslClearReactorConnectInfo(&cInfo);
…
// Prepare our connection properties
rsslClearReactorConnectOptions(&connectionOptions);
//connectionOptions.rsslConnectOptions.connectionInfo.unified.address = RTDS_SERVER_NAME;
//connectionOptions.rsslConnectOptions.connectionInfo.unified.serviceName = RTDS_SERVER_PORT;
//connectionOptions.rsslConnectOptions.guaranteedOutputBuffers = 500;
//connectionOptions.rsslConnectOptions.majorVersion = RSSL_RWF_MAJOR_VERSION;
//connectionOptions.rsslConnectOptions.minorVersion = RSSL_RWF_MINOR_VERSION;
//RTO V2
cInfo.rsslConnectOptions.connectionType = RSSL_CONN_TYPE_ENCRYPTED;
cInfo.rsslConnectOptions.encryptionOpts.encryptedProtocol = RSSL_CONN_TYPE_SOCKET;
cInfo.rsslConnectOptions.guaranteedOutputBuffers = 500;
cInfo.rsslConnectOptions.majorVersion = RSSL_RWF_MAJOR_VERSION;
cInfo.rsslConnectOptions.minorVersion = RSSL_RWF_MINOR_VERSION;
cInfo.location = locationBuffer;
cInfo.enableSessionManagement = RSSL_TRUE;
connectionOptions.reactorConnectionList = &cInfo;
connectionOptions.connectionCount = 1;
connectionOptions.reconnectAttemptLimit = -1; // Keep trying to reconnect upon failure
connectionOptions.reconnectMinDelay = 5000; // Reconnect interval (milliseconds)
connectionOptions.reconnectMaxDelay = 10000;
…
To connect to RTO, it requires the curl, crypto, and openssl libraries. The curl libraries are available in the RTSDK package. The crypto and openssl libraries can be download from their official websites.
Otherwise, you will see the following error messages when running the application.
Error rsslReactorConnect(): Failed to initialize RsslRestClient. Text: <D:\Jenkins\workspace\RTSDK_Core_DX1\OS\VS143-64\rcdev\source\rtsdk\Cpp-C\Eta\Impl\Transport\rsslSocketTransportImpl.c:516> Error: 0012 Unable to load CURL.
…
Connection down, reconnecting. Channel fd=-1
Error text: <D:\Jenkins\workspace\RTSDK_Core_DX1\OS\VS143-64\rcdev\source\rtsdk\Cpp-C\Eta\Impl\Transport\rsslSocketTransportImpl.c:456> Error: 0012 Unable to load openSSL Libraries.
On Windows machines, the crypto and openssl libraries could be:
Assuming you have properly set up the Visual Studio project, you can now compile and run the tutorial. If you encounter any compile errors, ensure your environment variables are properly defined. When you run the tutorial, you should see no errors, and the application will connect to RTO, send a subscription request, and display decoded market price at the command line.
Unable to load field dictionary. Will attempt to download from provider.
Error Text: Can't open file: 'RDMFieldDictionary'.
Unable to load enum type dictionary. Will attempt to download from provider.
Error Text: Can't open file: 'enumtype.def'.
Connection up! Channel fd=784
Received Login response: State: Open/Ok/None - text: "Login accepted by host ads-fanout-sm-az2-apne1-prd."
Received Source Directory Response: State: Open/Ok/None - text: ""
Received serviceName: ELEKTRON_DD. Service State: Up
Found your service ELEKTRON_DD using serviceId: 257
Received serviceName: PERM_SVR_SNKDRV. Service State: Up
Received serviceName: ERT_FD3_LF1. Service State: Up
Received Dictionary Response: RWFFld
Field Dictionary complete.
Received Dictionary Response: RWFEnum
Enumerated Types Dictionary complete.
Channel is ready. Subscribing to /LSEG.L
Received activity on channel: 784
Item: /LSEG.L
State: Open/Ok/None - text: ""
Domain: RSSL_DMT_MARKET_PRICE
PROD_PERM 9241
RDNDISPLAY 115
DSPLY_NAME LON.STK.EXCH/d
RDN_EXCHID LSE(64)
TRDPRC_1 <blank data>
TRDPRC_2 <blank data>
TRDPRC_3 <blank data>
TRDPRC_4 <blank data>
TRDPRC_5 <blank data>
NETCHNG_1 <blank data>
HIGH_1 <blank data>
LOW_1 <blank data>
PRCTCK_1 <blank data>
CURRENCY GBp(2008)
TRADE_DATE 23 JUL 2024
TRDTIM_1 <blank data>
OPEN_PRC <blank data>
HST_CLOSE 9454
BID 9000
ASK 10200
NEWS YYYY
NEWS_TIME 17:00:01:000:000:000
…
------------------------------------------------------------------------------------
If the application got the Access Denied status message, please change the subscribed RIC in the itemName variable.
Receive statusMsg for stream: 100
State: Closed/Suspect/Not entitled - text: "Access Denied: User req to PE(5625)"
For example, change the itemName varible to another delayed RIC.
char itemName[] = "/LSEG.L";
For more information, refer to the ETA C Development Guides.