hyperliquid_http_exec/
http_exec.rs1use std::{env, str::FromStr};
17
18use nautilus_hyperliquid::http::{
19 client::HyperliquidHttpClient,
20 models::{
21 HyperliquidExecAction, HyperliquidExecGrouping, HyperliquidExecLimitParams,
22 HyperliquidExecOrderKind, HyperliquidExecPlaceOrderRequest, HyperliquidExecTif,
23 },
24};
25use rust_decimal::Decimal;
26use rust_decimal_macros::dec;
27use tracing_subscriber::{EnvFilter, fmt};
28
29#[tokio::main]
30async fn main() -> Result<(), Box<dyn std::error::Error>> {
31 let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
32 fmt().with_target(false).with_env_filter(filter).init();
33
34 let _ = env::var("HYPERLIQUID_TESTNET_PK")
35 .expect("HYPERLIQUID_TESTNET_PK environment variable not set");
36
37 tracing::info!("Starting Hyperliquid Testnet Order Placer");
38
39 let client = match HyperliquidHttpClient::from_env() {
40 Ok(client) => {
41 tracing::info!("Client created (testnet: {})", client.is_testnet());
42 client
43 }
44 Err(e) => {
45 tracing::error!("Failed to create client: {}", e);
46 return Err(e.into());
47 }
48 };
49
50 tracing::info!("Fetching market metadata...");
51 let meta = client.info_meta().await?;
52
53 tracing::debug!("Available assets:");
55 for (idx, asset) in meta.universe.iter().enumerate() {
56 tracing::debug!(
57 " [{}] {} (sz_decimals: {})",
58 idx,
59 asset.name,
60 asset.sz_decimals
61 );
62 }
63
64 let btc_asset_id = meta
65 .universe
66 .iter()
67 .position(|asset| asset.name == "BTC")
68 .expect("BTC not found in universe");
69
70 tracing::info!("BTC asset ID: {}", btc_asset_id);
71 tracing::info!(
72 "BTC sz_decimals: {}",
73 meta.universe[btc_asset_id].sz_decimals
74 );
75
76 let wallet_address = client
78 .get_user_address()
79 .expect("Failed to get wallet address");
80 tracing::info!("Wallet address: {}", wallet_address);
81
82 tracing::info!("Fetching account state...");
84 match client.info_clearinghouse_state(&wallet_address).await {
85 Ok(state) => {
86 tracing::info!(
87 "Account state: {}",
88 serde_json::to_string_pretty(&state).unwrap_or_else(|_| "N/A".to_string())
89 );
90 }
91 Err(e) => {
92 tracing::warn!("Failed to fetch account state: {}", e);
93 }
94 }
95
96 tracing::info!("Fetching BTC order book...");
97 let book = client.info_l2_book("BTC").await?;
98
99 let best_bid_str = &book.levels[0][0].px;
100 let best_bid = Decimal::from_str(best_bid_str)?;
101
102 tracing::info!("Best bid: ${}", best_bid);
103
104 let limit_price = (best_bid * dec!(0.95)).round();
106 tracing::info!("Limit order price: ${}", limit_price);
107
108 let order = HyperliquidExecPlaceOrderRequest {
109 asset: btc_asset_id as u32,
110 is_buy: true,
111 price: limit_price,
112 size: dec!(0.001),
113 reduce_only: false,
114 kind: HyperliquidExecOrderKind::Limit {
115 limit: HyperliquidExecLimitParams {
116 tif: HyperliquidExecTif::Gtc,
117 },
118 },
119 cloid: None,
120 };
121
122 tracing::info!("Order details:");
123 tracing::info!(" Asset: {} (BTC)", btc_asset_id);
124 tracing::info!(" Side: BUY");
125 tracing::info!(" Price: ${}", limit_price);
126 tracing::info!(" Size: 0.001 BTC");
127
128 tracing::info!("Placing order...");
129
130 let action = HyperliquidExecAction::Order {
132 orders: vec![order],
133 grouping: HyperliquidExecGrouping::Na,
134 builder: None,
135 };
136
137 tracing::debug!("ExchangeAction: {:?}", action);
138
139 if let Ok(action_json) = serde_json::to_value(&action) {
141 tracing::debug!(
142 "Action JSON: {}",
143 serde_json::to_string_pretty(&action_json)?
144 );
145 }
146
147 match client.post_action_exec(&action).await {
148 Ok(response) => {
149 tracing::info!("Order placed successfully!");
150 tracing::info!("Response: {:#?}", response);
151
152 if let Ok(json) = serde_json::to_string_pretty(&response) {
154 tracing::info!("Response JSON:\n{}", json);
155 }
156 }
157 Err(e) => {
158 tracing::error!("Failed to place order: {}", e);
159 tracing::error!("Error details: {:?}", e);
160 return Err(e.into());
161 }
162 }
163 tracing::info!("Done!");
164 Ok(())
165}