Retrieving Ticker Data in Python (OHLCV)

First of all, I want to thank anyone who contributes to this question. I know everyone’s time is valuable and it means a lot!!

I created python code to do things like generate trades, list position, etc., but I also want to pull ticker data (OHLCV) from Tradovate. I am using the following code, but I get the following error…
“websockets.exceptions.ConnectionClosedOK: received 1000 (OK) Bye; then sent 1000 (OK) Bye”

Here is the code…

import asyncio
import websockets
import json
from config import *
from GetAccessToken import getAccessToken

— Get Access Token
token = getAccessToken(False)[0]

— Define the WebSocket URL for Tradovate’s API
websocket_url = f"wss://md.tradovateapi.com/v1/websocket/{token}"

— Create a message to authenticate with Tradovate
auth_message = {
“action”: “login”,
“data”: {
“appId”: APP_ID,
“appVersion”: “1.0.0”,
“cid”: CID,
“deviceId”: DEVICE_ID,
“sec”: API_SECRET,
“name”: USER_NAME,
“password”: PASSWORD,
},
}

— Create a message to subscribe to quotes for a specific symbol
subscribe_message = {
“action”: “quote”,
“data”: [
{“symbol”: “CLV3”}
],
}

async def connect_and_request_quotes():

 async with websockets.connect(websocket_url) as websocket:
    
    # Send the authentication message
    await websocket.send(json.dumps(auth_message))

    # Wait for the authentication response
    auth_response = await websocket.recv()

    if "error" in auth_response:
        print(f"Authentication Error: {auth_response}")
        return  # Exit the function or handle the error as needed

    print(f"Authentication Response: {auth_response}")

    # Send the subscription message
    await websocket.send(json.dumps(subscribe_message))

    print(f"Subscribe Message Response: {subscribe_message}")

    # Continuously receive and print quotes
    while True:
        quote_response = await websocket.recv()
        print(f"Quote: {quote_response}")

if name == “main”: #(name and main are surrounded by “__”)
asyncio.get_event_loop().run_until_complete(connect_and_request_quotes())

Here is the error message…

Authentication Response: o
Subscribe Message Response: {‘action’: ‘quote’, ‘data’: [{‘symbol’: ‘CLV3’}]}
Traceback (most recent call last):
File “/home/kt7/PycharmProjects/tradovateMarketData/GetMarketData4.py”, line 62, in
asyncio.get_event_loop().run_until_complete(connect_and_request_quotes())
File “/usr/lib/python3.11/asyncio/base_events.py”, line 653, in run_until_complete
return future.result()
^^^^^^^^^^^^^^^
File “/home/kt7/PycharmProjects/tradovateMarketData/GetMarketData4.py”, line 58, in connect_and_request_quotes
quote_response = await websocket.recv()
^^^^^^^^^^^^^^^^^^^^^^
File “/home/kt7/PycharmProjects/tradovateMarketData/venv/lib/python3.11/site-packages/websockets/legacy/protocol.py”, line 568, in recv
await self.ensure_open()
File “/home/kt7/PycharmProjects/tradovateMarketData/venv/lib/python3.11/site-packages/websockets/legacy/protocol.py”, line 944, in ensure_open
raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedOK: received 1000 (OK) Bye; then sent 1000 (OK) Bye

I am not as familiar with the pythonic way of interacting with Tradovate but I would check how you are authenticating the market data connection. I see you are appending the token to the end of the market data socket url which looks incorrect. Confirm that at some point in your code you make a socket request like this: (Below code in TypeScript):

marketDataSocket.send(`authorize\n0\n\n${token}`)

The auth request should be sent once you receive the ‘o’ frame from .connect(websocket_url) call.

Please reference the api documentation for the socket authorization endpoint

In this line of code it appears you are passing the accessTokenRequest body params for what should be the actual token.

await websocket.send(json.dumps(auth_message))

Hope that helps.

Thanks, Colby!!

That worked great, but now I am getting a repeating “Quote: h”. I know the “Quote:” is from the code and the “h” is the heartbeat, but how do I get the CLV3 quote data (OHLCV)…Any ideas?

Here is the output…

Authentication Response: o
Subscribe Message Response: {‘action’: ‘quote’, ‘data’: [{‘symbol’: ‘CLV3’}]}
Quote: a[{“s”:200,“i”:0}]
Quote: h
Quote: h
Quote: h

1 Like

Yes that is the heartbeat frame.

Here is an overview of what happened:

  1. You connected to the market data websocket
  2. You received an ‘o’ open frame from the socket
  3. You sent authorize\n0\n\n${token} request via the socket
  4. You received an ‘a’ frame that tells you the authorize\n0\n\n${token} was successful. You know this ‘a’ frame response related to the auth request because the i field matches the id you passed in the request. ( \n0 )
  5. You continue to receive heartbeat frames every 2.5 seconds.

How to change you code (In TypeScript):

  1. You should parse all received socket messages through a function like prepareMessage that returns an object {T, data}.

T is the socket frame that can be ‘o’, ‘a’ , ‘h’ or ‘c’. Data is whatever came after the socket frame (if anything) parsed from a JSON string to an object. More on server frames.

  1. It does not appear from your output the request for quote data is working properly. Make you sure you increment the requestId. Docs on socket client requests.

  2. Set up a listener for ‘a’ frames that have the quote data.

  3. Send a heartbeat message back to Tradovate on every ‘h’ frame received. This is a good way to make sure your connection stays connected.

Here is everything I am talking about in code:

import {MessageEvent, Data} form 'ws'

// Instantiate the marketDataSocket and authorize

// Change 1.
const prepareMessage = (raw: Data) => {
    const T = raw.slice(0, 1)
    let data = []
    if ((raw as string).length > 1) {
        data = JSON.parse((raw as string).slice(1))
    }
    return {T, data}
}

// Change 3.
type Quote = {
    timestamp: string //example: "2017-04-13T11:33:57.488Z"
    contractId: number // ID of the quote contract
    entries: {
        // Any of entries may absent if no data available for them.
        // Either price or size field (but not both) may absent in any entries.
        Bid: Price
        TotalTradeVolume: Price
        Offer: Price
        LowPrice: Price
        Trade: Price
        OpenInterest: Price
        OpeningPrice: Price
        HighPrice: Price
        SettlementPrice: Price
        EmptyBook: Price
    }
}

type Price = {
    price: number
    size: number
}


const onQuoteData = (msg: MessageEvent) => {
    const {T, data} = prepareMessage(msg.data)
    if (T === 'a' && data && data.length > 0) {
        data.forEach((item: any) => {
             // initial response from subscribeQuote Request 
             // normally you wouldn't hard code 1 as id
             if(item.s === 200 && item.i  === 1){
                 // Whatever you want to do on confirmation
             }

             // type guard for quote data, 
             if(item.d && item.d.quotes) {
                 item.d.quotes.forEach((quote: Quote) => {
                     // manipulate data
                     console.log(quote)
                 })
             }
        })

    } else if (T === 'h') { // Change 4.
        marketDataSocket.send('[]')
    }
}

marketDataSocket.addEventListener('message', onQuoteData)

// Change 2. Best to send request after listener is attached
const quoteRequestBody = JSON.stringify({symbol: 'CLV3'}, null, 2) 
marketDataSocket.send(`md/subscribequote\n1\n\n${quoteRequestBody}

Thanks, Colby!!

I will dig into this and let you know if I run into any issues.

I really appreciate your time!!!

Hey Colby,

Once again…you nailed it!!

I am recieving this error message now (I have every setting as read only in my API Key setup and I have an active NYMEX Market Data Subscription for CLV3).

a[{“s”:200,“i”:2,“d”:{“errorText”:“Symbol is inaccessible”,“errorCode”:“UnknownSymbol”,“mode”:“None”}}]

Glad to hear it. And unfortunately that is due to not having a license agreement signed directly with whoever provides the data for CLV.

I had to sign an agreement directly with the CME for $407/mo to receive real time data for ES products. (All CME products are included in the $407/mo). Here is an email response I got from Tradovate on the matter and a reddit post worth reading:

Bastiaan Smit (Tradovate LLC)
Jul 6, 2023, 2:43 PM EDT

Hello,

The CME will require users with access to the Tradovate API to be registered as a data distributor at the exchange. Any person or firm that plans to use CME market data in a Non-Display capacity to automate their trading activity is required to put a CME Information License Agreement (ILA) into place. You will need to see whether this would be feasible, as the monthly cost that CME charges for this registration is currently over $400/month.

If you are interested in using the API, please email CME sales (CMEDataSales@cmegroup.com) and inform them that you intend to use the Tradovate API and therefore require registering with the CME as a data distributor/CME Information License Agreement (ILA). Once we receive confirmation from the CME that this has been applied for you, we can enable the API access for your user again.

Bastiaan Smit
Tradovate, LLC
support@tradovate.com
312-283-3100

Sorry for the unfortunate news.