Market Data Subscription

To receive market data for resolved contract(s) there is a market_data_2.MarketDataSubscription client message.

Basic subscription

Client message:

{
   "market_data_subscriptions": [
      {
         "contract_id": 1,
         "level": 4,
         "request_id": 1
      }
   ]
}

contract_id is taken from previously received metadata_2.SymbolResolutionReport message for a symbol or any other metadata response providing metadata_2.ContractMetadata. Note: contract_id value does not persist between sessions and should be re-resolved using appropriate client message such as metadata_2.SymbolResolutionRequest

request_id is a required field that is used for further identification of incoming status responses. This is because different server responses for the same contract_id may refer to different request_id’s. As well to drop existing subscription both contract_id and request_id are required.

Server response:

{
  "market_data_subscription_statuses": [
    {
      "contract_id": 1,
      "status_code": 0,
      "level": 7,
      "past_quotes_included": false,
      "yields_included": false,
      "session_market_values_included": false,
      "source_price_included": false,
      "requests_for_quotation_included": false,
      "trade_attributes_included": false,
      "market_state_included": false,
      "request_id": 1,
      "off_market_trades_included": false,
      "currency_rate_included": false,
      "actual_dom_subscription_type": 0,
      "premium_included": false
    }
  ],
  "real_time_market_data": [
    {
      "contract_id": 1,
      "is_snapshot": true,
      "market_values": [
        {
          "scaled_yesterday_settlement": 694175,
          "scaled_total_volume": 0,
          "scaled_yesterday_last": 694175,
          "day_index": 0,
          "tick_volume": 0,
          "trade_date": 206706000
        }
      ],
      "quotes_trade_date": 206706000,
      "correct_price_scale": 0.01
    }
  ]
}

Here real_time_market_data is a part of the same message, but it’s not always the rule - any of market data messages sent by the server may appear in different ServerMsg responses.

For explanation of different market_data_subscription_statuses.*_included flags please refer to corresponding protocol section.

These flags may not exactly match those requested in market_data_subscriptions.include_* flags because of different factors:

      Trader entitlements

      Availability of concrete data for specified contract

      Exchange limitations for specified contract

This is also true for market_data_subscription_statuses.level field which may be lowered depending on the above conditions.

All contract’s quote data updates are coming by means of market_data_2.RealTimeMarketData server responses. For example:

{
  "real_time_market_data": [
    {
      "contract_id": 1,
      "quotes": [
        {
          "type": 1,
          "quote_utc_time": 235301992,
          "scaled_price": 6521,
          "volume": {
            "significand": 10
          }
        },
        {
          "type": 3,
          "scaled_price": 6521,
          "volume": {
            "significand": 10
          }
        },
        {
          "type": 0,
          "scaled_price": 6521,
          "volume": {
            "significand": 10
          },
          "indicators": [
            4,
            11
          ],
          "sales_condition": 5
        },
        {
          "type": 1,
          "scaled_price": 6521,
          "volume": {
            "significand": 10
          }
        },
        {
          "type": 3,
          "scaled_price": 6521,
          "volume": {
            "significand": 10
          }
        },
        {
          "type": 0,
          "scaled_price": 6521,
          "volume": {
            "significand": 9
          },
          "indicators": [
            4,
            11
          ],
          "sales_condition": 5
        },
        {
          "type": 1,
          "scaled_price": 6521,
          "volume": {
            "significand": 0
          }
        },
        {
          "type": 3,
          "scaled_price": 6521,
          "volume": {
            "significand": 0
          }
        }
      ]
    },
    {
      "contract_id": 1,
      "detailed_dom": {
        "is_detailed_dom_complete": true,
        "price_levels": [
          {
            "scaled_price": 6521,
            "side": 1,
            "orders": [
              {
                "detailed_dom_order_id": "801558941271",
                "order_index": 0,
                "volume": {
                  "significand": 10
                }
              }
            ]
          }
        ]
      }
    },
    {
      "contract_id": 1,
      "detailed_dom": {
        "is_detailed_dom_complete": true,
        "price_levels": [
          {
            "scaled_price": 6521,
            "side": 1,
            "orders": [
              {
                "detailed_dom_order_id": "801558941271",
                "operation": 2,
                "order_index": 0,
                "volume": {
                  "significand": 9
                }
              }
            ]
          }
        ]
      }
    },
    {
      "contract_id": 1,
      "detailed_dom": {
        "is_detailed_dom_complete": true,
        "price_levels": [
          {
            "scaled_price": 6521,
            "side": 1,
            "is_snapshot": true
          }
        ]
      }
    }
  ]
}

Please refer on how to:

      convert scaled prices

      convert relative times (*_utc_time)

There may also be corrections quotes inside a report. These quotes will have INDICATOR_INSERTION or INDICATOR_DELETION indicators (see Quote for details) and quote_utc_time that is time when correction took place.

Quote with zero volume like

{
  "type": 3,
  "scaled_price": 6521,
  "volume": {
    "significand": 0
  }
}

indicates that the quote has been cleared. It is included even if volumes were not requested.

Quotes may also have optional indicators to emphasize additional attributes. For example the next quote

{
  "type": 0,
  "scaled_price": 6521,
  "volume": {
    "significand": 10
  },
  "indicators": [
    4,
    11
  ],
  "sales_condition": 5
}

has indicators CLOSE and LAST - the price of this quote is a new last and close price of the contract session. See Indicator enum of Quote message for details.

Types of market data subscriptions

There are 2 main types of subscriptions do exist: real-time and delayed. The type is directly affected by the trader’s entitlements for the requested contract.

To check if contract market data is delayed please use the next fields of metadata_2.ContractMetadata message:

      market_data_delay: if this field is set all market data for a contract will come with a delay specified, in milliseconds. Note: initial snapshot will come right after subscription request and will match contract’s state market_data_delay milliseconds ago.

      end_of_day_delay: if this field is set, trader will be allowed to get only market values for the current and previous trading days, and only if these days were finished (closed) end_of_day_delay milliseconds ago.

If the above 2 fields of metadata_2.ContractMetadata are absent then the subscription is either:

      Should provide real-time data.

      Does not provide end of day data or is not available for current trader’s account. In this case market_data_2.MarketDataSubscriptionStatus will contain STATUS_CODE_ACCESS_DENIED.

Collapsing of market data

Under certain conditions, the server may activate a market data collapsing algorithm:

      When Logon.max_collapsing_level is unset or set to any other value than REAL_TIME_COLLAPSING_LEVEL_NONE or when Logon.market_data_bandwidth is specified (see user_session_2.Logon)

      In case of slow client’s internet connection.

      When client’s application can’t cope with incoming market data in time.

The last 2 cases won’t be activated if Logon.max_collapsing_level is explicitly set to REAL_TIME_COLLAPSING_LEVEL_NONE. But be aware that the server may forcibly close the connection if its market data queue reaches internal limits when collapsing would normally be activated.

When collapsing is active real_time_market_data.collapsing_level will be set. Please refer protocol comments for types of collapsing levels and their effect.

Logon.market_data_bandwidth is a special version of the collapsing option. Server will begin collapsing outgoing market data when its calculated rate exceeds above specified bandwidth value.

if neither of Logon.max_collapsing_level or Logon.market_data_bandwidth are specified default collapsing algorithm is used when slow connection is detected:

      First stage: DOM quotes are collapsed.

      Second stage: BBA quotes are collapsed.

      Third stage: Trade quotes are collapsed.

      Fourth stage: connection is forcibly closed by server if none of previous stages achieve desired result (reducing of outgoing market data queue).

The second and the third options are similar and can be distinguished only on client’s side. Example steps for detection the exact reason could be:

      Ensure user_session_2.Logon message does not contain max_collapsing_level or market_data_bandwidth fields set.

      Ensure internet connection is fast enough. Take several subsequent real_time_market_data responses and calculate desired network speed by using quote_utc_time from several messages and received binary message sizes.

      Discarding all incoming market data and checking only its collapsing_level field. If this fields becomes unset most likely the speed of client’s application is not enough to process all market data in time.

Changing market data subscription level

To change the level of existing active subscription it’s enough to send the same market_data_2.MarketDataSubscription client message with desired new level. In case of level set to 0 (NONE) subscription will be dropped.

This change won’t affect existing contract’s data flow, just will extend or lower the details of sent market data by server.

Note: If you need just to change the level of the existing subscription do not drop it (with level 0) and re-request again because this is not an optimal way and is considered a bad practice.

Best practices

      Keep subscription alive by applying updates to your contract’s snapshot - do not drop and re-subscribe to get fresh data - it will be the same as in “update” case but will produce much more network traffic.

      If you’re using bandwidth limiting (Logon.market_data_bandwidth) try to specify values less or equal to real upstream bandwidth you’re using. This will make market data collapsing more predictable.

      Interpret each market_data_2.RealTimeMarketData with unset is_snapshot or is_snapshot set to false as “incomplete” update, that is: market data for every contract_id in current update may be followed by its continuation in the next message. This is because the server may split a single update into several ServerMsg responses if the size of some response reaches internal limits.