nautilus_hyperliquid/common/
converters.rs1use anyhow::Context;
22use nautilus_model::enums::{OrderType, TimeInForce};
23use rust_decimal::Decimal;
24
25use super::enums::{
26 HyperliquidConditionalOrderType, HyperliquidOrderType, HyperliquidTimeInForce, HyperliquidTpSl,
27};
28
29pub fn nautilus_order_type_to_hyperliquid(
36 order_type: OrderType,
37 time_in_force: Option<TimeInForce>,
38 trigger_price: Option<Decimal>,
39) -> anyhow::Result<HyperliquidOrderType> {
40 let result = match order_type {
41 OrderType::Limit => {
43 let tif = match time_in_force {
44 Some(t) => nautilus_time_in_force_to_hyperliquid(t)?,
45 None => HyperliquidTimeInForce::Gtc,
46 };
47 HyperliquidOrderType::Limit { tif }
48 }
49
50 OrderType::StopMarket => {
52 let trigger_px = trigger_price
53 .context("Trigger price required for StopMarket order")?
54 .to_string();
55 HyperliquidOrderType::Trigger {
56 is_market: true,
57 trigger_px,
58 tpsl: HyperliquidTpSl::Sl,
59 }
60 }
61
62 OrderType::StopLimit => {
64 let trigger_px = trigger_price
65 .context("Trigger price required for StopLimit order")?
66 .to_string();
67 HyperliquidOrderType::Trigger {
68 is_market: false,
69 trigger_px,
70 tpsl: HyperliquidTpSl::Sl,
71 }
72 }
73
74 OrderType::MarketIfTouched => {
76 let trigger_px = trigger_price
77 .context("Trigger price required for MarketIfTouched order")?
78 .to_string();
79 HyperliquidOrderType::Trigger {
80 is_market: true,
81 trigger_px,
82 tpsl: HyperliquidTpSl::Tp,
83 }
84 }
85
86 OrderType::LimitIfTouched => {
88 let trigger_px = trigger_price
89 .context("Trigger price required for LimitIfTouched order")?
90 .to_string();
91 HyperliquidOrderType::Trigger {
92 is_market: false,
93 trigger_px,
94 tpsl: HyperliquidTpSl::Tp,
95 }
96 }
97
98 OrderType::TrailingStopMarket => {
100 let trigger_px = trigger_price
101 .context("Trigger price required for TrailingStopMarket order")?
102 .to_string();
103 HyperliquidOrderType::Trigger {
104 is_market: true,
105 trigger_px,
106 tpsl: HyperliquidTpSl::Sl,
107 }
108 }
109
110 OrderType::TrailingStopLimit => {
112 let trigger_px = trigger_price
113 .context("Trigger price required for TrailingStopLimit order")?
114 .to_string();
115 HyperliquidOrderType::Trigger {
116 is_market: false,
117 trigger_px,
118 tpsl: HyperliquidTpSl::Sl,
119 }
120 }
121
122 _ => anyhow::bail!("Unsupported order type: {order_type:?}"),
123 };
124
125 Ok(result)
126}
127
128pub fn hyperliquid_order_type_to_nautilus(hl_order_type: &HyperliquidOrderType) -> OrderType {
130 match hl_order_type {
131 HyperliquidOrderType::Limit { .. } => OrderType::Limit,
132 HyperliquidOrderType::Trigger {
133 is_market, tpsl, ..
134 } => match (is_market, tpsl) {
135 (true, HyperliquidTpSl::Sl) => OrderType::StopMarket,
136 (false, HyperliquidTpSl::Sl) => OrderType::StopLimit,
137 (true, HyperliquidTpSl::Tp) => OrderType::MarketIfTouched,
138 (false, HyperliquidTpSl::Tp) => OrderType::LimitIfTouched,
139 },
140 }
141}
142
143pub fn hyperliquid_conditional_to_nautilus(
145 conditional_type: HyperliquidConditionalOrderType,
146) -> OrderType {
147 OrderType::from(conditional_type)
148}
149
150pub fn nautilus_to_hyperliquid_conditional(
156 order_type: OrderType,
157) -> HyperliquidConditionalOrderType {
158 HyperliquidConditionalOrderType::from(order_type)
159}
160
161pub fn nautilus_time_in_force_to_hyperliquid(
167 tif: TimeInForce,
168) -> anyhow::Result<HyperliquidTimeInForce> {
169 match tif {
170 TimeInForce::Gtc => Ok(HyperliquidTimeInForce::Gtc),
171 TimeInForce::Ioc => Ok(HyperliquidTimeInForce::Ioc),
172 TimeInForce::Fok => {
173 anyhow::bail!("FOK time in force is not supported by Hyperliquid")
174 }
175 TimeInForce::Gtd => {
176 anyhow::bail!("GTD time in force is not supported by Hyperliquid")
177 }
178 TimeInForce::Day => {
179 anyhow::bail!("DAY time in force is not supported by Hyperliquid")
180 }
181 TimeInForce::AtTheOpen => {
182 anyhow::bail!("AT_THE_OPEN time in force is not supported by Hyperliquid")
183 }
184 TimeInForce::AtTheClose => {
185 anyhow::bail!("AT_THE_CLOSE time in force is not supported by Hyperliquid")
186 }
187 }
188}
189
190pub fn hyperliquid_time_in_force_to_nautilus(hl_tif: HyperliquidTimeInForce) -> TimeInForce {
192 match hl_tif {
193 HyperliquidTimeInForce::Gtc => TimeInForce::Gtc,
194 HyperliquidTimeInForce::Ioc => TimeInForce::Ioc,
195 HyperliquidTimeInForce::Alo => TimeInForce::Gtc, }
197}
198
199pub fn determine_tpsl_type(order_type: OrderType, is_buy: bool) -> HyperliquidTpSl {
211 match order_type {
212 OrderType::StopMarket
213 | OrderType::StopLimit
214 | OrderType::TrailingStopMarket
215 | OrderType::TrailingStopLimit => HyperliquidTpSl::Sl,
216 OrderType::MarketIfTouched | OrderType::LimitIfTouched => HyperliquidTpSl::Tp,
217 _ => {
218 if is_buy {
220 HyperliquidTpSl::Sl
221 } else {
222 HyperliquidTpSl::Tp
223 }
224 }
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use rstest::rstest;
231
232 use super::*;
233
234 #[rstest]
235 fn test_nautilus_to_hyperliquid_limit_order() {
236 let result =
237 nautilus_order_type_to_hyperliquid(OrderType::Limit, Some(TimeInForce::Gtc), None)
238 .unwrap();
239
240 match result {
241 HyperliquidOrderType::Limit { tif } => {
242 assert_eq!(tif, HyperliquidTimeInForce::Gtc);
243 }
244 _ => panic!("Expected Limit order type"),
245 }
246 }
247
248 #[rstest]
249 fn test_nautilus_to_hyperliquid_stop_market() {
250 let result = nautilus_order_type_to_hyperliquid(
251 OrderType::StopMarket,
252 None,
253 Some(Decimal::new(49000, 0)),
254 )
255 .unwrap();
256
257 match result {
258 HyperliquidOrderType::Trigger {
259 is_market,
260 trigger_px,
261 tpsl,
262 } => {
263 assert!(is_market);
264 assert_eq!(trigger_px, "49000");
265 assert_eq!(tpsl, HyperliquidTpSl::Sl);
266 }
267 _ => panic!("Expected Trigger order type"),
268 }
269 }
270
271 #[rstest]
272 fn test_nautilus_to_hyperliquid_stop_limit() {
273 let result = nautilus_order_type_to_hyperliquid(
274 OrderType::StopLimit,
275 None,
276 Some(Decimal::new(49000, 0)),
277 )
278 .unwrap();
279
280 match result {
281 HyperliquidOrderType::Trigger {
282 is_market,
283 trigger_px,
284 tpsl,
285 } => {
286 assert!(!is_market);
287 assert_eq!(trigger_px, "49000");
288 assert_eq!(tpsl, HyperliquidTpSl::Sl);
289 }
290 _ => panic!("Expected Trigger order type"),
291 }
292 }
293
294 #[rstest]
295 fn test_nautilus_to_hyperliquid_take_profit_market() {
296 let result = nautilus_order_type_to_hyperliquid(
297 OrderType::MarketIfTouched,
298 None,
299 Some(Decimal::new(51000, 0)),
300 )
301 .unwrap();
302
303 match result {
304 HyperliquidOrderType::Trigger {
305 is_market,
306 trigger_px,
307 tpsl,
308 } => {
309 assert!(is_market);
310 assert_eq!(trigger_px, "51000");
311 assert_eq!(tpsl, HyperliquidTpSl::Tp);
312 }
313 _ => panic!("Expected Trigger order type"),
314 }
315 }
316
317 #[rstest]
318 fn test_nautilus_to_hyperliquid_take_profit_limit() {
319 let result = nautilus_order_type_to_hyperliquid(
320 OrderType::LimitIfTouched,
321 None,
322 Some(Decimal::new(51000, 0)),
323 )
324 .unwrap();
325
326 match result {
327 HyperliquidOrderType::Trigger {
328 is_market,
329 trigger_px,
330 tpsl,
331 } => {
332 assert!(!is_market);
333 assert_eq!(trigger_px, "51000");
334 assert_eq!(tpsl, HyperliquidTpSl::Tp);
335 }
336 _ => panic!("Expected Trigger order type"),
337 }
338 }
339
340 #[rstest]
341 fn test_hyperliquid_to_nautilus_limit() {
342 let hl_order = HyperliquidOrderType::Limit {
343 tif: HyperliquidTimeInForce::Gtc,
344 };
345 assert_eq!(
346 hyperliquid_order_type_to_nautilus(&hl_order),
347 OrderType::Limit
348 );
349 }
350
351 #[rstest]
352 fn test_hyperliquid_to_nautilus_stop_market() {
353 let hl_order = HyperliquidOrderType::Trigger {
354 is_market: true,
355 trigger_px: "49000".to_string(),
356 tpsl: HyperliquidTpSl::Sl,
357 };
358 assert_eq!(
359 hyperliquid_order_type_to_nautilus(&hl_order),
360 OrderType::StopMarket
361 );
362 }
363
364 #[rstest]
365 fn test_hyperliquid_to_nautilus_stop_limit() {
366 let hl_order = HyperliquidOrderType::Trigger {
367 is_market: false,
368 trigger_px: "49000".to_string(),
369 tpsl: HyperliquidTpSl::Sl,
370 };
371 assert_eq!(
372 hyperliquid_order_type_to_nautilus(&hl_order),
373 OrderType::StopLimit
374 );
375 }
376
377 #[rstest]
378 fn test_hyperliquid_to_nautilus_take_profit_market() {
379 let hl_order = HyperliquidOrderType::Trigger {
380 is_market: true,
381 trigger_px: "51000".to_string(),
382 tpsl: HyperliquidTpSl::Tp,
383 };
384 assert_eq!(
385 hyperliquid_order_type_to_nautilus(&hl_order),
386 OrderType::MarketIfTouched
387 );
388 }
389
390 #[rstest]
391 fn test_hyperliquid_to_nautilus_take_profit_limit() {
392 let hl_order = HyperliquidOrderType::Trigger {
393 is_market: false,
394 trigger_px: "51000".to_string(),
395 tpsl: HyperliquidTpSl::Tp,
396 };
397 assert_eq!(
398 hyperliquid_order_type_to_nautilus(&hl_order),
399 OrderType::LimitIfTouched
400 );
401 }
402
403 #[rstest]
404 fn test_time_in_force_conversions() {
405 assert_eq!(
407 nautilus_time_in_force_to_hyperliquid(TimeInForce::Gtc).unwrap(),
408 HyperliquidTimeInForce::Gtc
409 );
410 assert_eq!(
411 nautilus_time_in_force_to_hyperliquid(TimeInForce::Ioc).unwrap(),
412 HyperliquidTimeInForce::Ioc
413 );
414
415 assert_eq!(
417 hyperliquid_time_in_force_to_nautilus(HyperliquidTimeInForce::Gtc),
418 TimeInForce::Gtc
419 );
420 assert_eq!(
421 hyperliquid_time_in_force_to_nautilus(HyperliquidTimeInForce::Ioc),
422 TimeInForce::Ioc
423 );
424 assert_eq!(
425 hyperliquid_time_in_force_to_nautilus(HyperliquidTimeInForce::Alo),
426 TimeInForce::Gtc
427 );
428 }
429
430 #[rstest]
431 #[case(TimeInForce::Fok, "FOK")]
432 #[case(TimeInForce::Gtd, "GTD")]
433 #[case(TimeInForce::Day, "DAY")]
434 #[case(TimeInForce::AtTheOpen, "AT_THE_OPEN")]
435 #[case(TimeInForce::AtTheClose, "AT_THE_CLOSE")]
436 fn test_unsupported_time_in_force_returns_error(#[case] tif: TimeInForce, #[case] name: &str) {
437 let result = nautilus_time_in_force_to_hyperliquid(tif);
438 assert!(result.is_err());
439 assert!(
440 result
441 .unwrap_err()
442 .to_string()
443 .contains(&format!("{name} time in force is not supported"))
444 );
445 }
446
447 #[rstest]
448 fn test_conditional_order_type_conversions() {
449 assert_eq!(
451 hyperliquid_conditional_to_nautilus(HyperliquidConditionalOrderType::StopMarket),
452 OrderType::StopMarket
453 );
454 assert_eq!(
455 hyperliquid_conditional_to_nautilus(HyperliquidConditionalOrderType::StopLimit),
456 OrderType::StopLimit
457 );
458 assert_eq!(
459 hyperliquid_conditional_to_nautilus(HyperliquidConditionalOrderType::TakeProfitMarket),
460 OrderType::MarketIfTouched
461 );
462 assert_eq!(
463 hyperliquid_conditional_to_nautilus(HyperliquidConditionalOrderType::TakeProfitLimit),
464 OrderType::LimitIfTouched
465 );
466
467 assert_eq!(
469 nautilus_to_hyperliquid_conditional(OrderType::StopMarket),
470 HyperliquidConditionalOrderType::StopMarket
471 );
472 assert_eq!(
473 nautilus_to_hyperliquid_conditional(OrderType::StopLimit),
474 HyperliquidConditionalOrderType::StopLimit
475 );
476 assert_eq!(
477 nautilus_to_hyperliquid_conditional(OrderType::MarketIfTouched),
478 HyperliquidConditionalOrderType::TakeProfitMarket
479 );
480 assert_eq!(
481 nautilus_to_hyperliquid_conditional(OrderType::LimitIfTouched),
482 HyperliquidConditionalOrderType::TakeProfitLimit
483 );
484 }
485
486 #[rstest]
487 fn test_determine_tpsl_type() {
488 assert_eq!(
490 determine_tpsl_type(OrderType::StopMarket, true),
491 HyperliquidTpSl::Sl
492 );
493 assert_eq!(
494 determine_tpsl_type(OrderType::StopLimit, false),
495 HyperliquidTpSl::Sl
496 );
497
498 assert_eq!(
500 determine_tpsl_type(OrderType::MarketIfTouched, true),
501 HyperliquidTpSl::Tp
502 );
503 assert_eq!(
504 determine_tpsl_type(OrderType::LimitIfTouched, false),
505 HyperliquidTpSl::Tp
506 );
507
508 assert_eq!(
510 determine_tpsl_type(OrderType::TrailingStopMarket, true),
511 HyperliquidTpSl::Sl
512 );
513 assert_eq!(
514 determine_tpsl_type(OrderType::TrailingStopLimit, false),
515 HyperliquidTpSl::Sl
516 );
517 }
518}