'IB_insync - Sanic error after one successful order preventing any further orders

I'm writing an API using ib_insync, Sanic and ngrok to forward webhook signals from Tradingview onto Interactive Brokers. It works on only the first attempt and the following error is thrown preventing any further orders:

[ERROR] Exception occurred while handling uri: 'http://url.ngrok.io/webhook' Traceback (most recent call last): File "handle_request", line 103, in handle_request "_future_listeners", sanic.exceptions.ServerError: Invalid response type None (need HTTPResponse)

The code is as follows:

from datetime import datetime
from sanic import Sanic    
from sanic import response
from ib_insync import *

#Create Sanic object
app = Sanic(__name__)
app.ib = None

#Create root
@app.route('/')
async def root(request):
    return response.text('online')

#Listen for signals and execute orders
@app.route('/webhook', methods=['POST'])
async def webhook(request):
    if request.method == 'POST':
        await checkIfReconnect()
        #Parse alert data
        alert = request.json
        order = MarketOrder(alert['action'],alert['quantity'],account=app.ib.wrapper.accounts[0])
        #Submit market order
        stock_contract = Stock('NVDA','SMART','USD')
        app.ib.placeOrder(stock_contract,order)

#Reconnect if needed
async def checkIfReconnect():
    if not app.ib.isConnected() or not app.ib.client.isConnected():
        app.ib.disconnect()
        app.ib = IB()
        app.ib.connect('127.0.0.1',7496,clientId=1)

#Run app
if __name__ == '__main__':
    #Connect to IB
    app.ib = IB()
    app.ib.connect('127.0.0.1',7496,clientId=1)
    app.run(port=5000)


Solution 1:[1]

You are seeing this error because you have forgotten to send a response to the first POST request. All HTTP requests need a corresponding response, even if it is just for triggering an action.

Ie, change your webhook code to this:

@app.route('/webhook', methods=['POST'])
async def webhook(request):
    if request.method == 'POST':
        await checkIfReconnect()
        #Parse alert data
        alert = request.json
        order = MarketOrder(alert['action'],alert['quantity'],account=app.ib.wrapper.accounts[0])
        #Submit market order
        stock_contract = Stock('NVDA','SMART','USD')
        app.ib.placeOrder(stock_contract,order)
        return HTTPResponse("ok", 200)  #<-- This line added
    return HTTPResponse("", 405)  #<-- else return this

Solution 2:[2]

Im working with the same code, using breakpoints every POST triggers correctly. I see exactly what you mean how only 1 order can be placed each time the app is started. I tried using ib.qualifyContract(Stock) but it creates an error within the loop for the webhook. I wonder if you can move your order placement outside any loop functions. Ill try when I get some time and report back.

Solution 3:[3]

I'm using almost the same script as you do. I'm guessing your problem is not the http response as mentioned before(I don't use it). The thing is that each order sent to IB has to have a unique identifier and im not seeing you applied to your code. You can read about here (https://interactivebrokers.github.io/tws-api/order_submission.html). I found a way for doing that but its complicated for me to explain here. basically you'll have to add the order id to each and every order sent and from there there are two ways to go:

  1. connect properly to IBapi and check for the current available unique order ID
  2. use an ID of your own(numeric) for example in a form of a loop, and reset the sequence in TWS on each restart of the script as shown on the link I added.

Solution 4:[4]

I encountered the same issue (_future_listeners) while working on the same code. Been looking around to find the solution but none of them worked so far. I am sharing this to see if you guys were able to fix it.

I tried (or intended to try) these solutions: 1- I used asynchronous connect (app.ib.connectAsync) instead of app.ib.connect in both places. But it returned the await warning error. The second app.ib.connectAsync is outside an async function so cannot be awaited. You can run the code but it gives another error: "list index out of range" for the MarketOrder function.

2- I added app.ib.qualifyContracts . but it did not resolve the issue as well. I used it and not even the first order was sent to TWS.

3- Adding the unique orderid. I have not tried it because I am not sure if it works. I printed the orders, it seems like they already been ordered.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Ashley Sommer
Solution 2 Richard M.
Solution 3 Xavier
Solution 4 Alex_88