Article

Migrating Eikon .NET API calls to the Workspace .Net Data Library

Authors:

Nick Zincone
Lead Developer Advocate Lead Developer Advocate

Note: Please keep in mind that this is a living document. Keep an eye on it as we update it with latest info and your feedback.

What are the Eikon .Net APIs

The Eikon .Net APIs are older APIs that were included with the Eikon desktop application providing access to real-time subscriptions, historical time-series and symbology conversion.  Below, you will find quick links to some of the common details outlining the migration APIs and how to access the same features including some of the differences when using the new LSEG Data Library for .NET.

Upgrading away from the Eikon .Net APIs

Target Audience

This product page, and the corresponding migration details only pertains to developers using the legacy .Net APIs available within the Eikon desktop offering. If you are using Python, COM, or Typescript, this is not relevant to you.  Instead, refer to the Upgrading to Workspace landing page to help navigate your upgrade path.

The Eikon .Net APIs are feature-complete and no new development work is being done on them. This means that new datasets and capabilities will not be available to users of these APIs. For access to such new capabilities, please migrate to Workspace.

Check if your code uses any of the Eikon .Net APIs by loading your C# project  within your Microsoft Visual Studio IDE and review the library packages used by your application.  Once you have loaded your application, you can refer to the project NuGet packages or the project References.  If it shows packages named ThomsonReuters, this migration guide is for you.

For example:

Advantages of migrating away from the Eikon .Net APIs:

  • Data Availability
    The Data Library for .Net provides access to a much larger content set available within the Workspace environment   
  • Modern .Net
    The Data Library for .Net is an active set of APIs built on top of modern, 64-bit based APIs.  
  • LSEG Workspace
    The Eikon .Net APIs will not be available within the Workspace environment

Prerequisites

  • IDE such as Visual Studio 2022 or Visual Studio for Code
    During your migration effort, you can test some of the examples outlined within this article within your IDE of choice.
  • Eikon .Net APIs example package
    The example code used to describe existing API calls within Eikon and what this guide references.
  • LSEG Data Library for .NET example package
    The example code used to describe future API calls within Workspace and what this guide references. 
  • Setup Review
    For simplicity and to avoid bloating the details with setup code, we have only presented the relevant API calls for the specific actions.  You can refer to the example packages for details related to setup and connection into the desktop.

Real-time data API

This section outlines the approaches to retrieve streaming market data from the desktop.  Both libraries provide the ability to subscribe to 1 or more instruments and receive streaming updates based on activity within the market.  In addition, we'll outline the ability to retrieve chains, a mechanism used to define a collection of instrument codes, or constituents, based on specific type of instrument, such as an index, e.g: Dow Jones. For a detailed description of chains, you can refer to this article within the Developer Community.

Eikon .Net APIs

What does 'DataService.Instance.Realtime' provide?

This functionality is used to get real-time, market data for selected instruments and optionally a list of fields.  When setting up a subscription, the user is required to provide a callback to capture updates.

The following is the basic setup:

    	
            

// Namespace reference
var realtime = DataServices.Instance.Realtime;

 

// Create a subscription to 1 or more instruments specifying fields of interest
var subscription = realtime.SetupDataSubscription()
                               .WithRics("EUR=", "GBP=")
                               .WithFields("DSPLY_NAME", "BID", "ASK")
                               .OnDataUpdated(DataCallback)
                               .CreateAndStart();

// To capture updates, create a reference to the callback
private void DataCallback(IRealtimeUpdateDictionary updates)
{
    // Iterate through all updates
    foreach (var update in updates)
    {
        Console.WriteLine($"Update for item: {update.Key}");
        
        // For each instrument, display the field values
        foreach (var value in update.Value)
        {
            Console.WriteLine($"\t{value.Key} => {value.Value.Value}");
        }
    }
}

In addition, the APIs support an interface to retrieve chains.

In the sample below, we have requested to retrieve the Nasdaq Top 25 and to monitor updates to the chain.

    	
            

// Namespace reference
var realtime = DataServices.Instance.Realtime;

 

// Create a subscription to the Nasdaq Top 25 chain (.AV.O)
var chainSubscription = realtime.SetupChainSubscription(".AV.O")
                                     .OnDataUpdated(ChainUpdatedCallback)
                                     .CreateAndStart();


// To capture updates, create a reference to the callback

private void ChainUpdatedCallback(ChainChunk chainChunk)
{
    // Iterate through the chain to capture each update
    foreach (var update in chainChunk.Updates)
    {
        // The following actions describe how a chain can be updated
        switch (update.UpdateType)
        {
            case UpdateType.Added:
                Console.WriteLine($"{update.Ric} added at position {update.Position}");
                break;
            case UpdateType.Removed:
                Console.WriteLine($"{update.Ric} removed");
                break;
            case UpdateType.Changed:
                Console.WriteLine($"{update.Ric} position changed to {update.Position}");
                break;
        }
    }

    if (chainChunk.IsLast)
    {
        Console.WriteLine("---------End of update---------");
        Console.WriteLine();
    }
}

LSEG Data Library for .NET

The LSEG Data Library for .Net provides an equivalent service allowing developers to register interest in 1 or more instruments and to receive real-time streaming updates representing trades and quotes.

What does 'Data.Content.Pricing' provide?

This functionality is used to get real-time, market data for selected instruments and optionally a list of fields.  When defining your pricing stream, the user provides a lambda (or callback) to capture updates.

The following is the basic setup:

    	
            

// Create a streaming price interface for a list of instruments
// specify lambda expressions to capture real-time events

using var stream = Pricing.Definition("EUR=", "GBP=")
                      .Fields("DSPLY_NAME", "BID", "ASK")
                      .GetStream().OnRefresh((item, refresh, s) => Console.WriteLine(refresh))
                                  .OnUpdate((item, update, s) => DisplayUpdate(item, update))
                                  .OnStatus((item, status, s) => Console.WriteLine(status));
stream.Open();

 

// Pause on the main thread while updates come in.  Wait for a key press to exit.
Console.WriteLine("Streaming updates.  Press any key to stop...");
Console.ReadKey();

 

// Based on market data events, reach into the message and pull out the fields of interest for our display.

private static void DisplayUpdate(string item, JObject update)
{
    var fields = update["Fields"];

    // Display the quote for the asset we're watching
    Console.WriteLine($"{ DateTime.Now:HH:mm:ss} Seqno: {update["SeqNumber"], -6} => {item}" +
                              $"({fields["BID"],6}/{fields["ASK"],6}) - {fields["DSPLY_NAME"]}");

}

In the above code segment, you can see a difference between the libraries when we included the OnRefresh() expression.  When requesting for streaming data using our modern streaming APIs, we intentionally separate the concept of the initial image and update images.  The OnRefresh() represents the initial image when requesting for market data.  This concept allows users to capture the latest prices for the image to be received within a separate callback to distinguish between updates.  Updates involve the concept of capturing real-time events based on changes in the markets, for example, trades and quotes.  The initial image will always contain all values/fields you request where updates will only provide fields that are affected by the event, i.e. a trade fields or a quote fields.

In addition, the APIs support an interface to retrieve chains.

In the sample below, we have requested to retrieve the Nasdaq Top 25 and to monitor updates to the chain.

    	
            

// Create a subscription to the Nasdaq Top 25 chain (.AV.O) specifying the lambda expressions
// to capture chain events
using var chain = Chain.Definition(".AV.O").GetStream()

                        .OnAdd((index, newv, stream) =>
                                Console.WriteLine($"New constituent {newv} added at: {index}"))
                        .OnRemove((index, oldv, stream) =>
                                Console.WriteLine($"Removed constituent {oldv} added at: {index}"))
                        .OnUpdate((index, oldv, newv, stream) =>
                                Console.WriteLine($"Index {index} changed from {oldv} => {newv}"));

chain.Open();

 

// Pause on the main thread while updates come in.  Wait for a key press to exit.
Console.WriteLine("Streaming updates.  Press any key to stop...");
Console.ReadKey();

Time Series API

The Time Series or Historical Pricing services available in both libraries provide the ability to retrieve time series bars in intervals such as 1-minutes, 5-minute, 30-minute, etc as well as interday intervals including daily, weekly, monthly, etc.  In addition, both libraries provide the facility of streaming pricing bars in real-time. However, there are subtle differences in the interfaces and features that we'll outline.

Eikon .Net APIs

What does 'ITimesSeriesDataService' provide?

This functionality is used to retrieve historical time-series bars as well as the ability to open subscriptions to update and insert new bars, in real-time.  The service segments the collection of historical fields within Views which define a set of related fields for a given asset type.  For example, users can request for an asset and define a specific View, such as the 'TRDPRC_1' view defining the OHLC (Open/High/Low/Close) view.  Or if they are interested in quotes (BID or ASK) related fields, they have the option of narrowing down their views to these specific related fields.  In addition, the service supports the ability to query this metadata to capture the available Views along with the intervals that support these views and some useful details related to the time range available for the different intervals.

The following demonstrates how to retrieve the last 10 bars:

    	
            

...

Console.WriteLine("[2] Time series request example");
Console.WriteLine("");

 

// Request for daily bars using the default View (OHLC details)
request = timeSeries.SetupDataRequest("VOD.L")
                .WithInterval(CommonInterval.Daily)
                .WithNumberOfPoints(10)
                .OnDataReceived(DataReceivedCallback)
                .CreateAndSend();

...

private void DataReceivedCallback(DataChunk chunk)
{
    foreach (IBarData bar in chunk.Records.ToBarRecords())
    {
         if (bar.Open.HasValue && bar.High.HasValue && bar.Low.HasValue && bar.Close.HasValue &&
             bar.Timestamp.HasValue)
         {
              Console.WriteLine(
                        "{0}: {1} OHLC {2} {3} {4} {5}",
                        bar.Timestamp.Value.ToShortDateString(),
                        chunk.Ric,
                        bar.Open.Value.ToString("##.0000"),
                        bar.High.Value.ToString("##.0000"),
                        bar.Low.Value.ToString("##.0000"),
                        bar.Close.Value.ToString("##.0000")
                    );
                };
           }
       }
    }
}

To support the ability to open a subscription to receive the historical bars but also keep track of new updates and bars inserted is available within the following code segment:

    	
            

...

Console.WriteLine("[3] Time series subscription example");
Console.WriteLine("");


// Request for 1-minute bars
subscription = timeSeries.SetupDataSubscription("VOD.L")
                            .WithInterval(CommonInterval.Intraday1Minute)
                            .WithNumberOfPoints(10)
                            .OnDataReceived(DataReceivedCallback)
                            .OnDataUpdated(DataUpdatedCallback)
                            .CreateAndStart();

...

private void DataReceivedCallback(DataChunk chunk)
{
     foreach (IBarData bar in chunk.Records.ToBarRecords())
          DisplayValues(chunk.Ric, bar, "Insert");


          if (chunk.IsLast)
                Console.WriteLine("------------------------------------------------");
}

 

private void DataUpdatedCallback(IDataUpdate dataUpdate)
{
      IBarData bar = dataUpdate.Records.ToBarRecords().FirstOrDefault();

      if (dataUpdate.UpdateType == UpdateType.NewPoint)
      {
           Console.WriteLine("===========INSERT==============");
           DisplayValues(dataUpdate.Ric, bar, "Insert");
      }
      else
      {
           DisplayValues(dataUpdate.Ric, bar, "Update");
      }
}

 

private void DisplayValues(string ric, IBarData bar, string label)
{
     if (bar.Open.HasValue && bar.High.HasValue && bar.Low.HasValue && bar.Close.HasValue &&
         bar.Volume.HasValue && bar.Timestamp.HasValue)
     {
          Console.WriteLine(
                    "{0} {1}: OHLC {2} {3} {4} {5} {6} Vol: {7}",
                    label,
                    bar.Timestamp.Value.ToLongTimeString(),
                    ric,
                    bar.Open.Value.ToString("##.0000"),
                    bar.High.Value.ToString("##.0000"),
                    bar.Low.Value.ToString("##.0000"),
                    bar.Close.Value.ToString("##.0000"),
                    bar.Volume.Value.ToString()
             );
       };
}

As expected, we received the historical bars requested - similar to the previous example. Because we opened a subscription, we can observe updates to the most recent bar.  However, changes do not show deltas based on the most recent bar, but instead the library presents all values.  Users will to need to compare with the latest bar to confirm which values have changed .  This is evident in the output where the entire update record includes all fields within the view.  And as expected, when we move to the next minute, a new bar is inserted into the collection followed by updates.  This trend continues.

LSEG Data Library for .NET

The Data Library for .Net provides an equivalent service allowing developers to request for historical bars but also register interest opening a stream to receive real-time updates to existing bars and the ability to deliver new bars as we saw within the Eikon .Net API.  However, there are some key distinctions between the 2 libraries:

  • Views
    The Data Library for .Net does not have the concept of Views nor does it provide the ability to request which fields are associated with which interval and the data range available.  The Data Library for .Net is based on the HPA (Historical Pricing API) that presents all available fields for the selected asset and interval.

  • Updates
    The Data Library for .Net delivers only those fields that have changed based on the market activity.  Specifically, users can easily observe the deltas much like what you would observe when you interface with a typical real-time service.

  • Built-in filtering
    The Data Library for .Net does not support built-in filtering.  It is the developers responsibility to extend any desired filtering when the raw data arrives.

What does 'Data.Content.HistoricalPricing' provide?

The HistoricalPricing interface provides the ability to request for historical bars based on the selected interval but also open a subscription to the request to retrieve updates and inserts based on market activity.  When defining your historical pricing stream, the user provides a lambda (or callback) to capture both inserts (new bars) or updates (to the most recent bar).

The following demonstrates how to retrieve the last 10 bars:

    	
            

// Retrieve Interday Summaries with P1D (1-day interval).  
var response = Summaries.Definition("VOD.L").Interval(Summaries.Interval.P1D)
                                            .Fields("OPEN_PRC", "HIGH_1", "LOW_1", "TRDPRC_1", "ACVOL_UNS")
                                            .Count(10)
                                            .GetData();

 

// Use common display routine to display response
Common.DisplayTable("Historical Interday Summaries", response);

The above request explicitly specifies the equivalent fields (OHLC) as we saw in the Eikon .Net API.  If no fields were specified, the service would return all available fields for the specified instrument/interval.  For the sake of readability, we have suppressed the common function of displaying the result to the console.  Users can review this code at their own leisure within the example package.

To support the ability to open a stream to receive the historical bars but also keep track of new updates and bars inserted is available within the following code segment:

    	
            

// Create a Historical Pricing stream - specifying the desired 'intraday' interval
var stream = Summaries.Definition("VOD.L").Fields("OPEN_PRC", "HIGH_1", "LOW_1", "TRDPRC_1", "ACVOL_UNS")
                                  .Interval(Summaries.Interval.PT1M)
                                  .Count(10)
                                  .GetStream();

 

// Specify the TSI lambda expressions to capture 'Insert' and 'Update' events
stream.OnInsert((data, stream) => Common.DisplayTable($"INSERT: {DateTime.Now}", data.Table))
      .OnUpdate((data, stream) => Common.DisplayTable($"UPDATE: {DateTime.Now}", data.Table))
      .OnStatus((status, stream) => Console.WriteLine($"Status: {status}"))
      .OnError((error, stream) => Console.WriteLine($"Error: {error}"))
      .Open();

Similar to the Eikon .Net API, we received the historical bars based on our request..  When we opened a stream, we can see a number of updates and based on the interval, Inserts representing new historical bars, in real-time.  However, we do not receive all fields for each update, only those fields that have been affected by the activity in the market.

Symbology API

When working with symbol conversion, the majority of use cases involves the ability to request for a 1:1 mapping from a symbol, in one of many common formats, to a best match of another.  For example convert an ISIN into a RIC.  However, symbol conversion can extend into the requirement of converting to all available matches.  That is, depending on the conversion, there may be a 1:many mapping.  For example, convert an ISIN into all available RICs for the given ISIN, as opposed to the best match.

In this section, we'll outline the approach used by both libraries.

Eikon .Net APIs

What does 'IDataServices.ReferenceData' provide?

This service provides the ability to request for 1 or more symbols of the same type to be converted to another type.  The supported types using this interface are ISIN, SEDOL, CUSIP, Ticker and RIC.  If the conversion involves a 1:many relationship, the result set will provide the entire list of converted results for the specific input symbol requested.  In addition, the response will also include a "best match", which is a common requirement when performing basic symbology mapping.

To demonstrate a simple request for a best match of both an ISIN and CUSIP to be mapped to a RIC, the following can be used:

    	
            

// Converting ISIN to RIC - best match

services.ReferenceData.RequestSymbols("US5949181045", SymbolType.Isin, SymbolsBestMatchResponse);

 

// Converting CUSIP to RIC - best match

services.ReferenceData.RequestSymbols("037833100", SymbolType.Cusip, SymbolsBestMatchResponse);

 

private void SymbolsBestMatchResponse(SymbolsResponse response)

{

    if (response.HasError)

    {

          Console.WriteLine(response.Error.Message);

    }

    else

    {

          var request = response.RequestedSymbols.ToArray();

          var result = response.Symbols.ToArray();

 

          for (int i = 0; i < response.Count; i++)

               Console.WriteLine($"{request[i]} => {result[i].ErrorMessage ?? result[i].BestMatch.Ric}");

    }

}

Extending the above example, we can take one of the conversions and instead provide a mapping of a symbol to all available RICs.  The following code simply pulls out the result collection as opposed to the best match:

    	
            

// Converting ISIN to RIC - Top 25

services.ReferenceData.RequestSymbols("US0378331005", SymbolType.Isin, SymbolsAllResponse);

 

private void SymbolsAllResponse(SymbolsResponse response)

{

    if (response.HasError)

    {

        Console.WriteLine(response.Error.Message);

    }

    else

    {

        var request = response.RequestedSymbols.ToArray();

        var result = response.Symbols.ToArray();

        var cnt = 25;

 

        for (int i = 0; i < response.Count; i++)

        {

            Console.WriteLine($"{request[i]} => {result[i].ErrorMessage ?? $"{cnt} results"}");

            

            foreach (var value in result[i].Rics.Take(cnt))

                Console.WriteLine($"\t{value}");

        }

    }

}

LSEG Data Library for .NET

The Data Library for .Net provides an equivalent service allowing developers to request for symbol mappings that include both a "best match" as well as the equivalent 1:many conversion results.  However, the Data Library for .Net uses separate interfaces to achieve the 2 distinct capabilities. 

The following interfaces are supported:

  • Data.Content.Symbology.SymbolConversion
    Provides a simple best match conversion supporting the ability to specify a mixed set of symbols to be converted to common symbols such as ISIN, SEDOL, CUSIP,  LipperID,  PermID, RIC and Ticker.

  • Data.Content.SearchService.Search
    A powerful search mechanism that includes the ability to pull out a 1:many mapping but also allows metadata to be pulled down and includes powerful filtering mechanisms to narrow down result sets.

What does 'Data.Content.Symbology.SymbolConversion' provide?

The SymbolConversion interface provides the ability to specify 1 or more symbols, of any type, to be mapped to 1 or more symbols using its "best match" algorithm.  The service automatically includes a description of the symbol for display/reference purposes.  As with the Eikon .Net API, the service also allows the explicit specification of 'from' symbol type, which can be used for validation purposes.

To demonstrate a simple request for a best match of both an ISIN and CUSIP to be mapped to a RIC, the following can be used:

    	
            

// Mixed symbols (ISIN, CUSIP) to RIC conversion - best match
var response = SymbolConversion.Definition().Symbols("US5949181045", "037833100")
                                            .ToSymbolType(SymbolConversion.SymbolType.RIC)

                                            .GetData();

Common.DisplayTable("Mixed symbols (ISIN, CUSIP) to RIC conversion - best match:", response);

What does 'Data.Content.Search.SearchService' provide?

The Search interface provides access to a powerful API accessing a wealth of content covering content such as quotes, instruments, organizations, and many other assets that can be programmatically integrated within your business Workflow.  As part of its capabilities, the service can be used to provide symbol conversion queries allowing for 1:many mapping results using various symbol types.  In addition, we also have the ability to pull down much more than the basic description and symbol mappings including metadata that may be useful describing the details of the mapping.  Because we are using the Search service, the filtering and details extracted can be endless.

It may be worthwhile to familiarize yourself with this powerful service as outline within this article: Building Search into your Application Workflow.

To demonstrate a request to provide all mappings for an ISIN to RIC conversion:

    	
            

// ISIN to RIC conversion - Top 25
// Note: Only show those results where the asset is 'AC' (Active)

var response = Search.Definition().Filter("IssueISIN eq 'US0378331005 and AssetState eq 'AC'")

                                  .Select("DocumentTitle, RIC")

                                  .Top(25)

                                  .GetData();

 

Common.DisplayTable("ISIN to RIC conversion - top 25:", response);

The above request contains a filter expression explicitly searching for all RICs associated with the ISIN value.  The filter expression offers quite a bit of power when choosing the collection of hits and offers total control over the ability to filter unwanted results by expanding the expression.  Because of the power offered within the service, users can easily extend the capability to cover additional symbol mapping expressions.  For example, we could have instead chose to return all RICs associated with other popular symbol mapping requirements.  For example:

Mapping Filter Expression
CUSIP "CUSIP eq '037833100' and AssetState eq 'AC'"
SEDOL "SedolSecondaryQuoteValue eq 'BH4HKS3 and AssetState eq 'AC''
TICKER ""TickerSymbol xeq 'IBM' and AssetState eq 'AC'"

So long as the data is available, we can build powerful expressions to pull out very specific data mappings.  In addition, choose alternative output fields beyond the simple description of the match.

Conclusion

Moving from your Eikon .Net API to the LSEG Data Library for .NET should provide you with a path to acquire the desired content within your applications.  As we continue to improve and expand our content set, we anticipate there may be some differences.  The goal of this article is to provide solutions that not only meet your needs but hopefully include additional value not present with the legacy platform.   Please report any issues or challenges within our Q&A site using the tag: workspace-upgrade and we'll continue to update this article to do our best to ensure we can provide a suitable migration path.