#![allow(clippy::unwrap_used, clippy::expect_used)]
use futures::StreamExt;
use rustrade_execution::{
AccountEventKind,
client::{
ExecutionClient,
binance::{BinanceMargin, BinanceMarginConfig},
},
};
use rustrade_instrument::instrument::name::InstrumentNameExchange;
use std::time::Duration;
fn env(name: &str) -> String {
std::env::var(name).unwrap_or_else(|_| panic!("{name} env var required"))
}
fn keys() -> (String, String) {
(
env("BINANCE_SUBACCOUNT_READONLY_API_KEY"),
env("BINANCE_SUBACCOUNT_READONLY_API_SECRET"),
)
}
fn listen_for() -> Duration {
let secs = std::env::var("BINANCE_MARGIN_STREAM_SECS")
.map(|secs| {
secs.parse()
.expect("BINANCE_MARGIN_STREAM_SECS is whole seconds")
})
.unwrap_or(75);
assert!(
secs >= 65,
"listen past the second heartbeat check, at 60 s, so a pong must keep the stream open"
);
Duration::from_secs(secs)
}
async fn stream_stays_open(config: BinanceMarginConfig, instruments: &[InstrumentNameExchange]) {
let _ = tracing_subscriber::fmt()
.with_env_filter("rustrade_execution=info")
.try_init();
let client = BinanceMargin::new(config);
let mut stream = client
.account_stream(&[], instruments)
.await
.expect("account_stream opens");
let deadline = tokio::time::Instant::now() + listen_for();
let mut kinds = Vec::new();
loop {
match tokio::time::timeout_at(deadline, stream.next()).await {
Err(_elapsed) => break,
Ok(None) => panic!("the stream ended after {kinds:?}"),
Ok(Some(event)) => {
let kind = match &event.kind {
AccountEventKind::StreamTerminated(reason) => {
panic!("the stream reported its end after {kinds:?}: {reason:?}")
}
AccountEventKind::BalanceStreamUpdate(_) => "BalanceStreamUpdate",
AccountEventKind::InstrumentBalanceUpdate(_) => "InstrumentBalanceUpdate",
AccountEventKind::OrderSnapshot(_) => "OrderSnapshot",
AccountEventKind::OrderCancelled(_) => "OrderCancelled",
AccountEventKind::Trade(_) => "Trade",
AccountEventKind::MarginDebt(_) => "MarginDebt",
AccountEventKind::Notice(_) => "Notice",
_ => "Other",
};
println!("stream event: {kind}");
kinds.push(kind);
}
}
}
println!("SUMMARY open after {:?}, events: {kinds:?}", listen_for());
}
#[tokio::test]
#[ignore]
async fn cross_margin_stream_stays_open_through_the_heartbeat() {
let (api_key, secret_key) = keys();
stream_stays_open(
BinanceMarginConfig::cross_margin(api_key, secret_key),
&[InstrumentNameExchange::new("BTCUSDT")],
)
.await;
}
#[tokio::test]
#[ignore]
async fn isolated_margin_stream_stays_open_through_the_heartbeat() {
let (api_key, secret_key) = keys();
let pair = InstrumentNameExchange::new(
std::env::var("BINANCE_MARGIN_STREAM_SYMBOL").unwrap_or_else(|_| "BTCUSDT".to_owned()),
);
stream_stays_open(
BinanceMarginConfig::isolated(api_key, secret_key, vec![pair]),
&[],
)
.await;
}