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 priority_fee: Option<u64>,
43}
44
45impl Order {
46 pub fn buy(asset: impl Into<String>) -> Self {
48 Self::new(asset.into(), Side::Buy)
49 }
50
51 pub fn sell(asset: impl Into<String>) -> Self {
53 Self::new(asset.into(), Side::Sell)
54 }
55
56 pub fn long(asset: impl Into<String>) -> Self {
58 Self::buy(asset)
59 }
60
61 pub fn short(asset: impl Into<String>) -> Self {
63 Self::sell(asset)
64 }
65
66 fn new(asset: String, side: Side) -> Self {
67 Self {
68 asset,
69 side,
70 size: None,
71 notional: None,
72 price: None,
73 tif: TIF::Ioc,
74 reduce_only: false,
75 cloid: None,
76 priority_fee: None,
77 }
78 }
79
80 pub fn size(mut self, size: f64) -> Self {
82 self.size = Some(Decimal::from_f64_retain(size).unwrap_or_default());
83 self
84 }
85
86 pub fn size_decimal(mut self, size: Decimal) -> Self {
88 self.size = Some(size);
89 self
90 }
91
92 pub fn notional(mut self, notional: f64) -> Self {
94 self.notional = Some(Decimal::from_f64_retain(notional).unwrap_or_default());
95 self
96 }
97
98 pub fn price(mut self, price: f64) -> Self {
100 self.price = Some(Decimal::from_f64_retain(price).unwrap_or_default());
101 self
102 }
103
104 pub fn limit(self, price: f64) -> Self {
106 self.price(price)
107 }
108
109 pub fn market(mut self) -> Self {
111 self.tif = TIF::Market;
112 self
113 }
114
115 pub fn ioc(mut self) -> Self {
117 self.tif = TIF::Ioc;
118 self
119 }
120
121 pub fn gtc(mut self) -> Self {
123 self.tif = TIF::Gtc;
124 self
125 }
126
127 pub fn alo(mut self) -> Self {
129 self.tif = TIF::Alo;
130 self
131 }
132
133 pub fn post_only(self) -> Self {
135 self.alo()
136 }
137
138 pub fn reduce_only(mut self) -> Self {
140 self.reduce_only = true;
141 self
142 }
143
144 pub fn cloid(mut self, cloid: impl Into<String>) -> Self {
146 let cloid_str = cloid.into();
147 if let Ok(parsed) = cloid_str.parse::<B128>() {
148 self.cloid = Some(parsed);
149 }
150 self
151 }
152
153 pub fn cloid_bytes(mut self, cloid: [u8; 16]) -> Self {
155 self.cloid = Some(B128::from(cloid));
156 self
157 }
158
159 pub fn random_cloid(mut self) -> Self {
161 let mut bytes = [0u8; 16];
162 let now = std::time::SystemTime::now()
164 .duration_since(std::time::UNIX_EPOCH)
165 .unwrap_or_default();
166 let nanos = now.as_nanos() as u64;
167 bytes[0..8].copy_from_slice(&nanos.to_le_bytes());
168 bytes[8..16].copy_from_slice(&(nanos.wrapping_mul(0x517cc1b727220a95)).to_le_bytes());
169 self.cloid = Some(B128::from(bytes));
170 self
171 }
172
173 pub fn priority_fee(mut self, p: u64) -> Self {
178 self.priority_fee = Some(p);
179 self
180 }
181
182 pub fn get_asset(&self) -> &str {
188 &self.asset
189 }
190
191 pub fn get_side(&self) -> Side {
193 self.side
194 }
195
196 pub fn get_size(&self) -> Option<Decimal> {
198 self.size
199 }
200
201 pub fn get_notional(&self) -> Option<Decimal> {
203 self.notional
204 }
205
206 pub fn get_price(&self) -> Option<Decimal> {
208 self.price
209 }
210
211 pub fn get_tif(&self) -> TIF {
213 self.tif
214 }
215
216 pub fn is_reduce_only(&self) -> bool {
218 self.reduce_only
219 }
220
221 pub fn is_market(&self) -> bool {
223 self.tif == TIF::Market || self.price.is_none()
224 }
225
226 pub fn get_cloid(&self) -> Option<Cloid> {
228 self.cloid
229 }
230
231 pub fn get_priority_fee(&self) -> Option<u64> {
233 self.priority_fee
234 }
235
236 pub fn validate(&self) -> crate::Result<()> {
242 if self.size.is_none() && self.notional.is_none() {
243 return Err(crate::Error::ValidationError(
244 "Order must have either size or notional".to_string(),
245 ));
246 }
247
248 if self.tif != TIF::Market && self.price.is_none() && self.notional.is_none() {
249 return Err(crate::Error::ValidationError(
250 "Non-market orders must have a price".to_string(),
251 ));
252 }
253
254 Ok(())
255 }
256
257 pub fn to_request(&self, asset_index: usize, resolved_price: Decimal) -> OrderRequest {
259 let cloid = self.cloid.unwrap_or_else(|| {
261 let mut bytes = [0u8; 16];
262 let now = std::time::SystemTime::now()
263 .duration_since(std::time::UNIX_EPOCH)
264 .unwrap_or_default();
265 let nanos = now.as_nanos() as u64;
266 bytes[0..8].copy_from_slice(&nanos.to_le_bytes());
267 bytes[8..16].copy_from_slice(&(nanos.wrapping_mul(0x517cc1b727220a95)).to_le_bytes());
268 B128::from(bytes)
269 });
270
271 OrderRequest {
272 asset: asset_index,
273 is_buy: self.side.is_buy(),
274 limit_px: resolved_price,
275 sz: self.size.unwrap_or_default(),
276 reduce_only: self.reduce_only,
277 order_type: OrderTypePlacement::Limit {
278 tif: TimeInForce::from(self.tif),
279 },
280 cloid,
281 }
282 }
283}
284
285#[derive(Debug, Clone)]
291pub struct TriggerOrder {
292 asset: String,
293 tpsl: TpSl,
294 side: Side,
295 size: Option<Decimal>,
296 trigger_price: Option<Decimal>,
297 limit_price: Option<Decimal>,
298 is_market: bool,
299 reduce_only: bool,
300 cloid: Option<Cloid>,
301}
302
303impl TriggerOrder {
304 pub fn stop_loss(asset: impl Into<String>) -> Self {
306 Self::new(asset.into(), TpSl::Sl)
307 }
308
309 pub fn sl(asset: impl Into<String>) -> Self {
311 Self::stop_loss(asset)
312 }
313
314 pub fn take_profit(asset: impl Into<String>) -> Self {
316 Self::new(asset.into(), TpSl::Tp)
317 }
318
319 pub fn tp(asset: impl Into<String>) -> Self {
321 Self::take_profit(asset)
322 }
323
324 fn new(asset: String, tpsl: TpSl) -> Self {
325 Self {
326 asset,
327 tpsl,
328 side: Side::Sell, size: None,
330 trigger_price: None,
331 limit_price: None,
332 is_market: true,
333 reduce_only: true, cloid: None,
335 }
336 }
337
338 pub fn side(mut self, side: Side) -> Self {
340 self.side = side;
341 self
342 }
343
344 pub fn buy(mut self) -> Self {
346 self.side = Side::Buy;
347 self
348 }
349
350 pub fn sell(mut self) -> Self {
352 self.side = Side::Sell;
353 self
354 }
355
356 pub fn size(mut self, size: f64) -> Self {
358 self.size = Some(Decimal::from_f64_retain(size).unwrap_or_default());
359 self
360 }
361
362 pub fn trigger_price(mut self, price: f64) -> Self {
364 self.trigger_price = Some(Decimal::from_f64_retain(price).unwrap_or_default());
365 self
366 }
367
368 pub fn trigger(self, price: f64) -> Self {
370 self.trigger_price(price)
371 }
372
373 pub fn market(mut self) -> Self {
375 self.is_market = true;
376 self.limit_price = None;
377 self
378 }
379
380 pub fn limit(mut self, price: f64) -> Self {
382 self.is_market = false;
383 self.limit_price = Some(Decimal::from_f64_retain(price).unwrap_or_default());
384 self
385 }
386
387 pub fn reduce_only(mut self) -> Self {
389 self.reduce_only = true;
390 self
391 }
392
393 pub fn not_reduce_only(mut self) -> Self {
395 self.reduce_only = false;
396 self
397 }
398
399 pub fn cloid(mut self, cloid: impl Into<String>) -> Self {
401 let cloid_str = cloid.into();
402 if let Ok(parsed) = cloid_str.parse::<B128>() {
403 self.cloid = Some(parsed);
404 }
405 self
406 }
407
408 pub fn get_asset(&self) -> &str {
414 &self.asset
415 }
416
417 pub fn get_tpsl(&self) -> TpSl {
419 self.tpsl
420 }
421
422 pub fn get_side(&self) -> Side {
424 self.side
425 }
426
427 pub fn get_size(&self) -> Option<Decimal> {
429 self.size
430 }
431
432 pub fn get_trigger_price(&self) -> Option<Decimal> {
434 self.trigger_price
435 }
436
437 pub fn get_limit_price(&self) -> Option<Decimal> {
439 self.limit_price
440 }
441
442 pub fn is_market(&self) -> bool {
444 self.is_market
445 }
446
447 pub fn is_reduce_only(&self) -> bool {
449 self.reduce_only
450 }
451
452 pub fn validate(&self) -> crate::Result<()> {
458 if self.size.is_none() {
459 return Err(crate::Error::ValidationError(
460 "Trigger order must have a size".to_string(),
461 ));
462 }
463
464 if self.trigger_price.is_none() {
465 return Err(crate::Error::ValidationError(
466 "Trigger order must have a trigger price".to_string(),
467 ));
468 }
469
470 Ok(())
471 }
472
473 pub fn to_request(&self, asset_index: usize, execution_price: Decimal) -> OrderRequest {
475 OrderRequest {
476 asset: asset_index,
477 is_buy: self.side.is_buy(),
478 limit_px: self.limit_price.unwrap_or(execution_price),
479 sz: self.size.unwrap_or_default(),
480 reduce_only: self.reduce_only,
481 order_type: OrderTypePlacement::Trigger {
482 is_market: self.is_market,
483 trigger_px: self.trigger_price.unwrap_or_default(),
484 tpsl: self.tpsl,
485 },
486 cloid: self.cloid.unwrap_or(B128::ZERO),
487 }
488 }
489}
490
491#[derive(Debug, Clone)]
497pub struct PlacedOrder {
498 pub oid: Option<u64>,
500 pub status: String,
502 pub asset: String,
504 pub side: String,
506 pub size: String,
508 pub price: Option<String>,
510 pub filled_size: Option<String>,
512 pub avg_price: Option<String>,
514 pub error: Option<String>,
516 pub raw_response: serde_json::Value,
518 sdk: Option<Arc<crate::client::HyperliquidSDKInner>>,
520}
521
522impl PlacedOrder {
523 pub(crate) fn from_response(
525 response: serde_json::Value,
526 asset: String,
527 side: Side,
528 size: Decimal,
529 price: Option<Decimal>,
530 sdk: Option<Arc<crate::client::HyperliquidSDKInner>>,
531 ) -> Self {
532 let status = response
534 .get("status")
535 .and_then(|s| s.as_str())
536 .unwrap_or("unknown")
537 .to_string();
538
539 let mut oid = None;
540 let mut filled_size = None;
541 let mut avg_price = None;
542 let mut error = None;
543
544 if status == "ok" {
545 if let Some(data) = response
547 .get("response")
548 .and_then(|r| r.get("data"))
549 .and_then(|d| d.get("statuses"))
550 .and_then(|s| s.get(0))
551 {
552 if let Some(resting) = data.get("resting") {
554 oid = resting.get("oid").and_then(|o| o.as_u64());
555 }
556 if let Some(filled) = data.get("filled") {
558 oid = filled.get("oid").and_then(|o| o.as_u64());
559 filled_size = filled
560 .get("totalSz")
561 .and_then(|s| s.as_str())
562 .map(|s| s.to_string());
563 avg_price = filled
564 .get("avgPx")
565 .and_then(|p| p.as_str())
566 .map(|s| s.to_string());
567 }
568 if let Some(err) = data.get("error") {
570 error = err.as_str().map(|s| s.to_string());
571 }
572 }
573 } else if status == "err" {
574 error = response
575 .get("response")
576 .and_then(|r| r.as_str())
577 .map(|s| s.to_string());
578 }
579
580 let status_str = if oid.is_some() {
581 if filled_size.is_some() {
582 "filled"
583 } else {
584 "resting"
585 }
586 } else if error.is_some() {
587 "error"
588 } else {
589 "unknown"
590 };
591
592 Self {
593 oid,
594 status: status_str.to_string(),
595 asset,
596 side: side.to_string(),
597 size: size.to_string(),
598 price: price.map(|p| p.to_string()),
599 filled_size,
600 avg_price,
601 error,
602 raw_response: response,
603 sdk,
604 }
605 }
606
607 #[allow(dead_code)]
609 pub(crate) fn error(
610 asset: String,
611 side: Side,
612 size: Decimal,
613 error: String,
614 ) -> Self {
615 Self {
616 oid: None,
617 status: "error".to_string(),
618 asset,
619 side: side.to_string(),
620 size: size.to_string(),
621 price: None,
622 filled_size: None,
623 avg_price: None,
624 error: Some(error),
625 raw_response: serde_json::Value::Null,
626 sdk: None,
627 }
628 }
629
630 pub fn is_resting(&self) -> bool {
632 self.status == "resting"
633 }
634
635 pub fn is_filled(&self) -> bool {
637 self.status == "filled"
638 }
639
640 pub fn is_error(&self) -> bool {
642 self.status == "error" || self.error.is_some()
643 }
644
645 pub async fn cancel(&self) -> crate::Result<serde_json::Value> {
647 let oid = self.oid.ok_or_else(|| {
648 crate::Error::OrderError("Cannot cancel order without OID".to_string())
649 })?;
650
651 let sdk = self.sdk.as_ref().ok_or_else(|| {
652 crate::Error::OrderError("SDK reference not available for cancel".to_string())
653 })?;
654
655 sdk.cancel_by_oid(oid, &self.asset).await
656 }
657
658 pub async fn modify(
660 &self,
661 price: Option<f64>,
662 size: Option<f64>,
663 ) -> crate::Result<PlacedOrder> {
664 let oid = self.oid.ok_or_else(|| {
665 crate::Error::OrderError("Cannot modify order without OID".to_string())
666 })?;
667
668 let sdk = self.sdk.as_ref().ok_or_else(|| {
669 crate::Error::OrderError("SDK reference not available for modify".to_string())
670 })?;
671
672 let new_price = price
673 .map(|p| Decimal::from_f64_retain(p).unwrap_or_default())
674 .or_else(|| self.price.as_ref().and_then(|p| Decimal::from_str(p).ok()));
675
676 let new_size = size
677 .map(|s| Decimal::from_f64_retain(s).unwrap_or_default())
678 .or_else(|| Decimal::from_str(&self.size).ok());
679
680 sdk.modify_by_oid(
681 oid,
682 &self.asset,
683 Side::from_str(&self.side).unwrap_or(Side::Buy),
684 new_price.unwrap_or_default(),
685 new_size.unwrap_or_default(),
686 )
687 .await
688 }
689}