areed1192 / interactive-broker-python-api

A python packaged used to interact with the Interactive Brokers REST API.
MIT License
389 stars 124 forks source link

Streaming Websocket Data #6

Open vepak opened 4 years ago

vepak commented 4 years ago

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)
areed1192 commented 4 years ago

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.

vepak commented 4 years ago

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 commented 4 years ago

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

benjaminpolk commented 4 years ago

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

sandybradley commented 4 years ago

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

pandaxbacon commented 4 years ago

@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()
AlsaAnz commented 2 years ago

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

areed1192 commented 2 years ago

@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.

GCB-Botmodels commented 2 years ago

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()
marianputis commented 2 years ago

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.

nugzar commented 1 year ago

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()
farhoodMoslehi commented 1 year ago

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