Fix Binance options WS endpoint and connected flag.
Use fstream /public/stream with lowercase bookTicker streams; keep market connected if futures WS is up. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -37,8 +37,8 @@ class Settings(BaseSettings):
|
|||||||
binance_fapi_base: str = "https://fapi.binance.com"
|
binance_fapi_base: str = "https://fapi.binance.com"
|
||||||
binance_eapi_base: str = "https://eapi.binance.com"
|
binance_eapi_base: str = "https://eapi.binance.com"
|
||||||
binance_futures_ws: str = "wss://fstream.binance.com/stream"
|
binance_futures_ws: str = "wss://fstream.binance.com/stream"
|
||||||
# 官方默认根路径 /eoptions;组合流用 /eoptions/stream?streams=
|
# 欧式期权公共流(2025-12 起):/public/stream;旧 nbstream/eoptions 已 404
|
||||||
binance_options_ws: str = "wss://nbstream.binance.com/eoptions/stream"
|
binance_options_ws: str = "wss://fstream.binance.com/public/stream"
|
||||||
binance_http_proxy: str = ""
|
binance_http_proxy: str = ""
|
||||||
|
|
||||||
perp_inst_id: str = "ETH-USDT-SWAP"
|
perp_inst_id: str = "ETH-USDT-SWAP"
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ class BinancePublicWs:
|
|||||||
self._inst_ids: list[str] = []
|
self._inst_ids: list[str] = []
|
||||||
self._tasks: list[asyncio.Task[None]] = []
|
self._tasks: list[asyncio.Task[None]] = []
|
||||||
self._stop = asyncio.Event()
|
self._stop = asyncio.Event()
|
||||||
|
self._alive: set[str] = set()
|
||||||
|
|
||||||
def set_instruments(self, inst_ids: list[str]) -> None:
|
def set_instruments(self, inst_ids: list[str]) -> None:
|
||||||
self._inst_ids = [i for i in inst_ids if i]
|
self._inst_ids = [i for i in inst_ids if i]
|
||||||
@@ -49,6 +50,14 @@ class BinancePublicWs:
|
|||||||
perps.append(i.upper())
|
perps.append(i.upper())
|
||||||
return perps, opts
|
return perps, opts
|
||||||
|
|
||||||
|
def _mark_alive(self, kind: str, ok: bool) -> None:
|
||||||
|
if ok:
|
||||||
|
self._alive.add(kind)
|
||||||
|
else:
|
||||||
|
self._alive.discard(kind)
|
||||||
|
# 任一腿连上即认为行情可用(期权腿偶发失败时仍可用 REST/永续)
|
||||||
|
self.cache.set_connected(bool(self._alive))
|
||||||
|
|
||||||
async def start(self) -> None:
|
async def start(self) -> None:
|
||||||
if self._tasks and any(not t.done() for t in self._tasks):
|
if self._tasks and any(not t.done() for t in self._tasks):
|
||||||
return
|
return
|
||||||
@@ -65,6 +74,7 @@ class BinancePublicWs:
|
|||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
pass
|
pass
|
||||||
self._tasks = []
|
self._tasks = []
|
||||||
|
self._alive.clear()
|
||||||
self.cache.set_connected(False)
|
self.cache.set_connected(False)
|
||||||
|
|
||||||
async def resubscribe(self, inst_ids: list[str]) -> None:
|
async def resubscribe(self, inst_ids: list[str]) -> None:
|
||||||
@@ -76,13 +86,15 @@ class BinancePublicWs:
|
|||||||
async def _spawn(self) -> None:
|
async def _spawn(self) -> None:
|
||||||
perps, opts = self._split()
|
perps, opts = self._split()
|
||||||
self._tasks = []
|
self._tasks = []
|
||||||
|
self._alive.clear()
|
||||||
if perps:
|
if perps:
|
||||||
url = self._combined_url(self.futures_ws_base, [f"{p.lower()}@bookTicker" for p in perps])
|
url = self._combined_url(self.futures_ws_base, [f"{p.lower()}@bookTicker" for p in perps])
|
||||||
self._tasks.append(
|
self._tasks.append(
|
||||||
asyncio.create_task(self._run_forever(url, kind="futures"), name="bn-fapi-ws")
|
asyncio.create_task(self._run_forever(url, kind="futures"), name="bn-fapi-ws")
|
||||||
)
|
)
|
||||||
if opts:
|
if opts:
|
||||||
streams = [f"{s}@bookTicker" for s in opts]
|
# 期权 stream 名必须小写:eth-260726-1850-c@bookTicker
|
||||||
|
streams = [f"{s.lower()}@bookTicker" for s in opts]
|
||||||
url = self._combined_url(self.options_ws_base, streams)
|
url = self._combined_url(self.options_ws_base, streams)
|
||||||
self._tasks.append(
|
self._tasks.append(
|
||||||
asyncio.create_task(self._run_forever(url, kind="options"), name="bn-eapi-ws")
|
asyncio.create_task(self._run_forever(url, kind="options"), name="bn-eapi-ws")
|
||||||
@@ -127,9 +139,9 @@ class BinancePublicWs:
|
|||||||
while not self._stop.is_set():
|
while not self._stop.is_set():
|
||||||
try:
|
try:
|
||||||
async with await self._open_connection(url) as ws:
|
async with await self._open_connection(url) as ws:
|
||||||
self.cache.set_connected(True)
|
self._mark_alive(kind, True)
|
||||||
backoff = 1.0
|
backoff = 1.0
|
||||||
logger.info("Binance %s WS connected: %s", kind, url[:120])
|
logger.info("Binance %s WS connected: %s", kind, url[:160])
|
||||||
waiter = asyncio.create_task(self._stop.wait())
|
waiter = asyncio.create_task(self._stop.wait())
|
||||||
reader = asyncio.create_task(self._read_loop(ws))
|
reader = asyncio.create_task(self._read_loop(ws))
|
||||||
pinger = asyncio.create_task(self._ping_loop(ws))
|
pinger = asyncio.create_task(self._ping_loop(ws))
|
||||||
@@ -147,13 +159,13 @@ class BinancePublicWs:
|
|||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("Binance %s WS disconnected: %s", kind, e)
|
logger.warning("Binance %s WS disconnected: %s", kind, e)
|
||||||
self.cache.set_connected(False)
|
self._mark_alive(kind, False)
|
||||||
try:
|
try:
|
||||||
await asyncio.wait_for(self._stop.wait(), timeout=backoff)
|
await asyncio.wait_for(self._stop.wait(), timeout=backoff)
|
||||||
break
|
break
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
backoff = min(backoff * 2, 30.0)
|
backoff = min(backoff * 2, 30.0)
|
||||||
self.cache.set_connected(False)
|
self._mark_alive(kind, False)
|
||||||
|
|
||||||
async def _ping_loop(self, ws: ClientConnection) -> None:
|
async def _ping_loop(self, ws: ClientConnection) -> None:
|
||||||
while True:
|
while True:
|
||||||
|
|||||||
Reference in New Issue
Block a user