Skip to content

Market Data API

Start receiving market data callbacks in these 4 steps:

  1. Initialize a callback listener object
  2. registerApplication with the listener
  3. subscribe to desired instruments
  4. start nanoconda threads

Tip

Please refer to the Sample Application for demonstration. The sample application is the best starting point for your own algo.

Callbacks

Callback Listener

As market data arrives from the exchange it gets normalized and appropriate callback is raised. All our nanoconda market data objects are simple structs and you can directly access any field.

In order to receive callback updates user must implement a listener class and inherit nanoconda::listener

nanoconda::listener
struct myListener : nanoconda::listener
{
   void onsecurity(const nanoconda::security* s) {  };
   void onbook(const nanoconda::book* b) { };
   void ontrade(const nanoconda::trade* t) {  };
   void onbookorder(const bookorder* o) { };
   /* 
   ...
   */
}

Security Information

onsecurity() callback is called one time on any subscription and then for any update from the exchange to the security, usch as a trading status change.

void onsecurity(const nanoconda::security* s) { };
nanoconda::security structure
   struct security
   {
      char symbol[24];
      char reservedC1[8];
      unsigned long long symbolId;
      unsigned long long exchangeSecurityId;
      unsigned long long tickSize;
      unsigned long long multiplier;
      unsigned long long underlyingId;
      long long lowLimitBand;
      long long highLimitBand;
      securityStatus status;
      char entCode[6];
      char highPrecisionPrice;
      unsigned char segmentId;
      securityType type;
      unsigned char legs;
      char reserved[4];
      long long settlementPrice;
      int openInterest;
      int clearedVolume;
      long long reservedL2;
      static inline unsigned short size() 
      { 
         return 128;
      }

   };

Book Snapshot

Book updates are provided on any book update from the exchange. Snapshot of the top 10 levels is provided. Price level 0 is the top level for both sells and buys

void onbook(const nanoconda::book* b) {  };
nanoconda::book structure
struct pricelevel
{
   long long price;
   unsigned int qty;
   unsigned int orders;
   static inline unsigned short size() 
   { 
      return 16;
   }
};

struct book
{
   unsigned long long symbolId;
   unsigned long long exchangetime;
   unsigned long long receivetime;
   unsigned long long symbolseqno;
   unsigned long long writetime;
   unsigned long long writeseqno;
   unsigned long long reserved[2];
   pricelevel buys[10];
   pricelevel sells[10];
   static inline unsigned short size() 
   { 
      return 384;
   }
};

Book Order Update

When enabled, individual order updates are provided through the onbookorder() callback. This feature enables Level 3 (L3) processing by the algorithm, such as constructing the full order book or identifying order-level changes between two onbook() callbacks.

void onbookorder(const bookorder* o) { };
nanoconda::bookorder structure
enum orderupdatetype : unsigned char
{
   OU_UNKNOWN=0,
   OU_ADD=1,
   OU_DEL=2,
   OU_UPD=3,
   OU_FILL=4,
   OU_ADD_SNAPSHOT=5
};

struct bookorder
{
   unsigned long long symbolId;
   unsigned long long orderId;
   unsigned long long transactiontime;
   unsigned long long exchangetime;
   long long price;
   unsigned int quantity;
   orderupdatetype updatetype;
   char side;
   unsigned char lastEvent;
   unsigned char aggressing;
   unsigned long long receivetime;
   unsigned long long writetime;
   bookorder()
   {
      reset();
   }

   void reset();
   static inline unsigned short size() 
   { 
      return 64; 
   }
};

Trade Event

void ontrade(const nanoconda::trade* t) {  };
nanoconda::trade structure
struct trade
{
   unsigned long long symbolId;
   unsigned long long exchangetime;
   unsigned long long receivetime;
   unsigned long long symbolseqno;
   unsigned long long writetime;
   unsigned long long transactiontime;
   char aggressor;
   unsigned char lastEvent;
   char reserved[2];
   unsigned int numOrders;
   long long low;
   long long high;
   pricelevel bestbid;
   pricelevel bestask;
   long long lastPrice;
   long long vwap;
   unsigned int lastSize;
   unsigned int totalVol;
   long long open;
   static inline unsigned short size()
   {
      return 136;
   }
};

API functions

Additionally, several API calls are available to the user.

Note

API Calls return nanoconda::reasoncode to notify the user if the call was successful or if there was an error. It is users responsibility to handle any errors returned. Please refer to Reason Codes for explanation of each code and to Sample Application for an example on how to handle the codes.

List Available Symbols

If A list of available symbols is required, you can call listSymbols(), which will trigger onsecurity() callbacks for each of available security

   reasoncode listSymbols(const char* entitlement);

Subscribe To A Symbol

Call subscribe() to subscribe to market data updates for a specific symbol

reasoncode subscribe(const char* symbol, const char* entitlement, short cpu = -1);

//Example: nanoconda::subscribe("ESM5", "XCME", 3); 

Subscribe To An Underlying

Call subscribeUnderlying() to subscribe to market data updates for all available symbols for an underlying.

reasoncode subscribeUnderlying(const char* symbol, const char* entitlement, short cpu = -1);

//Example: nanoconda::subscribeUnderlying("NQ", "XCME", 3); 

Helper Functions

Convert price to double

   double nacoPx(long long px);

Get Symbol ID from a Symbol Name

   unsigned long long getSymbolId(const char* symbol);

Get Symbol Name As String

   const char* getSymbolName(unsigned long long symbolId);

Get Security Object

   security* getSecurity(unsigned long long symbolId);

Get Spread Security Legs

To identify the individual legs of a complex instrument, use the getSecurityLegs API and provide the spread's symbolId.

   reasoncode getSecurityLegs(unsigned long long symbolId, securitylegs* securitylegsstorage);
nanoconda::leginfo and nanoconda::securitylegs
   struct leginfo
   {
      unsigned long long symbolId;
      unsigned char ratio;
      char side;
      unsigned short reservedUS;
      unsigned int multiplier;

   };

   struct securitylegs
   {
      enum : unsigned short { MAX_SPREAD_LEGS = 48 };

      unsigned int legCount;
      unsigned int reserved;
      leginfo legs[MAX_SPREAD_LEGS];

   };
example
   nanoconda::securitylegs legs;
   nanoconda::reasoncode rcode = nanoconda::getSecurityLegs(symbolId, &legs);
   if(rcode == SUCCESS)
   {
      for (int i=0; i< legs.legCount;i++)
      {
         printf("Leg %d: symbolId %llu symbol %s ratio %d side %c multiplier %d\n",i, legs.legs[i].symbolId, nanoconda::getSymbolName(legs.legs[i].symbolId), legs.legs[i].ratio, legs.legs[i].side, legs.legs[i].multiplier);

      }
   }
   else
   {
      printf("getSecurityLegs for %llu returned code %u %s\n", symbolId, rcode, reasonToStr(rcode));
   }

Sample Application

Here is a basic program to subscribe to common Index Futues on CME (can also be found in src/marketdata.cpp).

Tip

Code is annotated, you can click on (1) icon to view additional information.

  1. You found on a sample annotation, now look for the same in the code below!
marketdata.cpp
/* Nanoconda 2025 | marketdata.cpp | g++ marketdata.cpp -lnanoconda */
#include "nanoconda.h"
#include <iostream>

using namespace nanoconda;
struct myListener : nanoconda::listener
{
   void onsecurity(const nanoconda::security* s) { // (6) callback with security information
      printf("myListener::onsecurity %s| symbolId: %llu ExchangeId: %llu TickSize: %f ContractMultiplier: %llu Trading Status: %u\n", s->symbol, s->symbolId, s->exchangeSecurityId, nacoPx(s->tickSize), s->multiplier, s->status); 
      if(s->type == nanoconda::TP_SPREAD)
      {
         nanoconda::securitylegs legs;
         nanoconda::reasoncode rcode = nanoconda::getSecurityLegs(s->symbolId, &legs);
         if(rcode == SUCCESS)
         {
            for (int i=0; i< legs.legCount;i++)
            {
               printf("Leg %d: symbolId %llu symbol %s ratio %d side %c multiplier %d\n",i, legs.legs[i].symbolId, nanoconda::getSymbolName(legs.legs[i].symbolId), legs.legs[i].ratio, legs.legs[i].side, legs.legs[i].multiplier);

            }
         }
         else
         {
            printf("getSecurityLegs for %llu returned code %u %s\n", s->symbolId, rcode, reasonToStr(rcode));
         }
      }

   };
   void onbook(const nanoconda::book* b) {// (7) callback for a book update

      printf("myListener::onbook %s| [%u,%.4f | %.4f,%u] time: %llu\n", nanoconda::getSymbolName(b->symbolId), b->buys[0].qty, nacoPx(b->buys[0].price), nacoPx(b->sells[0].price), b->sells[0].qty, b->exchangetime);

   };
   void ontrade(const nanoconda::trade* t) {// (8) callback for a new trade

          printf("myListener::ontrade %s, %.4f|%d\n",nanoconda::getSymbolName(t->symbolId), nacoPx(t->lastPrice), t->lastSize);
   };

   void onorderack(const nanoconda::order* order) { };
   void onorderreject(const nanoconda::order* order) { };
   void oncancelack(const nanoconda::order* order) { };
   void oncancelreject(const nanoconda::order* order) { };
   void onmodifyack(const nanoconda::order* order) { };
   void onmodifyreject(const nanoconda::order* order) { };
   void onfill(const nanoconda::order* order) { };
   void onout(const nanoconda::order* order) { };
   void onorderstatus(const nanoconda::order* order) { };
};

int main(int argc, char *argv[])
{
   myListener* mdApp = new myListener();
   nanoconda::logger* mylogger = new nanoconda::logger("myLoggerFile.log");

   nanoconda::reasoncode rcode; 

   rcode = nanoconda::registerApplication("username1","password", mdApp); //(1) register your identity and Callback Listener

   if(rcode != nanoconda::SUCCESS) return rcode; // Exit program with the error code

   rcode = nanoconda::subscribe("ESM5", "XCME", 3); //(2) subscribe to ESM5 Futures on cpu #3

   if(rcode != nanoconda::SUCCESS) return rcode; // Exit program with the error code

   rcode = nanoconda::subscribeUnderlying("NQ", "XCME", 3); //(3) subscribe to ESM5 Futures on cpu #3

   if(rcode != nanoconda::SUCCESS) return rcode; // Exit program with the error code

   nanoconda::start(); //(4) start callback threads to start receiving updates

   bool running = true;
   while(running) //(5) keep your main application thread alive until running becomes false or you kill the process
   {
   }

   nanoconda::stop(); //cleanup resources

   return 0;
}
  1. Register your application with your listener and credentials, which are needed to identify your application. They will be provided by support@nanoconda.com.
  2. Add subscription to ESM5 Futures with XCME entitlement code, your credentials must be provisioned for this entitlement.
    Method Signature:
    reasoncode subscribe(const char* symbol, const char* entitlement, short cpu = -1);
    Refer to threading for more information on how to control nanoconda threads.
  3. Add subscription to NQ underlying chain. This call will subscribe to all NQ Futures, regardless of expiration.
  4. Once start() is called your application will start receiving callbacks for the subscribed instruments.
  5. Keep your main thread running for application to not be terminated, you can add your own logic to set terminate the loop, for example use SIGINT to set running = false .

  6. This is an example of printing information about a product. onsecurity() will be called upon subscription and then if there is a change in security status from the exchange.

    From nanoconda.h
       enum securityStatus : unsigned short
       {
          HALT = 0,
          CLOSE = 200,
          POST_CLOSE = 220,
          PRE_OPEN = 120,
          OPEN = 100
       };
       //...
       struct security
       {
          char symbol[32];
          unsigned long long symbolId;
          unsigned long long exchangeSecurityId;
          unsigned long long tickSize;
          unsigned long long multiplier;
          unsigned long long underlyingId;
          long long lowLimitBand;
          long long highLimitBand;
          securityStatus status;
          char entCode[6];
          char highPrecisionPrice;
          unsigned char segmentId;
          char reserved[6];
          long long settlementPrice;
          int openInterest;
          int clearedVolume;
          long long reservedL2;
          static inline unsigned short size() 
          { 
             return 128;
          }
    
       };
    

  7. This is an example of printing the top level of a onbook() update. You have access to top 10 levels. This callback will be raised on any book update.
    From nanoconda.h
       struct pricelevel
       {
          long long price;
          unsigned int qty;
          unsigned int orders;
          static inline unsigned short size() 
          { 
             return 16;
          }
       };
       //...
       struct book
       {
          unsigned long long symbolId;
          unsigned long long exchangetime;
          unsigned long long receivetime;
          unsigned long long symbolseqno;
          unsigned long long writetime;
          unsigned long long writeseqno;
          unsigned long long transactiontime;
          unsigned char lastEvent;
          char reserved[7];
          pricelevel buys[10];
          pricelevel sells[10];
          pricelevel buysImplied[2];
          pricelevel sellsImplied[2];
          static inline unsigned short size() 
          { 
             return 400;
          }
       };
    
  8. This is an example of printing information for the last trade update. ontrade() callback will be raised for every trade.
    From nanoconda.h
       struct pricelevel
       {
          long long price;
          unsigned int qty;
          unsigned int orders;
          static inline unsigned short size() 
          { 
             return 16;
          }
       };
       //...
       struct trade
       {
          unsigned long long symbolId;
          unsigned long long exchangetime;
          unsigned long long receivetime;
          unsigned long long symbolseqno;
          unsigned long long writetime;
          unsigned long long transactiontime;
          char aggressor;
          unsigned char lastEvent;
          char reserved[6];
          long long low;
          long long high;
          pricelevel bestbid;
          pricelevel bestask;
          long long lastPrice;
          long long vwap;
          unsigned int lastSize;
          unsigned int totalVol;
          long long open;
          static inline unsigned short size() 
          { 
             return 136; 
          }
       };
    

Compile your application

g++ marketdata.cpp -lnanoconda

Administrative Callbacks

The feed handler provides several administrative callbacks that notify the application about the technical state of market data processing and book integrity. Applications must use these callbacks to correctly manage local order book state and recovery behavior.

Gap Notification

If the feed handler encounters an unrecoverable sequence gap while processing network data, the ongap() callback is triggered.

void ongap(const security* s) { };

A gap indicates that one or more market data messages could not be recovered via retransmission and therefore the integrity of the locally maintained order book can no longer be guaranteed.

Upon receiving ongap(), the application must immediately discard or clear all locally maintained book state for the affected instruments. Continuing to use the existing book after a gap may result in incorrect trading decisions.

After a gap is detected, the feed handler will automatically initiate a snapshot recovery sequence.

Snapshot Notification

Whenever the feed handler starts, or after a gap recovery is required, it retrieves full snapshot data from the exchange in order to rebuild the complete order book.

The beginning of a snapshot recovery sequence is indicated by the onsnapshotstart() callback.

The completion of the snapshot recovery sequence is indicated by the onsnapshotend() callback.

void onsnapshotstart(const security* s) { };
void onsnapshotend(const security* s) { };
During the snapshot phase, the feed handler reconstructs the order book using snapshot messages received from the exchange.

Note

Individual book order updates received during the snapshot phase will be delivered with updateType = OU_ADD_SNAPSHOT. If the application maintains its own internal order book, these updates must be applied as standard order insertions.

Live Notification

While snapshot data is being processed, the feed handler may internally cache incremental market data messages received from the exchange.

After the snapshot sequence completes, the feed handler will replay all cached incremental updates to bring the book state fully up to date.

Once all cached messages have been processed and the feed handler is ready to resume normal real-time market data processing, the onlive() callback is triggered.

void onlive(const security* s) { };

Receipt of onlive() indicates that:

  • The order book is fully synchronized with the exchange
  • All recovery processing has completed
  • Normal trading logic and decision making may safely resume

Feed Handler Lifecycle

The feed handler transitions through several operational phases around market close and pre-open.

Applications that manually build internal order books from individual orders must use these callbacks to maintain a correct and consistent book state.


Startup Phase

When the feed handler starts, the order book is rebuilt using the exchange snapshot loop. After snapshot completion, cached incremental updates are applied and the handler transitions into live processing.

Callback Sequence

onsnapshotstart()

   onbookorder(OU_ADD_SNAPSHOT) ---> Orders received from CME snapshot loop
   ...

onsnapshotend()

   onbookorder(OU_ADD / OU_DEL / OU_UPD) ---> Cached delta updates applied
   ...

onlive()

   onbookorder(OU_ADD / OU_DEL / OU_UPD) ---> Live incremental updates

Client Responsibilities

  • The order book must be treated as under reconstruction between onsnapshotstart() and onsnapshotend().
  • Cached delta updates must be applied immediately after snapshot completion.
  • The order book should only be considered fully synchronized after onlive() is received.
  • Trading logic that depends on full book integrity should only activate after onlive().

Book Integrity Guidelines

  • The order book is not guaranteed to be valid during snapshot reconstruction.
  • The order book becomes incrementally consistent after cached deltas are applied.
  • The order book becomes fully synchronized only after onlive() is received.

Market Close Snapshot Cycling

When the market transitions into the CLOSE state, the feed handler can be configured to repeatedly provide the full list of active book orders via snapshot loops.

This feature allows client applications to restart prior to the next trading session and retrieve the complete set of individual resting orders required to rebuild their internal order book.

Since no new trading activity is permitted during the CLOSE state, synchronizing the internal book with the exchange becomes deterministic and straightforward.

Callback Sequence

onsecurity(CLOSE)

onsnapshotstart()

onbookorder(OU_ADD_SNAPSHOT)
onbookorder(OU_ADD_SNAPSHOT)
...

onsnapshotend()

onsnapshotstart()

onbookorder(OU_ADD_SNAPSHOT)
onbookorder(OU_ADD_SNAPSHOT)
...

onsnapshotend()

...

This snapshot cycling may continue throughout the closed session.

When the market transitions into PRE_OPEN, the handler resumes live incremental processing.

onsecurity(PRE_OPEN)

onbookorder(OU_ADD / OU_DEL / OU_UPD) ---> Live incremental updates

Client Responsibilities

  • Snapshot cycling during the closed session is can be enabled with snapshot-during-close="true" Docs
  • The client should process only the first snapshot rebuild loop after receiving onsecurity(CLOSE).
  • After the first onsnapshotstart()onsnapshotend() cycle completes, the client should:

    • Treat the resulting order book as a frozen reference state.
    • Ignore subsequent OU_ADD_SNAPSHOT updates while the instrument remains in the CLOSE state.
  • The client must not assume book stability or trading readiness during repeated snapshot phases.

  • Normal live incremental processing resumes once the instrument transitions into PRE_OPEN.

Book Integrity Guidelines

  • During the CLOSE state, snapshot loops provide reference state only and should not be treated as live market state.
  • During the CLOSE state, snapshot loops allow for clients who use full L3 / MBO data to restart their application and get the current book state.

Book Integrity Table

Phase Book Source Book Integrity Trading Readiness
Snapshot rebuild Snapshot loop Not current No
Delta apply Cached incremental updates Improving No
Live mode Incremental updates Current Yes
Market closed snapshot cycling Snapshot loop Reference only N/A