-
Notifications
You must be signed in to change notification settings - Fork 75
Description
After running successfully for 2 weeks, I'm now experiencing constant WebSocket timeout error when calling ws_api.account_information(AccountInformationParams::default()). The same call that worked reliably for 2 weeks started failing consistently about 12 hours ago.
Environment
- Binance SDK 35.0.0
- API: Binance Derivatives Trading USDS Futures WebSocket API
- Timeout Configuration: 2000ms
- Runtime: Worked successfully for ~2 weeks before failing
Core Loop:
- Build WebSocket connections (websocket_stream_handle and websocket_api_handle)
- Spawn threads to listen to UserUpdate and TickerUpdate streams and the trading algorithm
- Block on restart_listener until a thread signals that connections need to be rebuilt
- On errors (like WebSocket timeout), restart the entire loop and rebuild connections
WebSocket API Handle Creation:
pub async fn websocket_api_handle(
test_net: bool,
pub_key: &str,
private_key: &str,
) -> Result<WebsocketApi, Report> {
let cnf = ConfigurationWebsocketApi::builder()
.api_key(pub_key)
.api_secret(private_key)
.timeout(2000)
.build()?;
let handle = match test_net {
true => DerivativesTradingUsdsFuturesWsApi::testnet(cnf),
false => DerivativesTradingUsdsFuturesWsApi::production(cnf),
};
handle
.connect()
.await
.map_err(|e| eyre!("Error connecting to WebsocketApiHandle: {e}"))
}
The api_handle is cloned to multiple sub-threads (keep-alive listener, trading algorithm, etc.).
When it occurs:
In my trading algorithm, when the UserData stream receives updates (order updates, balance changes), I set a flag to sync state. The sync happens in this function:
async fn sync_binance_state(
&mut self,
ws_api: &WebsocketApi,
force: bool,
) -> Result<(), Report> {
if force
|| self.binance_sync.elapsed().as_secs() > 60
|| get_account_state_changed().await?
{
// THIS CALL NOW CONSTANTLY TIMES OUT:
let result = ws_api
.account_information(AccountInformationParams::default())
.await
.map_err(|e| eyre!("Error updating account_information: {e}"))?;
let data = result.data()?;
// Update balance
for asset in data.assets.iter().flatten() {
if asset.asset.as_ref() == Some(&self.asset) {
update_balance(asset.wallet_balance.as_deref().unwrap_or("0")).await?;
}
}
// Update positions
for position in data.positions.iter().flatten() {
if position.symbol.as_ref() == Some(&self.symbol) {
let pos_amt = position.position_amt.as_deref().unwrap_or("0");
let entry_price = position.entry_price.as_deref().unwrap_or("0.0");
let pos_side = position
.position_side
.as_deref()
.ok_or_else(|| eyre!("position_side is missing"))?;
let ts = position.update_time.unwrap_or(0);
update_position(pos_amt, entry_price, pos_side, ts).await?;
}
}
set_account_state_synced().await?;
self.binance_sync = Instant::now();
Ok(())
}
}
Temporary workaround:
Commenting out the account_information() call allows the program to run successfully. (but without balance/position information). The program then tried to create orders (using the same WebSocket API handle and it returned expected errors (it tried to sell an position I don't actively have because the state is not synced and the trading algorithm thinks it has one. The error is an expected error response considering an order that can not be executed)
Questions:
- Is there a rate limit on account_information() calls that I'm hitting after 2 weeks?
- Am I misusing the WebSocket API handle by cloning it across multiple threads?
- Could there be connection pooling or reuse issues that manifest after extended runtime?
- Should I be recreating the connection more frequently, or is my error recovery strategy (rebuild all connections on timeout) incorrect?
The sync is triggered either:
- When account state changes (from UserData events)
- Every 60 seconds as a fallback
- When forced
Any insights into what might have changed or what I'm doing wrong would be greatly appreciated!