bybit_http/
http.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//! Demonstration binary for testing Bybit HTTP client endpoints.
17//!
18//! Run with:
19//! ```bash
20//! cargo run -p nautilus-bybit --bin bybit-http
21//! ```
22//!
23//! For authenticated endpoints, set environment variables:
24//! ```bash
25//! export BYBIT_API_KEY=your_key
26//! export BYBIT_API_SECRET=your_secret
27//! cargo run -p nautilus-bybit --bin bybit-http
28//! ```
29
30use nautilus_bybit::{
31    common::enums::{BybitKlineInterval, BybitProductType},
32    http::{
33        client::BybitHttpClient,
34        query::{
35            BybitInstrumentsInfoParamsBuilder, BybitKlinesParamsBuilder, BybitTradesParamsBuilder,
36        },
37    },
38};
39
40#[tokio::main]
41async fn main() -> anyhow::Result<()> {
42    tracing_subscriber::fmt()
43        .with_max_level(tracing::Level::DEBUG)
44        .init();
45
46    println!("=== Bybit HTTP Client Demo ===\n");
47
48    // Test public endpoints
49    test_public_endpoints().await?;
50
51    // Test authenticated endpoints if credentials are provided
52    let api_key = std::env::var("BYBIT_API_KEY").ok();
53    let api_secret = std::env::var("BYBIT_API_SECRET").ok();
54
55    if let (Some(key), Some(secret)) = (api_key, api_secret) {
56        println!("\n=== Testing Authenticated Endpoints ===");
57        test_authenticated_endpoints(&key, &secret).await?;
58    } else {
59        println!(
60            "\n[SKIP] Skipping authenticated endpoints (set BYBIT_API_KEY and BYBIT_API_SECRET to test)"
61        );
62    }
63
64    Ok(())
65}
66
67async fn test_public_endpoints() -> anyhow::Result<()> {
68    let client = BybitHttpClient::new(None, Some(60), None, None, None)?;
69
70    // Test 1: Get server time
71    println!("1. Testing GET /v5/market/time");
72    match client.http_get_server_time().await {
73        Ok(response) => {
74            println!(
75                "   [OK] Server time: {} (seconds)",
76                response.result.time_second
77            );
78            println!("   [OK] Server time: {} (nanos)", response.result.time_nano);
79        }
80        Err(e) => {
81            println!("   [ERROR] {e}");
82            return Err(e.into());
83        }
84    }
85
86    // Test 2: Get linear instruments
87    println!("\n2. Testing GET /v5/market/instruments-info (linear)");
88    let params = BybitInstrumentsInfoParamsBuilder::default()
89        .category(BybitProductType::Linear)
90        .symbol("BTCUSDT")
91        .build()?;
92
93    match client.http_get_instruments_linear(&params).await {
94        Ok(response) => {
95            println!("   [OK] Found {} instruments", response.result.list.len());
96            if let Some(first) = response.result.list.first() {
97                println!("   [OK] First instrument: {}", first.symbol);
98                println!("   [OK] Status: {:?}", first.status);
99            }
100        }
101        Err(e) => {
102            println!("   [ERROR] {e}");
103            return Err(e.into());
104        }
105    }
106
107    // Test 3: Get spot instruments
108    println!("\n3. Testing GET /v5/market/instruments-info (spot)");
109    let params = BybitInstrumentsInfoParamsBuilder::default()
110        .category(BybitProductType::Spot)
111        .limit(5u32)
112        .build()?;
113
114    match client.http_get_instruments_spot(&params).await {
115        Ok(response) => {
116            println!("   [OK] Found {} instruments", response.result.list.len());
117            for instrument in response.result.list.iter().take(3) {
118                println!("   - {}: {:?}", instrument.symbol, instrument.status);
119            }
120        }
121        Err(e) => {
122            println!("   [ERROR] {e}");
123            return Err(e.into());
124        }
125    }
126
127    // Test 4: Get klines
128    println!("\n4. Testing GET /v5/market/kline");
129    let params = BybitKlinesParamsBuilder::default()
130        .category(BybitProductType::Linear)
131        .symbol("BTCUSDT")
132        .interval(BybitKlineInterval::Minute1)
133        .limit(5u32)
134        .build()?;
135
136    match client.http_get_klines(&params).await {
137        Ok(response) => {
138            println!("   [OK] Found {} klines", response.result.list.len());
139            if let Some(first) = response.result.list.first() {
140                println!(
141                    "   [OK] First kline: O={}, H={}, L={}, C={}",
142                    first.open, first.high, first.low, first.close
143                );
144            }
145        }
146        Err(e) => {
147            println!("   [ERROR] {e}");
148            return Err(e.into());
149        }
150    }
151
152    // Test 5: Get recent trades
153    println!("\n5. Testing GET /v5/market/recent-trade");
154    let params = BybitTradesParamsBuilder::default()
155        .category(BybitProductType::Linear)
156        .symbol("BTCUSDT")
157        .limit(5u32)
158        .build()?;
159
160    match client.http_get_recent_trades(&params).await {
161        Ok(response) => {
162            println!("   [OK] Found {} recent trades", response.result.list.len());
163            for trade in response.result.list.iter().take(3) {
164                println!(
165                    "   - Price: {}, Size: {}, Side: {:?}",
166                    trade.price, trade.size, trade.side
167                );
168            }
169        }
170        Err(e) => {
171            println!("   [ERROR] {e}");
172            return Err(e.into());
173        }
174    }
175
176    println!("\n[SUCCESS] All public endpoint tests passed!");
177    Ok(())
178}
179
180async fn test_authenticated_endpoints(api_key: &str, api_secret: &str) -> anyhow::Result<()> {
181    let base_url = std::env::var("BYBIT_BASE_URL")
182        .unwrap_or_else(|_| "https://api-testnet.bybit.com".to_string());
183
184    let client = BybitHttpClient::with_credentials(
185        api_key.to_string(),
186        api_secret.to_string(),
187        Some(base_url),
188        Some(60),
189        None,
190        None,
191        None,
192    )?;
193
194    // Test 1: Get open orders
195    println!("\n1. Testing GET /v5/order/realtime (open orders)");
196    match client
197        .http_get_open_orders(BybitProductType::Linear, Some("BTCUSDT"))
198        .await
199    {
200        Ok(response) => {
201            println!("   [OK] Found {} open orders", response.result.list.len());
202            for order in response.result.list.iter().take(3) {
203                println!(
204                    "   - Order: {} | {:?} | {} @ {}",
205                    order.order_id, order.side, order.qty, order.price
206                );
207            }
208        }
209        Err(e) => {
210            println!("   [WARN] Error (may be expected if no orders): {e}");
211        }
212    }
213
214    // Test 2: Place order (commented out to avoid actual order placement)
215    println!("\n2. Testing POST /v5/order/create (order placement)");
216    println!("   [SKIP] Skipping actual order placement to avoid unintended trades");
217    println!("   [INFO] Uncomment in code to test order placement");
218
219    /*
220    let order_request = serde_json::json!({
221        "category": "linear",
222        "symbol": "BTCUSDT",
223        "side": "Buy",
224        "orderType": "Limit",
225        "qty": "0.001",
226        "price": "20000",
227        "timeInForce": "GTC",
228        "orderLinkId": format!("test-{}", chrono::Utc::now().timestamp())
229    });
230
231    match client.place_order(&order_request).await {
232        Ok(response) => {
233            println!("   [OK] Order placed successfully");
234            println!("   [OK] Order ID: {:?}", response.result.order_id);
235            println!("   [OK] Order Link ID: {:?}", response.result.order_link_id);
236        }
237        Err(e) => {
238            println!("   [ERROR] {e}");
239            return Err(e.into());
240        }
241    }
242    */
243
244    println!("\n[SUCCESS] Authenticated endpoint tests completed!");
245    Ok(())
246}