Stage 5: Market Data

Market data access is essential for trading applications. This stage validates your integration’s ability to establish WebSocket connections, subscribe to real-time market data, and handle market data streams effectively.

Overview

The market data stage ensures your application can:

  • Establish secure WebSocket connections for market data
  • Subscribe to and receive real-time quotes, DOM, and chart data
  • Handle market data streams with proper error handling
  • Manage subscriptions and unsubscribe properly
  • Process tick chart data correctly

Required Tests

1. WebSocket Connection and Authorization

Purpose: Establish a secure WebSocket connection and authenticate for market data access.

Test Steps:

  1. Open WebSocket connection to market data endpoint
  2. Send authorization message with access token
  3. Verify successful authentication response
  4. Test connection stability and reconnection handling
  5. Verify proper error handling for invalid tokens

Example Implementation:

1// WebSocket connection setup
2const ws = new WebSocket("wss://md.tradovateapi.com/v1/websocket");
3//wss://md-api.staging.ninjatrader.dev/v1/websocket for staging
4
5ws.onopen = function () {
6 // Send authorization message
7 const authMessage = `authorize\n1\n\n${accessToken}`;
8 ws.send(JSON.stringify(authMessage));
9};
10
11ws.onmessage = function (event) {
12 const message = JSON.parse(event.data);
13
14 if (message.i === 1 && message.s === 200) {
15 console.log("Successfully authorized for market data");
16 // Proceed with market data subscriptions
17 } else if (message.d.errorText) {
18 console.error("Authorization failed:", message.d);
19 }
20};

Validation Criteria:

  • ✅ WebSocket connection establishes successfully
  • ✅ Authorization message is accepted
  • ✅ Connection remains stable during testing
  • ✅ Invalid tokens are rejected appropriately
  • ✅ Connection can be re-established after disconnection

2. Quote Subscription and Data Reception

Endpoint: md/subscribeQuote

Purpose: Subscribe to real-time quote data for specified contracts.

Test Steps:

  1. Subscribe to quote data for a valid contract symbol
  2. Verify subscription confirmation response
  3. Receive and validate quote data messages
  4. Test subscription with contract ID instead of symbol
  5. Unsubscribe and verify data stops flowing

Example Implementation:

1// Subscribe to quotes
2function subscribeToQuotes(symbol) {
3 const subscribeMessage = {
4 "md/subscribeQuote": {
5 symbol: symbol,
6 },
7 };
8 ws.send(JSON.stringify(subscribeMessage));
9}
10
11// Handle incoming quote data
12ws.onmessage = function (event) {
13 const message = JSON.parse(event.data);
14
15 if (message.e === "md" && message.d.quotes) {
16 message.d.quotes.forEach((quote) => {
17 console.log("Quote received:", {
18 contractId: quote.contractId,
19 timestamp: quote.timestamp,
20 bid: quote.entries.Bid,
21 offer: quote.entries.Offer,
22 trade: quote.entries.Trade,
23 });
24 });
25 }
26};
27
28// Unsubscribe from quotes
29function unsubscribeFromQuotes(symbol) {
30 const unsubscribeMessage = {
31 "md/unsubscribeQuote": {
32 symbol: symbol,
33 },
34 };
35 ws.send(JSON.stringify(unsubscribeMessage));
36}

Expected Quote Data:

1{
2 "e": "md",
3 "d": {
4 "quotes": [
5 {
6 "timestamp": "2024-01-01T12:00:00.000Z",
7 "contractId": 123456,
8 "entries": {
9 "Bid": { "price": 18405.123, "size": 7.123 },
10 "Offer": { "price": 18410.012, "size": 12.35 },
11 "Trade": { "price": 18405.023, "size": 2.1 },
12 "TotalTradeVolume": { "size": 4118.123 },
13 "OpenInterest": { "size": 40702.024 },
14 "HighPrice": { "price": 18520.125 },
15 "LowPrice": { "price": 18355.23 },
16 "OpeningPrice": { "price": 18515.123 },
17 "SettlementPrice": { "price": 18520.257 }
18 }
19 }
20 ]
21 }
22}

Validation Criteria:

  • ✅ Quote subscription succeeds with valid symbol/contract ID
  • ✅ Real-time quote data is received continuously
  • ✅ Quote data contains all expected fields
  • ✅ Data timestamps are accurate and recent
  • ✅ Unsubscription stops data flow immediately
  • ✅ Invalid symbols are rejected appropriately

3. DOM (Depth of Market) Subscription

Endpoint: md/subscribeDOM

Purpose: Subscribe to real-time depth of market data showing bid/offer levels.

Test Steps:

  1. Subscribe to DOM data for a liquid contract
  2. Verify DOM data structure and content
  3. Test multiple price levels in bids and offers
  4. Verify real-time updates to DOM data
  5. Unsubscribe and verify data stops

Example Implementation:

1// Subscribe to DOM
2function subscribeToDOM(symbol) {
3 const subscribeMessage = {
4 "md/subscribeDOM": {
5 symbol: symbol,
6 },
7 };
8 ws.send(JSON.stringify(subscribeMessage));
9}
10
11// Handle DOM data
12ws.onmessage = function (event) {
13 const message = JSON.parse(event.data);
14
15 if (message.e === "md" && message.d.doms) {
16 message.d.doms.forEach((dom) => {
17 console.log("DOM received:", {
18 contractId: dom.contractId,
19 timestamp: dom.timestamp,
20 bidLevels: dom.bids.length,
21 offerLevels: dom.offers.length,
22 bestBid: dom.bids[0],
23 bestOffer: dom.offers[0],
24 });
25 });
26 }
27};

Expected DOM Data:

1{
2 "e": "md",
3 "d": {
4 "doms": [
5 {
6 "contractId": 123456,
7 "timestamp": "2024-01-01T12:00:00.000Z",
8 "bids": [
9 { "price": 2335.25, "size": 33.54 },
10 { "price": 2335.0, "size": 45.12 },
11 { "price": 2334.75, "size": 22.88 }
12 ],
13 "offers": [
14 { "price": 2335.5, "size": 255.12 },
15 { "price": 2335.75, "size": 180.45 },
16 { "price": 2336.0, "size": 95.67 }
17 ]
18 }
19 ]
20 }
21}

Validation Criteria:

  • ✅ DOM subscription succeeds with valid contract
  • ✅ DOM data contains multiple bid/offer levels
  • ✅ Price levels are properly ordered (bids descending, offers ascending)
  • ✅ Real-time updates reflect market changes
  • ✅ DOM data structure is consistent
  • ✅ Unsubscription works correctly

4. Chart Data Subscription

Endpoint: md/getChart

Purpose: Request historical and real-time chart data for analysis.

Test Steps:

  1. Request chart data with specific parameters
  2. Verify historical data is returned
  3. Verify real-time chart updates are received
  4. Test different chart types (MinuteBar, DailyBar, Tick)
  5. Properly cancel chart subscription using returned ID

Example Implementation:

1// Request chart data
2function requestChart(symbol, chartType = "MinuteBar", elementSize = 15) {
3 const chartRequest = {
4 "md/getChart": {
5 symbol: symbol,
6 chartDescription: {
7 underlyingType: chartType,
8 elementSize: elementSize,
9 elementSizeUnit: "UnderlyingUnits",
10 withHistogram: false,
11 },
12 timeRange: {
13 asMuchAsElements: 100,
14 },
15 },
16 };
17 ws.send(JSON.stringify(chartRequest));
18}
19
20// Handle chart response and data
21ws.onmessage = function (event) {
22 const message = JSON.parse(event.data);
23
24 if (message.s === 200 && message.d) {
25 // Store subscription IDs for later cancellation
26 const historicalId = message.d.historicalId;
27 const realtimeId = message.d.realtimeId;
28 console.log("Chart subscription IDs:", { historicalId, realtimeId });
29 }
30
31 if (message.e === "chart" && message.d.charts) {
32 message.d.charts.forEach((chart) => {
33 console.log("Chart data received:", {
34 subscriptionId: chart.id,
35 tradeDate: chart.td,
36 barCount: chart.bars ? chart.bars.length : 0,
37 endOfHistory: chart.eoh,
38 });
39 });
40 }
41};
42
43// Cancel chart subscription
44function cancelChart(subscriptionId) {
45 const cancelRequest = {
46 "md/cancelChart": {
47 subscriptionId: subscriptionId,
48 },
49 };
50 ws.send(JSON.stringify(cancelRequest));
51}

Expected Chart Response:

1{
2 "s": 200,
3 "i": 13,
4 "d": {
5 "historicalId": 32,
6 "realtimeId": 31
7 }
8}

Expected Chart Data:

1{
2 "e": "chart",
3 "d": {
4 "charts": [
5 {
6 "id": 31,
7 "td": 20240101,
8 "bars": [
9 {
10 "timestamp": "2024-01-01T11:00:00.000Z",
11 "open": 2334.25,
12 "high": 2334.5,
13 "low": 2333.0,
14 "close": 2333.75,
15 "upVolume": 4712.234,
16 "downVolume": 201.124,
17 "upTicks": 1333.567,
18 "downTicks": 82.89,
19 "bidVolume": 2857.123,
20 "offerVolume": 2056.224
21 }
22 ]
23 }
24 ]
25 }
26}

Validation Criteria:

  • ✅ Chart request succeeds with valid parameters
  • ✅ Historical data is returned with proper structure
  • ✅ Real-time chart updates are received
  • ✅ Subscription IDs are properly returned
  • ✅ Chart cancellation works correctly
  • ✅ Different chart types work as expected

5. Tick Chart Data Processing

Purpose: Request and process tick-level chart data for high-frequency analysis.

Test Steps:

  1. Request tick chart data with elementSize: 1
  2. Receive and process tick data packets
  3. Calculate actual tick prices and timestamps from relative values
  4. Handle multiple packets and end-of-history flag
  5. Verify tick data accuracy and completeness

Example Implementation:

1// Request tick chart
2function requestTickChart(symbol) {
3 const tickRequest = {
4 "md/getChart": {
5 symbol: symbol,
6 chartDescription: {
7 underlyingType: "Tick",
8 elementSize: 1,
9 elementSizeUnit: "UnderlyingUnits",
10 },
11 timeRange: {
12 asMuchAsElements: 1000,
13 },
14 },
15 };
16 ws.send(JSON.stringify(tickRequest));
17}
18
19// Process tick chart data
20function processTickChartMessage(msg) {
21 const result = [];
22 if (msg.charts && msg.charts.length) {
23 for (let i = 0; i < msg.charts.length; ++i) {
24 const packet = msg.charts[i];
25 if (packet.eoh) {
26 console.log("End of historical ticks reached");
27 } else if (packet.tks && packet.tks.length) {
28 for (let j = 0; j < packet.tks.length; ++j) {
29 const tick = packet.tks[j];
30
31 const timestamp = packet.bt + tick.t; // Actual tick timestamp
32 const price = packet.bp + tick.p; // Actual tick price
33
34 const bid = tick.bs && packet.bp + tick.b; // Actual bid price
35 const ask = tick.as && packet.bp + tick.a; // Actual ask price
36
37 result.push({
38 id: tick.id,
39 timestamp: new Date(timestamp),
40 price: price * packet.ts, // Tick price as contract price
41 size: tick.s, // Tick size (volume)
42 bidPrice: bid && bid * packet.ts,
43 bidSize: tick.bs,
44 askPrice: ask && ask * packet.ts,
45 askSize: tick.as,
46 });
47 }
48 }
49 }
50 }
51 return result;
52}
53
54// Handle tick chart data
55ws.onmessage = function (event) {
56 const message = JSON.parse(event.data);
57
58 if (message.e === "chart" && message.d.charts) {
59 const processedTicks = processTickChartMessage(message.d);
60 console.log("Processed ticks:", processedTicks.length);
61 }
62};

Expected Tick Data:

1{
2 "charts": [
3 {
4 "id": 16335,
5 "s": "db",
6 "td": 20240101,
7 "bp": 11917,
8 "bt": 1704111179735,
9 "ts": 0.25,
10 "tks": [
11 {
12 "t": 0,
13 "p": 0,
14 "s": 3,
15 "b": -1,
16 "a": 0,
17 "bs": 122.21,
18 "as": 28.35,
19 "id": 11768401
20 }
21 ]
22 }
23 ]
24}

Validation Criteria:

  • ✅ Tick chart request succeeds
  • ✅ Tick data packets are received and processed
  • ✅ Relative prices and timestamps are calculated correctly
  • ✅ End-of-history flag is properly handled
  • ✅ Tick data accuracy is maintained
  • ✅ Multiple packets are processed correctly

6. Histogram Subscription

Endpoint: md/subscribeHistogram

Purpose: Subscribe to real-time histogram data showing price distribution.

Test Steps:

  1. Subscribe to histogram data for a contract
  2. Verify histogram data structure
  3. Test histogram updates and refresh flags
  4. Verify price distribution data accuracy
  5. Unsubscribe and verify data stops

Example Implementation:

1// Subscribe to histogram
2function subscribeToHistogram(symbol) {
3 const subscribeMessage = {
4 "md/subscribeHistogram": {
5 symbol: symbol,
6 },
7 };
8 ws.send(JSON.stringify(subscribeMessage));
9}
10
11// Handle histogram data
12ws.onmessage = function (event) {
13 const message = JSON.parse(event.data);
14
15 if (message.e === "md" && message.d.histograms) {
16 message.d.histograms.forEach((histogram) => {
17 console.log("Histogram received:", {
18 contractId: histogram.contractId,
19 timestamp: histogram.timestamp,
20 base: histogram.base,
21 refresh: histogram.refresh,
22 itemCount: Object.keys(histogram.items).length,
23 });
24 });
25 }
26};

Expected Histogram Data:

1{
2 "e": "md",
3 "d": {
4 "histograms": [
5 {
6 "contractId": 123456,
7 "timestamp": "2024-01-01T12:00:00.000Z",
8 "tradeDate": {
9 "year": 2024,
10 "month": 1,
11 "day": 1
12 },
13 "base": 2338.75,
14 "items": {
15 "-14": 5906.67,
16 "-13": 4521.23,
17 "-12": 3890.45,
18 "0": 12345.67,
19 "1": 2345.89,
20 "2": 1234.55
21 },
22 "refresh": false
23 }
24 ]
25 }
26}

Validation Criteria:

  • ✅ Histogram subscription succeeds
  • ✅ Histogram data contains price distribution
  • ✅ Base price and items are properly structured
  • ✅ Refresh flag indicates data updates
  • ✅ Unsubscription works correctly

Best Practices

  1. Connection Management: Implement robust WebSocket connection handling with automatic reconnection
  2. Subscription Tracking: Keep track of all active subscriptions to properly unsubscribe
  3. Data Processing: Handle market data efficiently to avoid memory leaks
  4. Error Recovery: Implement proper error handling and recovery mechanisms
  5. Rate Limiting: Respect rate limits and implement backoff strategies
  6. Data Validation: Validate all incoming market data before processing
  7. Resource Cleanup: Always unsubscribe from data streams when no longer needed

Testing Checklist

  • WebSocket connection establishes successfully
  • Authorization with access token works correctly
  • Quote subscription and data reception works
  • DOM subscription provides proper depth data
  • Chart data request returns historical and real-time data
  • Tick chart data is processed correctly
  • Histogram subscription provides data
  • All subscription types can be unsubscribed properly
  • Error handling works for invalid requests
  • Connection stability is maintained during testing
  • Resource cleanup is complete