Websocket実装に向けて非同期処理を勉強する(WebSocket)
前回に引き継ぎ、まちゅけんさんの記事を見ながら勉強。今回はWebSocket。
WebSocketの記事は流し読みして、pybotterライブラリを使うところにいきなり突入。
これまでWebSocketがうまく動作しなかった理由がようやくわかる
これまで、いろんなラッパーを使おうとするも、どうしてもエラーが出て使えなかったが、ようやくわかった。
Jupyter NotebookとPythonでは、微妙に非同期処理の使い方が違うらしい。
import asyncio import pybotters apis = {"bybit": ["YOUR_API_KEY", "YOUR_API_SECRET"]} async def main(): async with pybotters.Client(base_url="https://api.bybit.com", apis=apis) as client: r = await client.get("/v2/public/tickers", params={"symbol": "BTCUSD"}) data = await r.json() price = round(float(data["result"][0]["bid_price"]) - 300.0, 1) r = await client.post( "/v2/private/order/create", data={"symbol": "BTCUSD", "side": "Buy", "order_type": "Limit", "qty": 1, "price": price, "time_in_force": "PostOnly"}, ) data = await r.json() print(data) asyncio.run(main())
上記コードをFTX用に書き換えて実行したら、下記エラーが出た。
asyncio.run() cannot be called from a running event loop
なんでか調べたところ、こちらに行き着いた。
The asyncio.run() documentation says:
This function cannot be called when another asyncio event loop is running in the same thread.
The problem in your case is that jupyter (IPython) is already running an event loop (for IPython ≥ 7.0):
You can now use async/await at the top level in the IPython terminal and in the notebook, it should — in most of the cases — “just work”. Update IPython to version 7+, IPykernel to version 5+, and you’re off to the races.
That's the reason why you don't need to start the event loop yourself in jupyter and you can directly call await main(url) even outside asynchronous functions.
In jupyter
async def main():
print(1)
await main()
In plain Python (≥3.7)import asyncio
async def main():
print(1)
asyncio.run(main())
Jupyter Notebookでは、プログラムとは別にEvent Loopを回しているようで、
それとは別にEvent Loopにタスクを突っ込んで回そうとしてもだめらしい。
今回は最終的にPythonとして実行するため、Jupyter NotebookではなくPythonで開発するように変更した。
エンドポイント設定でハマる
Pythonでサンプルコードを変えて入れていたが、ずっとNot Logged Inとエラーが出てハマる。
FTX側のホワイトリスト設定も見てみるが、特に問題はない。
どうもわからなかったので、もしかしてPybottersのAuthentificationにミスが有るのでは?と思って見るがバグを発見できず。
しばらく試行錯誤して、base_url = 'https://ftx.com/api/'となっており、r = await client.get('/positions')としていたことに気づく。
もしかして"/"が重複してるのでは?と思い片方削ると無事成功。
えー、、、URL間違えてもNot Logged Inって出るの・・・。Authentificationミスのときだけそのエラーが出てほしかった。
self設定でハマる
なんか引数が足りんと言われるなぁ・・・と思ってると、Classの中の関数で、selfを引数に設定し忘れていた。
そんなわけて一応WebSocket動作
import time import asyncio import pybotters class FTX(): # 定数 URLS = {'REST': 'https://ftx.com/api/', 'WebSocket': 'wss://ftx.com/ws/', } def __init__(self, api_key = None, api_secret = None, subaccount = None): # APIキー、Secretをセット self.api_key = api_key self.api_secret = api_secret self.subaccount = subaccount self.apis = { 'ftx': [self.api_key, self.api_secret], } # ------------------------------------------------ # # WebSocket # ------------------------------------------------ # async def main(self): async with pybotters.Client(base_url = self.URLS['REST'], apis = self.apis, headers={'FTX-SUBACCOUNT': self.subaccount}) as client: symbol = 'BTC-PERP' await client.ws_connect( self.URLS['WebSocket'], send_json=[ {'op': 'subscribe', 'channel': 'orders'}, {'op': 'subscribe', 'channel': 'fills'}, ], hdlr_json=store.onmessage,) while True: start = time.time() await store.orders.wait() orders = store.orders.find() print(orders) await asyncio.sleep(5) end = time.time() elpsed_time = end - start print(elpsed_time) try: _api_key = '' _api_secret = '' _subaccount = '' ftx = FTX(_api_key, _api_secret, _subaccount) asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) # need windowsOS python3.8 asyncio.run(ftx.main()) except Exception as e: print('=== エラー内容 ===') print(f'{str(e)}') except KeyboardInterrupt: pass
あとはこいつを使ってラッパー化していくつもり。