-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathadvanced_order.rs
More file actions
163 lines (137 loc) · 5.29 KB
/
Copy pathadvanced_order.rs
File metadata and controls
163 lines (137 loc) · 5.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#![allow(clippy::too_many_lines)]
use rhyperliquid::{
example_helpers::load_signer,
init_tracing::init_tracing,
response::ResponseInner,
types::exchange::{order::Tpsl, Grouping, OrderRequest, OrderType, TriggerOrder},
utils::current_time_millis,
HyperliquidClientBuilder, HyperliquidError,
};
use rust_decimal::prelude::*;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
init_tracing();
let pkey_signer = load_signer();
let mut builder = HyperliquidClientBuilder::new();
let hyperliquid = builder.testnet().with_wallet(pkey_signer.clone()).build()?;
let info_api = hyperliquid.info();
let exchange_api = hyperliquid.exchange();
let perp_meta = info_api.perpetuals_metadata(None).await?;
let doge_asset_and_idx = perp_meta
.universe
.iter()
.enumerate()
.find(|asset| asset.1.name == "DOGE");
let (asset_idx, _) = doge_asset_and_idx.ok_or(HyperliquidError::Internal(
"Missing asset in universe".to_string(),
))?;
let asset_id = format!("@{}", asset_idx);
// Get current price
let all_mids = info_api.all_mids(None).await?;
let doge_price = all_mids.0.get(&asset_id).ok_or(HyperliquidError::Internal(
"Missing asset in universe".to_string(),
))?;
let price_decimal = Decimal::from_str(&doge_price.to_string())?;
// Set stop loss 5% below current price
tracing::info!("\n=== Placing Stop Loss Order ===");
let stop_loss_price = price_decimal.saturating_mul(Decimal::from_str("0.95")?); // 5% below
let stop_loss_trigger = stop_loss_price.round_dp(5).to_string();
let stop_loss_order = OrderRequest {
a: u32::try_from(asset_idx)?,
b: true, // SELL when triggered
p: stop_loss_trigger.clone(), // Execution price
s: "10.0".to_string(),
r: true,
t: OrderType::Trigger(TriggerOrder {
is_market: true, // Execute as market order when triggered
trigger_px: stop_loss_trigger.clone(),
tpsl: Tpsl::Sl,
}),
c: None,
};
tracing::info!(
"Stop loss will trigger at ${} (5% below current)",
stop_loss_trigger
);
let sl_response = exchange_api
.place_order(stop_loss_order, Grouping::Na, None, None, None)
.await?;
match sl_response.response {
ResponseInner::Error(e) => {
tracing::error!("Failed to place stop loss: {}", e);
}
ResponseInner::Ok(order_data) => {
tracing::info!("Stop loss placed: {:?}", order_data);
}
}
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
// Set take profit 10% above current price
tracing::info!("\n=== Placing Take Profit Order ===");
let take_profit_price = price_decimal.saturating_mul(Decimal::from_str("1.10")?); // 10% above
let take_profit_trigger = take_profit_price.round_dp(5).to_string();
let take_profit_order = OrderRequest {
a: u32::try_from(asset_idx)?,
b: false, // SELL when triggered
p: take_profit_trigger.clone(),
s: "10.0".to_string(),
r: true,
t: OrderType::Trigger(TriggerOrder {
is_market: true,
trigger_px: take_profit_trigger.clone(),
tpsl: Tpsl::Tp,
}),
c: None,
};
tracing::info!(
"Take profit will trigger at ${} (10% above current)",
take_profit_trigger
);
let tp_response = exchange_api
.place_order(take_profit_order, Grouping::Na, None, None, None)
.await?;
match tp_response.response {
ResponseInner::Error(e) => {
tracing::error!("Failed to place take profit: {}", e);
}
ResponseInner::Ok(order_data) => {
tracing::info!("Take profit placed: {:?}", order_data);
}
}
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
let cancel_time = current_time_millis().saturating_add(30_000);
match exchange_api
.schedule_cancel(Some(cancel_time), None, None)
.await
{
Ok(response) => {
tracing::debug!("{:?}", response);
tracing::info!("Scheduled to cancel all orders at: {}", cancel_time);
tracing::info!("Orders will auto-cancel in ~30 seconds");
}
Err(e) => {
if e.to_string().contains("volume traded") {
tracing::warn!("Schedule cancel requires $1M trading volume (testnet limitation)");
tracing::info!("Skipping dead man's switch demo");
} else {
tracing::error!("Failed to schedule cancel: {}", e);
}
}
}
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
let wallet_address = pkey_signer.address();
let open_orders = info_api
.open_orders(&wallet_address.to_string(), None)
.await?;
tracing::info!("Open orders: {} orders", open_orders.len());
for order in &open_orders {
tracing::info!(
" Order: coin={}, side={}, size={}",
order.coin,
if order.side == "B" { "BUY" } else { "SELL" },
order.sz
);
}
tracing::info!("\nAll orders will be automatically canceled in ~30 seconds");
tracing::info!("(Due to the scheduled dead man's switch)");
Ok(())
}