1use alloy::primitives::B128;
19use rust_decimal::Decimal;
20use std::str::FromStr;
21use std::sync::Arc;
22
23use crate::types::{
24 Cloid, OrderRequest, OrderTypePlacement, Side, TIF, TimeInForce, TpSl,
25};
26
27#[derive(Debug, Clone)]
33pub struct Order {
34 asset: String,
35 side: Side,
36 size: Option<Decimal>,
37 notional: Option<Decimal>,
38 price: Option<Decimal>,
39 tif: TIF,
40 reduce_only: bool,
41 cloid: Option<Cloid>,
42}
43
44impl Order {
45 pub fn buy(asset: impl Into<String>) -> Self {
47 Self::new(asset.into(), Side::Buy)
48 }
49
50 pub fn sell(asset: impl Into<String>) -> Self {
52 Self::new(asset.into(), Side::Sell)
53 }
54
55 pub fn long(asset: impl Into<String>) -> Self {
57 Self::buy(asset)
58 }
59
60 pub fn short(asset: impl Into<String>) -> Self {
62 Self::sell(asset)
63 }
64
65 fn new(asset: String, side: Side) -> Self {
66 Self {
67 asset,
68 side,
69 size: None,
70 notional: None,
71 price: None,
72 tif: TIF::Ioc,
73 reduce_only: false,
74 cloid: None,
75 }
76 }
77
78 pub fn size(mut self, size: f64) -> Self {
80 self.size = Some(Decimal::from_f64_retain(size).unwrap_or_default());
81 self
82 }
83
84 pub fn size_decimal(mut self, size: Decimal) -> Self {
86 self.size = Some(size);
87 self
88 }
89
90 pub fn notional(mut self, notional: f64) -> Self {
92 self.notional = Some(Decimal::from_f64_retain(notional).unwrap_or_default());
93 self
94 }
95
96 pub fn price(mut self, price: f64) -> Self {
98 self.price = Some(Decimal::from_f64_retain(price).unwrap_or_default());
99 self
100 }
101
102 pub fn limit(self, price: f64) -> Self {
104 self.price(price)
105 }
106
107 pub fn market(mut self) -> Self {
109 self.tif = TIF::Market;
110 self
111 }
112
113 pub fn ioc(mut self) -> Self {
115 self.tif = TIF::Ioc;
116 self
117 }
118
119 pub fn gtc(mut self) -> Self {
121 self.tif = TIF::Gtc;
122 self
123 }
124
125 pub fn alo(mut self) -> Self {
127 self.tif = TIF::Alo;
128 self
129 }
130
131 pub fn post_only(self) -> Self {
133 self.alo()
134 }
135
136 pub fn reduce_only(mut self) -> Self {
138 self.reduce_only = true;
139 self
140 }
141
142 pub fn cloid(mut self, cloid: impl Into<String>) -> Self {
144 let cloid_str = cloid.into();
145 if let Ok(parsed) = cloid_str.parse::<B128>() {
146 self.cloid = Some(parsed);
147 }
148 self
149 }
150
151 pub fn cloid_bytes(mut self, cloid: [u8; 16]) -> Self {
153 self.cloid = Some(B128::from(cloid));
154 self
155 }
156
157 pub fn random_cloid(mut self) -> Self {
159 let mut bytes = [0u8; 16];
160 let now = std::time::SystemTime::now()
162 .duration_since(std::time::UNIX_EPOCH)
163 .unwrap_or_default();
164 let nanos = now.as_nanos() as u64;
165 bytes[0..8].copy_from_slice(&nanos.to_le_bytes());
166 bytes[8..16].copy_from_slice(&(nanos.wrapping_mul(0x517cc1b727220a95)).to_le_bytes());
167 self.cloid = Some(B128::from(bytes));
168 self
169 }
170
171 pub fn get_asset(&self) -> &str {
177 &self.asset
178 }
179
180 pub fn get_side(&self) -> Side {
182 self.side
183 }
184
185 pub fn get_size(&self) -> Option<Decimal> {
187 self.size
188 }
189
190 pub fn get_notional(&self) -> Option<Decimal> {
192 self.notional
193 }
194
195 pub fn get_price(&self) -> Option<Decimal> {
197 self.price
198 }
199
200 pub fn get_tif(&self) -> TIF {
202 self.tif
203 }
204
205 pub fn is_reduce_only(&self) -> bool {
207 self.reduce_only
208 }
209
210 pub fn is_market(&self) -> bool {
212 self.tif == TIF::Market || self.price.is_none()
213 }
214
215 pub fn get_cloid(&self) -> Option<Cloid> {
217 self.cloid
218 }
219
220 pub fn validate(&self) -> crate::Result<()> {
226 if self.size.is_none() && self.notional.is_none() {
227 return Err(crate::Error::ValidationError(
228 "Order must have either size or notional".to_string(),
229 ));
230 }
231
232 if self.tif != TIF::Market && self.price.is_none() && self.notional.is_none() {
233 return Err(crate::Error::ValidationError(
234 "Non-market orders must have a price".to_string(),
235 ));
236 }
237
238 Ok(())
239 }
240
241 pub fn to_request(&self, asset_index: usize, resolved_price: Decimal) -> OrderRequest {
243 let cloid = self.cloid.unwrap_or_else(|| {
245 let mut bytes = [0u8; 16];
246 let now = std::time::SystemTime::now()
247 .duration_since(std::time::UNIX_EPOCH)
248 .unwrap_or_default();
249 let nanos = now.as_nanos() as u64;
250 bytes[0..8].copy_from_slice(&nanos.to_le_bytes());
251 bytes[8..16].copy_from_slice(&(nanos.wrapping_mul(0x517cc1b727220a95)).to_le_bytes());
252 B128::from(bytes)
253 });
254
255 OrderRequest {
256 asset: asset_index,
257 is_buy: self.side.is_buy(),
258 limit_px: resolved_price,
259 sz: self.size.unwrap_or_default(),
260 reduce_only: self.reduce_only,
261 order_type: OrderTypePlacement::Limit {
262 tif: TimeInForce::from(self.tif),
263 },
264 cloid,
265 }
266 }
267}
268
269#[derive(Debug, Clone)]
275pub struct TriggerOrder {
276 asset: String,
277 tpsl: TpSl,
278 side: Side,
279 size: Option<Decimal>,
280 trigger_price: Option<Decimal>,
281 limit_price: Option<Decimal>,
282 is_market: bool,
283 reduce_only: bool,
284 cloid: Option<Cloid>,
285}
286
287impl TriggerOrder {
288 pub fn stop_loss(asset: impl Into<String>) -> Self {
290 Self::new(asset.into(), TpSl::Sl)
291 }
292
293 pub fn sl(asset: impl Into<String>) -> Self {
295 Self::stop_loss(asset)
296 }
297
298 pub fn take_profit(asset: impl Into<String>) -> Self {
300 Self::new(asset.into(), TpSl::Tp)
301 }
302
303 pub fn tp(asset: impl Into<String>) -> Self {
305 Self::take_profit(asset)
306 }
307
308 fn new(asset: String, tpsl: TpSl) -> Self {
309 Self {
310 asset,
311 tpsl,
312 side: Side::Sell, size: None,
314 trigger_price: None,
315 limit_price: None,
316 is_market: true,
317 reduce_only: true, cloid: None,
319 }
320 }
321
322 pub fn side(mut self, side: Side) -> Self {
324 self.side = side;
325 self
326 }
327
328 pub fn buy(mut self) -> Self {
330 self.side = Side::Buy;
331 self
332 }
333
334 pub fn sell(mut self) -> Self {
336 self.side = Side::Sell;
337 self
338 }
339
340 pub fn size(mut self, size: f64) -> Self {
342 self.size = Some(Decimal::from_f64_retain(size).unwrap_or_default());
343 self
344 }
345
346 pub fn trigger_price(mut self, price: f64) -> Self {
348 self.trigger_price = Some(Decimal::from_f64_retain(price).unwrap_or_default());
349 self
350 }
351
352 pub fn trigger(self, price: f64) -> Self {
354 self.trigger_price(price)
355 }
356
357 pub fn market(mut self) -> Self {
359 self.is_market = true;
360 self.limit_price = None;
361 self
362 }
363
364 pub fn limit(mut self, price: f64) -> Self {
366 self.is_market = false;
367 self.limit_price = Some(Decimal::from_f64_retain(price).unwrap_or_default());
368 self
369 }
370
371 pub fn reduce_only(mut self) -> Self {
373 self.reduce_only = true;
374 self
375 }
376
377 pub fn not_reduce_only(mut self) -> Self {
379 self.reduce_only = false;
380 self
381 }
382
383 pub fn cloid(mut self, cloid: impl Into<String>) -> Self {
385 let cloid_str = cloid.into();
386 if let Ok(parsed) = cloid_str.parse::<B128>() {
387 self.cloid = Some(parsed);
388 }
389 self
390 }
391
392 pub fn get_asset(&self) -> &str {
398 &self.asset
399 }
400
401 pub fn get_tpsl(&self) -> TpSl {
403 self.tpsl
404 }
405
406 pub fn get_side(&self) -> Side {
408 self.side
409 }
410
411 pub fn get_size(&self) -> Option<Decimal> {
413 self.size
414 }
415
416 pub fn get_trigger_price(&self) -> Option<Decimal> {
418 self.trigger_price
419 }
420
421 pub fn get_limit_price(&self) -> Option<Decimal> {
423 self.limit_price
424 }
425
426 pub fn is_market(&self) -> bool {
428 self.is_market
429 }
430
431 pub fn is_reduce_only(&self) -> bool {
433 self.reduce_only
434 }
435
436 pub fn validate(&self) -> crate::Result<()> {
442 if self.size.is_none() {
443 return Err(crate::Error::ValidationError(
444 "Trigger order must have a size".to_string(),
445 ));
446 }
447
448 if self.trigger_price.is_none() {
449 return Err(crate::Error::ValidationError(
450 "Trigger order must have a trigger price".to_string(),
451 ));
452 }
453
454 Ok(())
455 }
456
457 pub fn to_request(&self, asset_index: usize, execution_price: Decimal) -> OrderRequest {
459 OrderRequest {
460 asset: asset_index,
461 is_buy: self.side.is_buy(),
462 limit_px: self.limit_price.unwrap_or(execution_price),
463 sz: self.size.unwrap_or_default(),
464 reduce_only: self.reduce_only,
465 order_type: OrderTypePlacement::Trigger {
466 is_market: self.is_market,
467 trigger_px: self.trigger_price.unwrap_or_default(),
468 tpsl: self.tpsl,
469 },
470 cloid: self.cloid.unwrap_or(B128::ZERO),
471 }
472 }
473}
474
475#[derive(Debug, Clone)]
481pub struct PlacedOrder {
482 pub oid: Option<u64>,
484 pub status: String,
486 pub asset: String,
488 pub side: String,
490 pub size: String,
492 pub price: Option<String>,
494 pub filled_size: Option<String>,
496 pub avg_price: Option<String>,
498 pub error: Option<String>,
500 pub raw_response: serde_json::Value,
502 sdk: Option<Arc<crate::client::HyperliquidSDKInner>>,
504}
505
506impl PlacedOrder {
507 pub(crate) fn from_response(
509 response: serde_json::Value,
510 asset: String,
511 side: Side,
512 size: Decimal,
513 price: Option<Decimal>,
514 sdk: Option<Arc<crate::client::HyperliquidSDKInner>>,
515 ) -> Self {
516 let status = response
518 .get("status")
519 .and_then(|s| s.as_str())
520 .unwrap_or("unknown")
521 .to_string();
522
523 let mut oid = None;
524 let mut filled_size = None;
525 let mut avg_price = None;
526 let mut error = None;
527
528 if status == "ok" {
529 if let Some(data) = response
531 .get("response")
532 .and_then(|r| r.get("data"))
533 .and_then(|d| d.get("statuses"))
534 .and_then(|s| s.get(0))
535 {
536 if let Some(resting) = data.get("resting") {
538 oid = resting.get("oid").and_then(|o| o.as_u64());
539 }
540 if let Some(filled) = data.get("filled") {
542 oid = filled.get("oid").and_then(|o| o.as_u64());
543 filled_size = filled
544 .get("totalSz")
545 .and_then(|s| s.as_str())
546 .map(|s| s.to_string());
547 avg_price = filled
548 .get("avgPx")
549 .and_then(|p| p.as_str())
550 .map(|s| s.to_string());
551 }
552 if let Some(err) = data.get("error") {
554 error = err.as_str().map(|s| s.to_string());
555 }
556 }
557 } else if status == "err" {
558 error = response
559 .get("response")
560 .and_then(|r| r.as_str())
561 .map(|s| s.to_string());
562 }
563
564 let status_str = if oid.is_some() {
565 if filled_size.is_some() {
566 "filled"
567 } else {
568 "resting"
569 }
570 } else if error.is_some() {
571 "error"
572 } else {
573 "unknown"
574 };
575
576 Self {
577 oid,
578 status: status_str.to_string(),
579 asset,
580 side: side.to_string(),
581 size: size.to_string(),
582 price: price.map(|p| p.to_string()),
583 filled_size,
584 avg_price,
585 error,
586 raw_response: response,
587 sdk,
588 }
589 }
590
591 #[allow(dead_code)]
593 pub(crate) fn error(
594 asset: String,
595 side: Side,
596 size: Decimal,
597 error: String,
598 ) -> Self {
599 Self {
600 oid: None,
601 status: "error".to_string(),
602 asset,
603 side: side.to_string(),
604 size: size.to_string(),
605 price: None,
606 filled_size: None,
607 avg_price: None,
608 error: Some(error),
609 raw_response: serde_json::Value::Null,
610 sdk: None,
611 }
612 }
613
614 pub fn is_resting(&self) -> bool {
616 self.status == "resting"
617 }
618
619 pub fn is_filled(&self) -> bool {
621 self.status == "filled"
622 }
623
624 pub fn is_error(&self) -> bool {
626 self.status == "error" || self.error.is_some()
627 }
628
629 pub async fn cancel(&self) -> crate::Result<serde_json::Value> {
631 let oid = self.oid.ok_or_else(|| {
632 crate::Error::OrderError("Cannot cancel order without OID".to_string())
633 })?;
634
635 let sdk = self.sdk.as_ref().ok_or_else(|| {
636 crate::Error::OrderError("SDK reference not available for cancel".to_string())
637 })?;
638
639 sdk.cancel_by_oid(oid, &self.asset).await
640 }
641
642 pub async fn modify(
644 &self,
645 price: Option<f64>,
646 size: Option<f64>,
647 ) -> crate::Result<PlacedOrder> {
648 let oid = self.oid.ok_or_else(|| {
649 crate::Error::OrderError("Cannot modify order without OID".to_string())
650 })?;
651
652 let sdk = self.sdk.as_ref().ok_or_else(|| {
653 crate::Error::OrderError("SDK reference not available for modify".to_string())
654 })?;
655
656 let new_price = price
657 .map(|p| Decimal::from_f64_retain(p).unwrap_or_default())
658 .or_else(|| self.price.as_ref().and_then(|p| Decimal::from_str(p).ok()));
659
660 let new_size = size
661 .map(|s| Decimal::from_f64_retain(s).unwrap_or_default())
662 .or_else(|| Decimal::from_str(&self.size).ok());
663
664 sdk.modify_by_oid(
665 oid,
666 &self.asset,
667 Side::from_str(&self.side).unwrap_or(Side::Buy),
668 new_price.unwrap_or_default(),
669 new_size.unwrap_or_default(),
670 )
671 .await
672 }
673}