interactive-broker-python-api icon indicating copy to clipboard operation
interactive-broker-python-api copied to clipboard

Streaming Websocket Data

Open vepak opened this issue 5 years ago • 12 comments

Hi Alex, Great lib, thank you for making this. After watching your video series, I'm trying to get continuous market data from IB Websocket API. I could able to connect to Client Portal gateway and also connection to websocket is successful, but I'm not able to subscribe to any quotes. Below is the code I used.

Any guidance on where I might be doing wrong/missing? Thank you.

# -*- coding: utf-8 -*-
import websocket, json
import ssl

web_socket_endpoint = "wss://localhost:5000/v1/portal/ws"

def logout(ws):
    print("close")
    ws.close()

def on_open(ws):
    print("opened")
    ws.send("s+md+59392609")

def on_message(ws, message):
    print("message recieved")
    try:
        print(message)
    except:
        print("JSON Decode Failed")

def on_close(ws):
    print("Connection Closed")
    

def on_error(ws, error):
    print("error")
    print(error)

try:
    websocket.enableTrace(True)
    WSCONNECTION = websocket.WebSocketApp(web_socket_endpoint,
                              on_message = on_message,
                              on_error = on_error,
                              on_close = on_close)
    WSCONNECTION.on_open = on_open
    WSCONNECTION.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
except Exception as e:
    print(e)
    capture_exception(e)

vepak avatar Jun 27 '20 01:06 vepak

What does the subscription request look like? Also, what's the error you're getting? Keep in mind that it may be hard to test during the weekend, the API seems to give very different results on the weekend compared to the weekdays.

areed1192 avatar Jun 27 '20 15:06 areed1192

Hi Alex,

Here is the subscription request:

ws.send("s+md+59392609")

as mentioned here https://interactivebrokers.github.io/cpwebapi/RealtimeSubscription.html s - Subscribe md - Market Data 59392609 - conId

As you mentioned I waited until Monday morning in India when market opens at NSE and tried agin.

I don't see any errors, but I'm not getting any response in on_message function. I've market data subscription for the markets as I'm getting individual ticker details.

Whenever you got some time could you try connecting over websockets?

Thank you, Vamsi.

vepak avatar Jun 29 '20 05:06 vepak

hey, just checking if you have endpoint for websockets in your lib? Thanks!

vepak avatar Jun 29 '20 05:06 vepak

I have the same issue and have tried opening a ticket with IBKR last week. No answers yet.

benjaminpolk avatar Jul 01 '20 17:07 benjaminpolk

Same issue. I can connect to the socket and send subscription messages but no response data is received.

sandybradley avatar Jul 24 '20 12:07 sandybradley

@sandybradley @benjaminpolk @vepak I believe that is more like a ssl cert issue. I created a self-signed-cert and managed to receive response Screenshot 2020-10-20 at 2 39 52 PM

Code snippet:

import asyncio
import pathlib
import ssl
import websockets
import os

ssl_context = ssl.SSLContext(ssl.CERT_REQUIRED)
localhost_pem = pathlib.Path(__file__).with_name("self-signed-cert.pem")
ssl_context.load_verify_locations(localhost_pem)

# To allow https connection 
if (not os.environ.get('PYTHONHTTPSVERIFY', '') and getattr(ssl, '_create_unverified_context', None)):
    ssl._create_default_https_context = ssl._create_unverified_context

async def receive_messages(websocket):
    while True:
        try:
            response = await websocket.recv()
        except websockets.ConnectionClosed:
            print(f"Terminated")
            break

        print(f"< {response}")

async def ib_websocket_connection():

    uri = "wss://localhost:5000/v1/api/ws"

    async with websockets.connect(
        uri, ssl=ssl_context
    ) as websocket:

        # Send market data subscription request
        await websocket.send('smd+265598+{"fields":["31","83"]}')

        await receive_messages(websocket)

asyncio.get_event_loop().run_until_complete(ib_websocket_connection())
asyncio.get_event_loop().run_forever()

pandaxbacon avatar Oct 20 '20 06:10 pandaxbacon

Hello @pandaxbacon @areed1192 , I am getting this exact same data with the "topic : system, hb : 12234567", however, this is really not the data I am looking for. I am more specifically looking for tick data.

I am using the web socket API in javascript and in my case the data comes in as a blob which I then convert to json. Can you please share your thoughts, I have been on this for some time now.

Capture3

AlsaAnz avatar Jan 12 '22 07:01 AlsaAnz

@pandaxbacon Random question, did generating the self-signed certificate alleviate issues when navigating to the login page. I remember it always was warning issues that it wasn't a secure page.

areed1192 avatar Jan 12 '22 15:01 areed1192

I want get the same response using websocket.WebSocketApp and run_forever. How did should will refactor the code to?

import websocket as ws
import json
import ssl


wss = 'wss://localhost:500/v1/api/ws'
conn = ws.create_connection(wss,sslopt={"cert_reqs":ssl.CERT_NONE})

conn.send('smd+265598+{"fields":["31","83"]}')
conn.recv()

GCB-Botmodels avatar Mar 23 '22 07:03 GCB-Botmodels

hi, by any chance was someone able to resolve this issue and if yes could you share the code ? i used ws:// connection instead of wss:// (no encryption in transit between client and gateway) but still getting only Unsolicited Message Types. if i sent 'smd+265598+{"fields":["31","83"]}' i receive answer always only once.

marianputis avatar Jul 19 '22 12:07 marianputis

Hi. I think I made the code work and I am successfully receiving the streaming prices.

  1. The endpoint should be "wss://localhost:5000/v1/api/ws" instead of "wss://localhost:5000/v1/portal/ws"
  2. There is no need in generating other custom certificate. You just need to pass 'sslopt={"cert_reqs": ssl.CERT_NONE}' ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
  3. Main problem with the making things work is to wait for the time when the server is ready. After initial connection, you need to wait for the server to send following messages: First message: {"topic":"system","success":"yourusername","isFT":false,"isPaper":false} Second message: {"topic":"sts","args":{"authenticated":true, "competing":false, "message":"", "fail":"", "serverName":"yourservername", "serverVersion":"Build 10.20.0d, Dec 5, 2022 6:31:27 PM", "username":"yourusername"}}
  4. Only after receiving "topic":"sts" with "authenticated":true I am sending smd+270639+{"tempo":1000,"snapshot":true,"fields":["31","70","71","82","84","85","86","87","88","7295","7296","7674","7675","7676","7677","TimestampBase","TimestampDelta","6509"]}

You can see my working code below. Hope it helps.

import json, os, websocket, time, syslog, ssl
from dotenv import load_dotenv
from datetime import datetime
#from lib.tickle import tickle

def subscribe():

    start_time = time.time()

    def on_message(ws, binarymessage):

        message = binarymessage.decode()
        syslog.syslog(syslog.LOG_INFO, message)

        m = json.loads(message)

        print (message)

        if ("topic" in m) and (m["topic"] == "sts") and ("args" in m) and ("authenticated" in m["args"]) and (m["args"]["authenticated"] == True):
            s = """smd+270639+{"tempo":1000,"snapshot":true,"fields":["31","70","71","82","84","85","86","87","88","7295","7296","7674","7675","7676","7677","TimestampBase","TimestampDelta","6509"]}"""
            syslog.syslog(syslog.LOG_INFO, "Sending: {}".format(s))
            ws.send(s)

        if ((datetime.now().minute == 0) and (time.time() - start_time > 3 * 60)) or (time.time() - start_time > 60 * 60):
            syslog.syslog(syslog.LOG_INFO, "Periodical restart of the streamer")
            ws.close()
            exit()

        return

    def on_error(ws, error):
        syslog.syslog(syslog.LOG_ERR, "received error as {}".format(error))

    def on_close(ws, close_status_code, close_msg):
        syslog.syslog(syslog.LOG_INFO, "Connection closed {}".format(close_msg))

    def on_open(ws):

        #response = tickle()
        #r = json.loads(response.text)
        #print (response.text)

        return

    def ex_callback(callback, *args):
        """
        Monkey patch for WebSocketApp._callback() because it swallows
        exceptions.
        """
        if callback is not None:
            callback(ws, *args)

    #websocket.enableTrace(True)
    url = "wss://localhost:5000/v1/api/ws"

    ws = websocket.WebSocketApp(url,
      on_message = on_message,
      on_error = on_error,
      on_close = on_close,
      on_open = on_open
    )
    ws._callback = ex_callback
    ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})

if __name__ == "__main__":

  subscribe()

nugzar avatar Dec 30 '22 18:12 nugzar

This is very helpful, does anyone know how this is done in .NET? something like websocket sharpe

farhoodMoslehi avatar May 11 '23 01:05 farhoodMoslehi