use std::time::{Duration, Instant};
use asterdex_sdk::{CancelOrderParams, ModifyOrderParams, PlaceOrderParams, FuturesClient};
const N: usize = 5;
const INTER_CALL_MS: u64 = 300;
fn build_client() -> FuturesClient {
let _ = dotenvy::dotenv();
FuturesClient::from_env().expect("FuturesClient::from_env() failed — check .env credentials")
}
struct Stats {
min: Duration,
max: Duration,
mean: Duration,
median: Duration,
p95: Duration,
}
fn compute_stats(samples: &[Duration]) -> Stats {
let mut sorted = samples.to_vec();
sorted.sort_unstable();
let n = sorted.len();
let sum: Duration = sorted.iter().sum();
Stats {
min: sorted[0],
max: sorted[n - 1],
mean: sum / n as u32,
median: sorted[n / 2],
p95: sorted[(n * 95 / 100).min(n - 1)],
}
}
fn print_report(label: &str, note: &str, samples: &[Duration]) {
let stats = compute_stats(samples);
println!();
println!(" {label} {note}");
print!(" Samples: ");
for (i, d) in samples.iter().enumerate() {
if i > 0 { print!(" "); }
print!("{:>5}ms", d.as_millis());
}
println!();
println!(
" Stats: min={:>4}ms max={:>4}ms mean={:>4}ms median={:>4}ms p95={:>4}ms",
stats.min.as_millis(),
stats.max.as_millis(),
stats.mean.as_millis(),
stats.median.as_millis(),
stats.p95.as_millis(),
);
}
async fn place_at(client: &FuturesClient, price: &str) -> i64 {
client
.place_order(PlaceOrderParams {
symbol: "BTCUSDT".to_string(),
side: "BUY".to_string(),
order_type: "LIMIT".to_string(),
quantity: "0.001".to_string(),
price: Some(price.to_string()),
time_in_force: Some("GTC".to_string()),
..Default::default()
})
.await
.expect("place_order failed")
.data
.order_id
}
async fn cancel(client: &FuturesClient, order_id: i64) {
let _ = client
.cancel_order(CancelOrderParams {
symbol: "BTCUSDT".to_string(),
order_id: Some(order_id),
..Default::default()
})
.await;
}
async fn delay() {
tokio::time::sleep(Duration::from_millis(INTER_CALL_MS)).await;
}
#[tokio::test]
#[ignore]
async fn bench_place_order() {
let client = build_client();
let mut timings = Vec::with_capacity(N);
let mut order_ids = Vec::with_capacity(N);
println!();
println!("========================================");
println!(" bench_place_order (n={N})");
println!("========================================");
for i in 0..N {
let price = format!("{}", 10_000 + i as u64);
let t = Instant::now();
let order_id = place_at(&client, &price).await;
let elapsed = t.elapsed();
println!(" run {:>2}: {:>5}ms (order_id={})", i + 1, elapsed.as_millis(), order_id);
timings.push(elapsed);
order_ids.push(order_id);
delay().await;
}
print_report("place_order", "(1 REST round trip)", &timings);
for oid in order_ids {
cancel(&client, oid).await;
delay().await;
}
}
#[tokio::test]
#[ignore]
async fn bench_cancel_order() {
let client = build_client();
let mut timings = Vec::with_capacity(N);
println!();
println!("========================================");
println!(" bench_cancel_order (n={N})");
println!("========================================");
let mut order_ids = Vec::with_capacity(N);
for i in 0..N {
let price = format!("{}", 10_100 + i as u64);
let oid = place_at(&client, &price).await;
order_ids.push(oid);
delay().await;
}
for (i, order_id) in order_ids.iter().enumerate() {
let t = Instant::now();
cancel(&client, *order_id).await;
let elapsed = t.elapsed();
println!(" run {:>2}: {:>5}ms (cancelled order_id={})", i + 1, elapsed.as_millis(), order_id);
timings.push(elapsed);
delay().await;
}
print_report("cancel_order", "(1 REST round trip)", &timings);
}
#[tokio::test]
#[ignore]
async fn bench_modify_order() {
let client = build_client();
let mut timings = Vec::with_capacity(N);
println!();
println!("========================================");
println!(" bench_modify_order (n={N})");
println!("========================================");
println!(" Note: modify = cancel + re-place (2 round trips, no native PUT endpoint)");
for i in 0..N {
let place_price = format!("{}", 10_200 + i as u64);
let order_id = place_at(&client, &place_price).await;
delay().await;
let modify_price = format!("{}", 10_500 + i as u64);
let t = Instant::now();
let new_order = client
.modify_order(ModifyOrderParams {
symbol: "BTCUSDT".to_string(),
quantity: Some("0.001".to_string()),
price: Some(modify_price),
order_id: Some(order_id),
..Default::default()
})
.await
.expect("modify_order failed");
let elapsed = t.elapsed();
println!(
" run {:>2}: {:>5}ms (orig={}, replacement={})",
i + 1,
elapsed.as_millis(),
order_id,
new_order.data.order_id
);
timings.push(elapsed);
cancel(&client, new_order.data.order_id).await;
delay().await;
}
print_report("modify_order", "(2 REST round trips: cancel + re-place)", &timings);
}
#[tokio::test]
#[ignore]
async fn bench_full_lifecycle() {
let client = build_client();
let mut timings = Vec::with_capacity(N);
println!();
println!("========================================");
println!(" bench_full_lifecycle (n={N})");
println!("========================================");
println!(" Sequence: place → modify → cancel (4 round trips total)");
for i in 0..N {
let place_price = format!("{}", 10_300 + i as u64);
let modify_price = format!("{}", 10_600 + i as u64);
let t = Instant::now();
let order_id = place_at(&client, &place_price).await;
let new_order = client
.modify_order(ModifyOrderParams {
symbol: "BTCUSDT".to_string(),
quantity: Some("0.001".to_string()),
price: Some(modify_price),
order_id: Some(order_id),
..Default::default()
})
.await
.expect("modify_order failed in lifecycle");
cancel(&client, new_order.data.order_id).await;
let elapsed = t.elapsed();
println!(" run {:>2}: {:>5}ms", i + 1, elapsed.as_millis());
timings.push(elapsed);
delay().await;
}
print_report(
"full_lifecycle",
"(place + modify + cancel = 4 round trips)",
&timings,
);
}
#[tokio::test]
#[ignore]
async fn bench_summary() {
let client = build_client();
println!();
println!("╔══════════════════════════════════════════════════════════════════╗");
println!("║ AsterDex Order Timing Benchmark (n={N} per operation) ║");
println!("║ Testnet: https://fapi.asterdex-testnet.com ║");
println!("╚══════════════════════════════════════════════════════════════════╝");
println!();
let mut place_timings = Vec::with_capacity(N);
let mut order_ids = Vec::with_capacity(N);
for i in 0..N {
let price = format!("{}", 10_000 + i as u64);
let t = Instant::now();
let oid = place_at(&client, &price).await;
place_timings.push(t.elapsed());
order_ids.push(oid);
delay().await;
}
let mut cancel_timings = Vec::with_capacity(N);
for oid in &order_ids {
let t = Instant::now();
cancel(&client, *oid).await;
cancel_timings.push(t.elapsed());
delay().await;
}
let mut modify_timings = Vec::with_capacity(N);
for i in 0..N {
let price_orig = format!("{}", 10_200 + i as u64);
let price_modify = format!("{}", 10_500 + i as u64);
let oid = place_at(&client, &price_orig).await;
delay().await;
let t = Instant::now();
let new_order = client
.modify_order(ModifyOrderParams {
symbol: "BTCUSDT".to_string(),
quantity: Some("0.001".to_string()),
price: Some(price_modify),
order_id: Some(oid),
..Default::default()
})
.await
.expect("modify_order failed in summary");
modify_timings.push(t.elapsed());
cancel(&client, new_order.data.order_id).await;
delay().await;
}
let mut lifecycle_timings = Vec::with_capacity(N);
for i in 0..N {
let price_orig = format!("{}", 10_300 + i as u64);
let price_modify = format!("{}", 10_600 + i as u64);
let t = Instant::now();
let oid = place_at(&client, &price_orig).await;
let new_order = client
.modify_order(ModifyOrderParams {
symbol: "BTCUSDT".to_string(),
quantity: Some("0.001".to_string()),
price: Some(price_modify),
order_id: Some(oid),
..Default::default()
})
.await
.expect("modify_order failed in lifecycle summary");
cancel(&client, new_order.data.order_id).await;
lifecycle_timings.push(t.elapsed());
delay().await;
}
let p = compute_stats(&place_timings);
let c = compute_stats(&cancel_timings);
let m = compute_stats(&modify_timings);
let l = compute_stats(&lifecycle_timings);
println!(
" {:<22} {:>6} {:>6} {:>6} {:>8} {:>6} {}",
"Operation", "min", "max", "mean", "median", "p95", "round trips"
);
println!(" {}", "-".repeat(75));
println!(
" {:<22} {:>5}ms {:>5}ms {:>5}ms {:>7}ms {:>5}ms {}",
"place_order",
p.min.as_millis(), p.max.as_millis(), p.mean.as_millis(),
p.median.as_millis(), p.p95.as_millis(), "1"
);
println!(
" {:<22} {:>5}ms {:>5}ms {:>5}ms {:>7}ms {:>5}ms {}",
"cancel_order",
c.min.as_millis(), c.max.as_millis(), c.mean.as_millis(),
c.median.as_millis(), c.p95.as_millis(), "1"
);
println!(
" {:<22} {:>5}ms {:>5}ms {:>5}ms {:>7}ms {:>5}ms {} (cancel + re-place)",
"modify_order",
m.min.as_millis(), m.max.as_millis(), m.mean.as_millis(),
m.median.as_millis(), m.p95.as_millis(), "2"
);
println!(
" {:<22} {:>5}ms {:>5}ms {:>5}ms {:>7}ms {:>5}ms {} (place+modify+cancel)",
"full_lifecycle",
l.min.as_millis(), l.max.as_millis(), l.mean.as_millis(),
l.median.as_millis(), l.p95.as_millis(), "4"
);
println!();
println!(" Raw samples (ms):");
let label_width = 22usize;
let print_row = |label: &str, samples: &[Duration]| {
print!(" {:<label_width$} ", label);
for d in samples {
print!("{:>5} ", d.as_millis());
}
println!();
};
print_row("place_order", &place_timings);
print_row("cancel_order", &cancel_timings);
print_row("modify_order", &modify_timings);
print_row("full_lifecycle",&lifecycle_timings);
println!();
}