dydx_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//! Manual verification script for dYdX HTTP public endpoints.
17//!
18//! Tests all historical request methods including direct access to inner HTTP client
19//! for trades and candles endpoints.
20//!
21//! Usage:
22//! ```bash
23//! # Test against testnet (default)
24//! cargo run --bin dydx-http-public -p nautilus-dydx
25//!
26//! # Test against mainnet
27//! DYDX_HTTP_URL=https://indexer.dydx.trade cargo run --bin dydx-http-public -p nautilus-dydx
28//! ```
29
30use chrono::{Duration, Utc};
31use nautilus_dydx::{
32    common::{consts::DYDX_TESTNET_HTTP_URL, enums::DydxCandleResolution},
33    http::client::DydxHttpClient,
34};
35use nautilus_model::instruments::Instrument;
36use ustr::Ustr;
37
38#[tokio::main]
39async fn main() -> Result<(), Box<dyn std::error::Error>> {
40    tracing_subscriber::fmt()
41        .with_max_level(tracing::Level::INFO)
42        .init();
43
44    let base_url =
45        std::env::var("DYDX_HTTP_URL").unwrap_or_else(|_| DYDX_TESTNET_HTTP_URL.to_string());
46    let is_testnet = base_url.contains("testnet");
47
48    tracing::info!("Connecting to dYdX HTTP API: {}", base_url);
49    tracing::info!(
50        "Environment: {}",
51        if is_testnet { "TESTNET" } else { "MAINNET" }
52    );
53    tracing::info!("");
54
55    let client = DydxHttpClient::new(Some(base_url), Some(30), None, is_testnet, None)?;
56
57    let start = std::time::Instant::now();
58    let instruments = client.request_instruments(None, None, None).await?;
59    let elapsed = start.elapsed();
60
61    tracing::info!(
62        "SUCCESS: Fetched {} instruments in {:.2}s",
63        instruments.len(),
64        elapsed.as_secs_f64()
65    );
66
67    if !instruments.is_empty() {
68        tracing::info!("   Sample instruments:");
69        for inst in instruments.iter().take(5) {
70            tracing::info!("   - {} ({})", inst.id().symbol, inst.instrument_class());
71        }
72        if instruments.len() > 5 {
73            tracing::info!("   ... and {} more", instruments.len() - 5);
74        }
75    }
76
77    client.cache_instruments(instruments.clone());
78    tracing::info!("Cached {} instruments", instruments.len());
79
80    let symbol = Ustr::from("BTC-USD");
81    let start = std::time::Instant::now();
82    let instrument = client.get_instrument(&symbol);
83    let elapsed = start.elapsed();
84
85    match instrument {
86        Some(inst) => {
87            tracing::info!(
88                "SUCCESS: Found {} in cache in {:.4}ms",
89                inst.id(),
90                elapsed.as_micros() as f64 / 1000.0
91            );
92            tracing::info!("   Type: {}", inst.instrument_class());
93            tracing::info!("   Price precision: {}", inst.price_precision());
94            tracing::info!("   Size precision: {}", inst.size_precision());
95        }
96        None => {
97            tracing::warn!("FAILED: Instrument {} not found in cache", symbol);
98        }
99    }
100
101    let symbol = "BTC-USD";
102    let limit = Some(100);
103
104    let start = std::time::Instant::now();
105    let trades = client.request_trades(symbol, limit).await?;
106    let elapsed = start.elapsed();
107
108    tracing::info!(
109        "SUCCESS: Fetched {} trades for {} in {:.2}s",
110        trades.trades.len(),
111        symbol,
112        elapsed.as_secs_f64()
113    );
114
115    if !trades.trades.is_empty() {
116        let first = &trades.trades[0];
117        let last = &trades.trades[trades.trades.len() - 1];
118        tracing::info!(
119            "   First trade: {} @ {} ({})",
120            first.size,
121            first.price,
122            first.side
123        );
124        tracing::info!(
125            "   Last trade:  {} @ {} ({})",
126            last.size,
127            last.price,
128            last.side
129        );
130        tracing::info!("   Time range: {} to {}", first.created_at, last.created_at);
131    }
132
133    let resolution = DydxCandleResolution::OneMinute;
134    let end_time = Utc::now();
135    let start_time = end_time - Duration::hours(2); // 2 hours = ~120 bars
136
137    let start = std::time::Instant::now();
138    let candles = client
139        .request_candles(symbol, resolution, None, Some(start_time), Some(end_time))
140        .await?;
141    let elapsed = start.elapsed();
142
143    tracing::info!(
144        "SUCCESS: Fetched {} candles for {} ({:?}) in {:.2}s",
145        candles.candles.len(),
146        symbol,
147        resolution,
148        elapsed.as_secs_f64()
149    );
150
151    if !candles.candles.is_empty() {
152        let first = &candles.candles[0];
153        let last = &candles.candles[candles.candles.len() - 1];
154        tracing::info!(
155            "   First candle: O={} H={} L={} C={} V={}",
156            first.open,
157            first.high,
158            first.low,
159            first.close,
160            first.base_token_volume
161        );
162        tracing::info!(
163            "   Last candle:  O={} H={} L={} C={} V={}",
164            last.open,
165            last.high,
166            last.low,
167            last.close,
168            last.base_token_volume
169        );
170        tracing::info!("   Time range: {} to {}", first.started_at, last.started_at);
171    }
172
173    let end_time = Utc::now();
174    let start_time = end_time - Duration::days(7); // 7 days
175
176    tracing::info!(
177        "   Requesting {:?} bars from {} to {}",
178        resolution,
179        start_time,
180        end_time
181    );
182
183    let start = std::time::Instant::now();
184    let candles_large = client
185        .request_candles(symbol, resolution, None, Some(start_time), Some(end_time))
186        .await?;
187    let elapsed = start.elapsed();
188
189    let expected_bars_large = ((end_time - start_time).num_minutes() as usize).min(10_080);
190    let coverage_large = (candles_large.candles.len() as f64 / expected_bars_large as f64) * 100.0;
191
192    tracing::info!(
193        "SUCCESS: Fetched {} candles in {:.2}s ({:.0} bars/sec)",
194        candles_large.candles.len(),
195        elapsed.as_secs_f64(),
196        candles_large.candles.len() as f64 / elapsed.as_secs_f64()
197    );
198
199    if !candles_large.candles.is_empty() {
200        tracing::info!("   Coverage: {:.1}% of expected bars", coverage_large);
201        tracing::info!(
202            "   Time range: {} to {}",
203            candles_large.candles[0].started_at,
204            candles_large.candles[candles_large.candles.len() - 1].started_at
205        );
206    }
207    tracing::info!("");
208
209    tracing::info!("ALL TESTS COMPLETED SUCCESSFULLY");
210    tracing::info!("");
211    tracing::info!("Summary:");
212    tracing::info!(
213        "  [PASS] request_instruments: {} instruments",
214        instruments.len()
215    );
216    tracing::info!("  [PASS] get_instrument: Cache lookup works");
217    tracing::info!(
218        "  [PASS] get_trades: {} trades fetched",
219        trades.trades.len()
220    );
221    tracing::info!(
222        "  [PASS] get_candles (small): {} candles",
223        candles.candles.len()
224    );
225    tracing::info!(
226        "  [PASS] get_candles (large): {} candles with {:.1}% coverage",
227        candles_large.candles.len(),
228        coverage_large
229    );
230
231    Ok(())
232}