0

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)

6 Answers6

0

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

Ashley Sommer
  • 337
  • 3
  • 12
  • Thank you for the response! Can't believe I overlooked that... but that seems to only superficially return a 200 response now, and any additional orders still are not being executed. – needfulmeerkat41954 Jan 10 '22 at 23:58
  • What do you mean "additional"? – Adam Hopkins Jan 23 '22 at 11:24
  • Sorry I missed this comment... I mean after one order is automatically placed, no subsequent orders are placed until I restart the app. So for example, the above code would buy NVDA if there was a buy alert, but if I leave it running and a sell alert came in thereafter, it would not place that sell order. Only if I had restarted the app before then will the sell order have been received. – needfulmeerkat41954 Feb 03 '22 at 21:48
  • You are going to need to share more of your project, because nothing that you've shown so far would prevent the subsequent handling of incoming requests. – Ashley Sommer Feb 14 '22 at 03:14
  • @AshleySommer If you see other comments now, it's unrelated to the original error and most likely related to needing a unique orderId. I've seen multiple possible solutions such as this one: https://stackoverflow.com/questions/69628960/ib-insync-only-place-stk-order-once-will-not-execute-additional-buy-or-sell... but they all either don't work or throw another error - "runtime error: this event loop is already running" – needfulmeerkat41954 Apr 09 '22 at 18:40
0

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.

0

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.
Xavier
  • 1
  • 1
  • Ok interesting... I will try to come up with something to account for order ID. Thanks for the answer. – needfulmeerkat41954 Apr 06 '22 at 00:09
  • A couple things, first, I'm using ib_insync and not IBapi. 2nd, when I add a print statement for MarketOrder, it is showing that it already does increment orderId. It looks like this: MarketOrder(orderId=9048, clientId=1, action='sell', totalQuantity='2',account='Uxxxxxx'), then on next trade: MarketOrder(orderId=9049, clientId=1, action='sell', totalQuantity='2',account='Uxxxxxx'). And so on. – needfulmeerkat41954 Apr 10 '22 at 15:35
0

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.

  • 1
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community May 05 '22 at 15:37
  • I just use ib.connectAsync() on the initial connection and it works for me now (no need to await). – needfulmeerkat41954 May 09 '22 at 04:33
0

I started out with the same widely distributed boilerplate code you are using. After making the changes outlined below, my code works. I lack the expertise to explain why it does--but it does.

Assuming you have the following installed: Python 3.10.7_64 Sanic 22.6.2 ib_insync 0.9.71

(1) pip install --upgrade sanic-cors sanic-plugin-toolkit ref: IB_insync - Sanic error after one successful order preventing any further orders (not sure if necessary)

(2) Add: import time (see clientId note below)

(3) Add: import nest_asyncio
nest_asyncio.apply() #ref: RuntimeError: This event loop is already running in python

(4) Use Async connect in initial connect at bottom (but not in the reconnect area) app.ib.connectAsync('127.0.0.1',7497,clientId=see below) # 7946=live, 7947=paper
ref:IB_insync - Sanic error after one successful order preventing any further orders

(5) Use time to derive a unique, ascending clientId in both connect statements clientId=int(int((time.time()*1000)-1663849395690)/1000000)) This helps avoid a "socket in use" condition on reconnect

(6) Add HTTPResponse statements as suggested above.

(7) Per Ewald de Wit, ib_insync author: "There's no need to set the orderId, it's issued automatically when the order is placed."

Snorkel
  • 11
  • 3
  • [Please do not post an answer that consists essentially of code](https://stackoverflow.com/questions/how-to-answer). Please [edit] your answer to include an explanation of how and why the code solves the problem, when it should be used, what its limitations are, and if possible a link to relevant documentation. – ljmc Sep 25 '22 at 10:07
0

There is an alternative, return return response.json({}) at the end of the async function webhook

........
from sanic import response
......
#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)
    return response.json({})  # return a empty JSON
madeinQuant
  • 1,721
  • 1
  • 18
  • 29