binance_spot_http_public/
http_public.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Test binary for Binance Spot HTTP client with SBE encoding.
17//!
18//! This binary tests the public endpoints against the live Binance API
19//! to verify that SBE request/response handling works correctly.
20//!
21//! # Usage
22//!
23//! ```bash
24//! cargo run --bin binance-spot-http-public
25//! ```
26
27use nautilus_binance::{
28    common::enums::BinanceEnvironment,
29    spot::http::{BinanceSpotHttpClient, DepthParams, TradesParams},
30};
31use tracing_subscriber::EnvFilter;
32
33#[tokio::main]
34async fn main() -> anyhow::Result<()> {
35    // Initialize logging
36    tracing_subscriber::fmt()
37        .with_env_filter(
38            EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")),
39        )
40        .init();
41
42    tracing::info!("Starting Binance Spot SBE HTTP client test");
43    tracing::info!(
44        "Using SBE schema version: {}:{}",
45        BinanceSpotHttpClient::schema_id(),
46        BinanceSpotHttpClient::schema_version()
47    );
48
49    // Create client (no credentials needed for public endpoints)
50    let client = BinanceSpotHttpClient::new(
51        BinanceEnvironment::Mainnet,
52        None,     // api_key
53        None,     // api_secret
54        None,     // base_url_override
55        None,     // recv_window
56        Some(30), // timeout_secs
57        None,     // proxy_url
58    )?;
59
60    // Test 1: Ping
61    tracing::info!("=== Test 1: Ping ===");
62    match client.ping().await {
63        Ok(()) => tracing::info!("Ping successful"),
64        Err(e) => tracing::error!("Ping failed: {e}"),
65    }
66
67    // Test 2: Server Time
68    // Note: SBE returns microsecond timestamps
69    tracing::info!("=== Test 2: Server Time ===");
70    match client.server_time().await {
71        Ok(timestamp_us) => {
72            let timestamp_ms = timestamp_us / 1000;
73            let datetime = chrono::DateTime::from_timestamp_millis(timestamp_ms).map_or_else(
74                || "invalid timestamp".to_string(),
75                |dt| dt.format("%Y-%m-%d %H:%M:%S%.3f UTC").to_string(),
76            );
77            tracing::info!("Server time: {timestamp_us} µs ({datetime})");
78        }
79        Err(e) => tracing::error!("Server time failed: {e}"),
80    }
81
82    // Test 3: Depth (Order Book)
83    tracing::info!("=== Test 3: Depth (BTCUSDT) ===");
84    let depth_params = DepthParams::new("BTCUSDT").with_limit(5);
85    match client.depth(&depth_params).await {
86        Ok(depth) => {
87            tracing::info!("Last update ID: {}", depth.last_update_id);
88            tracing::info!(
89                "Price exponent: {}, Qty exponent: {}",
90                depth.price_exponent,
91                depth.qty_exponent
92            );
93            tracing::info!("Bids ({} levels):", depth.bids.len());
94            for (i, level) in depth.bids.iter().take(5).enumerate() {
95                let price = level.price_f64(depth.price_exponent);
96                let qty = level.qty_f64(depth.qty_exponent);
97                tracing::info!("  [{i}] Price: {price:.2}, Qty: {qty:.8}");
98            }
99            tracing::info!("Asks ({} levels):", depth.asks.len());
100            for (i, level) in depth.asks.iter().take(5).enumerate() {
101                let price = level.price_f64(depth.price_exponent);
102                let qty = level.qty_f64(depth.qty_exponent);
103                tracing::info!("  [{i}] Price: {price:.2}, Qty: {qty:.8}");
104            }
105        }
106        Err(e) => tracing::error!("Depth failed: {e}"),
107    }
108
109    // Test 4: Recent Trades
110    tracing::info!("=== Test 4: Trades (BTCUSDT) ===");
111    let trades_params = TradesParams::new("BTCUSDT").with_limit(5);
112    match client.trades(&trades_params).await {
113        Ok(trades) => {
114            tracing::info!(
115                "Price exponent: {}, Qty exponent: {}",
116                trades.price_exponent,
117                trades.qty_exponent
118            );
119            tracing::info!("Trades ({} total):", trades.trades.len());
120            for trade in trades.trades.iter().take(5) {
121                let price = trade.price_f64(trades.price_exponent);
122                let qty = trade.qty_f64(trades.qty_exponent);
123                let side = if trade.is_buyer_maker { "SELL" } else { "BUY" };
124                let datetime = chrono::DateTime::from_timestamp_millis(trade.time).map_or_else(
125                    || "?".to_string(),
126                    |dt| dt.format("%H:%M:%S%.3f").to_string(),
127                );
128                tracing::info!(
129                    "  ID: {}, {side} {qty:.8} @ {price:.2} at {datetime}",
130                    trade.id
131                );
132            }
133        }
134        Err(e) => tracing::error!("Trades failed: {e}"),
135    }
136
137    // Test 5: Depth for another symbol (ETHUSDT)
138    tracing::info!("=== Test 5: Depth (ETHUSDT) ===");
139    let depth_params = DepthParams::new("ETHUSDT").with_limit(3);
140    match client.depth(&depth_params).await {
141        Ok(depth) => {
142            tracing::info!("Last update ID: {}", depth.last_update_id);
143            tracing::info!(
144                "Best bid: {:.2}",
145                depth
146                    .bids
147                    .first()
148                    .map_or(0.0, |l| l.price_f64(depth.price_exponent))
149            );
150            tracing::info!(
151                "Best ask: {:.2}",
152                depth
153                    .asks
154                    .first()
155                    .map_or(0.0, |l| l.price_f64(depth.price_exponent))
156            );
157        }
158        Err(e) => tracing::error!("Depth (ETHUSDT) failed: {e}"),
159    }
160
161    tracing::info!("=== All tests completed ===");
162
163    Ok(())
164}