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 => Ok(HyperliquidTimeInForce::Gtc), TimeInForce::Day => Ok(HyperliquidTimeInForce::Gtc), TimeInForce::AtTheOpen => Ok(HyperliquidTimeInForce::Gtc), TimeInForce::AtTheClose => Ok(HyperliquidTimeInForce::Gtc), }
180}
181
182pub fn hyperliquid_time_in_force_to_nautilus(hl_tif: HyperliquidTimeInForce) -> TimeInForce {
184 match hl_tif {
185 HyperliquidTimeInForce::Gtc => TimeInForce::Gtc,
186 HyperliquidTimeInForce::Ioc => TimeInForce::Ioc,
187 HyperliquidTimeInForce::Alo => TimeInForce::Gtc, }
189}
190
191pub fn determine_tpsl_type(order_type: OrderType, is_buy: bool) -> HyperliquidTpSl {
203 match order_type {
204 OrderType::StopMarket
205 | OrderType::StopLimit
206 | OrderType::TrailingStopMarket
207 | OrderType::TrailingStopLimit => HyperliquidTpSl::Sl,
208 OrderType::MarketIfTouched | OrderType::LimitIfTouched => HyperliquidTpSl::Tp,
209 _ => {
210 if is_buy {
212 HyperliquidTpSl::Sl
213 } else {
214 HyperliquidTpSl::Tp
215 }
216 }
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use rstest::rstest;
223
224 use super::*;
225
226 #[rstest]
227 fn test_nautilus_to_hyperliquid_limit_order() {
228 let result =
229 nautilus_order_type_to_hyperliquid(OrderType::Limit, Some(TimeInForce::Gtc), None)
230 .unwrap();
231
232 match result {
233 HyperliquidOrderType::Limit { tif } => {
234 assert_eq!(tif, HyperliquidTimeInForce::Gtc);
235 }
236 _ => panic!("Expected Limit order type"),
237 }
238 }
239
240 #[rstest]
241 fn test_nautilus_to_hyperliquid_stop_market() {
242 let result = nautilus_order_type_to_hyperliquid(
243 OrderType::StopMarket,
244 None,
245 Some(Decimal::new(49000, 0)),
246 )
247 .unwrap();
248
249 match result {
250 HyperliquidOrderType::Trigger {
251 is_market,
252 trigger_px,
253 tpsl,
254 } => {
255 assert!(is_market);
256 assert_eq!(trigger_px, "49000");
257 assert_eq!(tpsl, HyperliquidTpSl::Sl);
258 }
259 _ => panic!("Expected Trigger order type"),
260 }
261 }
262
263 #[rstest]
264 fn test_nautilus_to_hyperliquid_stop_limit() {
265 let result = nautilus_order_type_to_hyperliquid(
266 OrderType::StopLimit,
267 None,
268 Some(Decimal::new(49000, 0)),
269 )
270 .unwrap();
271
272 match result {
273 HyperliquidOrderType::Trigger {
274 is_market,
275 trigger_px,
276 tpsl,
277 } => {
278 assert!(!is_market);
279 assert_eq!(trigger_px, "49000");
280 assert_eq!(tpsl, HyperliquidTpSl::Sl);
281 }
282 _ => panic!("Expected Trigger order type"),
283 }
284 }
285
286 #[rstest]
287 fn test_nautilus_to_hyperliquid_take_profit_market() {
288 let result = nautilus_order_type_to_hyperliquid(
289 OrderType::MarketIfTouched,
290 None,
291 Some(Decimal::new(51000, 0)),
292 )
293 .unwrap();
294
295 match result {
296 HyperliquidOrderType::Trigger {
297 is_market,
298 trigger_px,
299 tpsl,
300 } => {
301 assert!(is_market);
302 assert_eq!(trigger_px, "51000");
303 assert_eq!(tpsl, HyperliquidTpSl::Tp);
304 }
305 _ => panic!("Expected Trigger order type"),
306 }
307 }
308
309 #[rstest]
310 fn test_nautilus_to_hyperliquid_take_profit_limit() {
311 let result = nautilus_order_type_to_hyperliquid(
312 OrderType::LimitIfTouched,
313 None,
314 Some(Decimal::new(51000, 0)),
315 )
316 .unwrap();
317
318 match result {
319 HyperliquidOrderType::Trigger {
320 is_market,
321 trigger_px,
322 tpsl,
323 } => {
324 assert!(!is_market);
325 assert_eq!(trigger_px, "51000");
326 assert_eq!(tpsl, HyperliquidTpSl::Tp);
327 }
328 _ => panic!("Expected Trigger order type"),
329 }
330 }
331
332 #[rstest]
333 fn test_hyperliquid_to_nautilus_limit() {
334 let hl_order = HyperliquidOrderType::Limit {
335 tif: HyperliquidTimeInForce::Gtc,
336 };
337 assert_eq!(
338 hyperliquid_order_type_to_nautilus(&hl_order),
339 OrderType::Limit
340 );
341 }
342
343 #[rstest]
344 fn test_hyperliquid_to_nautilus_stop_market() {
345 let hl_order = HyperliquidOrderType::Trigger {
346 is_market: true,
347 trigger_px: "49000".to_string(),
348 tpsl: HyperliquidTpSl::Sl,
349 };
350 assert_eq!(
351 hyperliquid_order_type_to_nautilus(&hl_order),
352 OrderType::StopMarket
353 );
354 }
355
356 #[rstest]
357 fn test_hyperliquid_to_nautilus_stop_limit() {
358 let hl_order = HyperliquidOrderType::Trigger {
359 is_market: false,
360 trigger_px: "49000".to_string(),
361 tpsl: HyperliquidTpSl::Sl,
362 };
363 assert_eq!(
364 hyperliquid_order_type_to_nautilus(&hl_order),
365 OrderType::StopLimit
366 );
367 }
368
369 #[rstest]
370 fn test_hyperliquid_to_nautilus_take_profit_market() {
371 let hl_order = HyperliquidOrderType::Trigger {
372 is_market: true,
373 trigger_px: "51000".to_string(),
374 tpsl: HyperliquidTpSl::Tp,
375 };
376 assert_eq!(
377 hyperliquid_order_type_to_nautilus(&hl_order),
378 OrderType::MarketIfTouched
379 );
380 }
381
382 #[rstest]
383 fn test_hyperliquid_to_nautilus_take_profit_limit() {
384 let hl_order = HyperliquidOrderType::Trigger {
385 is_market: false,
386 trigger_px: "51000".to_string(),
387 tpsl: HyperliquidTpSl::Tp,
388 };
389 assert_eq!(
390 hyperliquid_order_type_to_nautilus(&hl_order),
391 OrderType::LimitIfTouched
392 );
393 }
394
395 #[rstest]
396 fn test_time_in_force_conversions() {
397 assert_eq!(
399 nautilus_time_in_force_to_hyperliquid(TimeInForce::Gtc).unwrap(),
400 HyperliquidTimeInForce::Gtc
401 );
402 assert_eq!(
403 nautilus_time_in_force_to_hyperliquid(TimeInForce::Ioc).unwrap(),
404 HyperliquidTimeInForce::Ioc
405 );
406
407 assert_eq!(
409 hyperliquid_time_in_force_to_nautilus(HyperliquidTimeInForce::Gtc),
410 TimeInForce::Gtc
411 );
412 assert_eq!(
413 hyperliquid_time_in_force_to_nautilus(HyperliquidTimeInForce::Ioc),
414 TimeInForce::Ioc
415 );
416 assert_eq!(
417 hyperliquid_time_in_force_to_nautilus(HyperliquidTimeInForce::Alo),
418 TimeInForce::Gtc
419 );
420 }
421
422 #[rstest]
423 fn test_fok_time_in_force_returns_error() {
424 let result = nautilus_time_in_force_to_hyperliquid(TimeInForce::Fok);
425 assert!(result.is_err());
426 assert!(
427 result
428 .unwrap_err()
429 .to_string()
430 .contains("FOK time in force is not supported")
431 );
432 }
433
434 #[rstest]
435 fn test_conditional_order_type_conversions() {
436 assert_eq!(
438 hyperliquid_conditional_to_nautilus(HyperliquidConditionalOrderType::StopMarket),
439 OrderType::StopMarket
440 );
441 assert_eq!(
442 hyperliquid_conditional_to_nautilus(HyperliquidConditionalOrderType::StopLimit),
443 OrderType::StopLimit
444 );
445 assert_eq!(
446 hyperliquid_conditional_to_nautilus(HyperliquidConditionalOrderType::TakeProfitMarket),
447 OrderType::MarketIfTouched
448 );
449 assert_eq!(
450 hyperliquid_conditional_to_nautilus(HyperliquidConditionalOrderType::TakeProfitLimit),
451 OrderType::LimitIfTouched
452 );
453
454 assert_eq!(
456 nautilus_to_hyperliquid_conditional(OrderType::StopMarket),
457 HyperliquidConditionalOrderType::StopMarket
458 );
459 assert_eq!(
460 nautilus_to_hyperliquid_conditional(OrderType::StopLimit),
461 HyperliquidConditionalOrderType::StopLimit
462 );
463 assert_eq!(
464 nautilus_to_hyperliquid_conditional(OrderType::MarketIfTouched),
465 HyperliquidConditionalOrderType::TakeProfitMarket
466 );
467 assert_eq!(
468 nautilus_to_hyperliquid_conditional(OrderType::LimitIfTouched),
469 HyperliquidConditionalOrderType::TakeProfitLimit
470 );
471 }
472
473 #[rstest]
474 fn test_determine_tpsl_type() {
475 assert_eq!(
477 determine_tpsl_type(OrderType::StopMarket, true),
478 HyperliquidTpSl::Sl
479 );
480 assert_eq!(
481 determine_tpsl_type(OrderType::StopLimit, false),
482 HyperliquidTpSl::Sl
483 );
484
485 assert_eq!(
487 determine_tpsl_type(OrderType::MarketIfTouched, true),
488 HyperliquidTpSl::Tp
489 );
490 assert_eq!(
491 determine_tpsl_type(OrderType::LimitIfTouched, false),
492 HyperliquidTpSl::Tp
493 );
494
495 assert_eq!(
497 determine_tpsl_type(OrderType::TrailingStopMarket, true),
498 HyperliquidTpSl::Sl
499 );
500 assert_eq!(
501 determine_tpsl_type(OrderType::TrailingStopLimit, false),
502 HyperliquidTpSl::Sl
503 );
504 }
505}