我正在使用 ib_insync、Sanic 和 ngrok 编写一个 API,将 webhook 信号从 Tradingview 转发到盈透证券。它仅在第一次尝试时有效,并且会抛出以下错误,阻止任何进一步的订单:
[错误]处理 uri 时发生异常: 'http://url.ngrok.io/webhook' 回溯(最新的 最后调用):文件“handle_request”,第103行,在handle_request中 “_future_listeners”,sanic.exceptions.ServerError:无效的响应类型无(需要 HTTPResponse)
代码如下:
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)
您看到此错误是因为您忘记发送对第一个 POST 请求的响应。所有 HTTP 请求都需要相应的响应,即使只是为了触发某个操作。
即,将您的 webhook 代码更改为:
@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
我正在使用相同的代码,并在每个 POST 正确触发时使用断点。我完全明白你的意思,每次启动应用程序时只能下 1 个订单。我尝试使用 ib.qualifyContract(Stock) 但它会在 webhook 循环中产生错误。我想知道您是否可以将订单放置移到任何循环函数之外。当我有时间时我会尝试并报告回来。
我使用的脚本与您几乎相同。 我猜你的问题不是前面提到的http响应(我不使用它)。 问题是,发送到 IB 的每个订单都必须有一个唯一的标识符,但我没有看到您应用到您的代码。 您可以在这里阅读相关内容(https://interactivebrokers.github.io/tws-api/order_submission.html)。 我找到了一种方法来做到这一点,但我在这里解释起来很复杂。 基本上,您必须将订单 ID 添加到发送的每个订单中,并且有两种方法可以选择:
我在处理相同的代码时遇到了同样的问题(_future_listeners)。一直在寻找解决方案,但到目前为止没有一个有效。我分享这个是为了看看你们是否能够修复它。
我尝试过(或打算尝试)这些解决方案: 1-我在这两个地方都使用了异步连接(app.ib.connectAsync)而不是app.ib.connect。但它返回了等待警告错误。第二个 app.ib.connectAsync 位于异步函数之外,因此无法等待。您可以运行代码,但它会给出另一个错误:MarketOrder 函数的“列表索引超出范围”。
2-我添加了 app.ib.qualifyContracts 。但它也没有解决问题。我用了它,连第一笔订单都没有发送到TWS。
3- 添加唯一的 orderid。我没有尝试过,因为我不确定它是否有效。我打印了订单,好像已经下单了。
我从您正在使用的相同广泛分布的样板代码开始。进行下面概述的更改后,我的代码可以运行。我缺乏专业知识来解释为什么会这样——但它确实如此。
假设您安装了以下软件: Python 3.10.7_64 萨尼克 22.6.2 ib_insync 0.9.71
(1) pip install --升级 sanic-cors sanic-plugin-toolkit 参考:IB_insync - 一个成功的订单后出现 Sanic 错误,阻止任何进一步的订单 (不确定是否需要)
(2)添加: 导入时间(参见下面的 clientId 注释)
(3)添加:
导入nest_asyncio
Nest_asyncio.apply()
#ref: RuntimeError: 此事件循环已在 python 中运行
(4) 在底部初始连接中使用异步连接(但不在重连区域中)
app.ib.connectAsync('127.0.0.1',7497,clientId=见下文) # 7946=live, 7947=paper
参考:IB_insync - 一个成功的订单后出现 Sanic 错误,阻止任何进一步的订单
(5) 在两个 connect 语句中使用时间派生出唯一的、升序的 clientId clientId=int(int((time.time()*1000)-1663849395690)/1000000)) 这有助于避免重新连接时出现“套接字正在使用”的情况
(6) 按照上面的建议添加 HTTPResponse 语句。
(7) Per Ewald de Wit,ib_insync 作者: "orderId无需设置,订单自动下发 已下订单。”
还有一个替代方案,在异步函数末尾返回
return response.json({})
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
当我尝试编译代码时遇到此错误
“不允许在 Sanic 实例上设置变量。您应该更改 Sanic 实例以使用 instance.ctx.ib。”
我正在使用 Visual Studio 2022
知道我需要在这里改变什么吗?
谢谢你