use anyhow::Context;
use nautilus_model::enums::{OrderType, TimeInForce};
use rust_decimal::Decimal;
use super::enums::{
HyperliquidConditionalOrderType, HyperliquidOrderType, HyperliquidTimeInForce, HyperliquidTpSl,
};
pub fn nautilus_order_type_to_hyperliquid(
order_type: OrderType,
time_in_force: Option<TimeInForce>,
trigger_price: Option<Decimal>,
) -> anyhow::Result<HyperliquidOrderType> {
let result = match order_type {
OrderType::Limit => {
let tif = match time_in_force {
Some(t) => nautilus_time_in_force_to_hyperliquid(t)?,
None => HyperliquidTimeInForce::Gtc,
};
HyperliquidOrderType::Limit { tif }
}
OrderType::StopMarket => {
let trigger_px = trigger_price
.context("Trigger price required for StopMarket order")?
.to_string();
HyperliquidOrderType::Trigger {
is_market: true,
trigger_px,
tpsl: HyperliquidTpSl::Sl,
}
}
OrderType::StopLimit => {
let trigger_px = trigger_price
.context("Trigger price required for StopLimit order")?
.to_string();
HyperliquidOrderType::Trigger {
is_market: false,
trigger_px,
tpsl: HyperliquidTpSl::Sl,
}
}
OrderType::MarketIfTouched => {
let trigger_px = trigger_price
.context("Trigger price required for MarketIfTouched order")?
.to_string();
HyperliquidOrderType::Trigger {
is_market: true,
trigger_px,
tpsl: HyperliquidTpSl::Tp,
}
}
OrderType::LimitIfTouched => {
let trigger_px = trigger_price
.context("Trigger price required for LimitIfTouched order")?
.to_string();
HyperliquidOrderType::Trigger {
is_market: false,
trigger_px,
tpsl: HyperliquidTpSl::Tp,
}
}
OrderType::TrailingStopMarket => {
let trigger_px = trigger_price
.context("Trigger price required for TrailingStopMarket order")?
.to_string();
HyperliquidOrderType::Trigger {
is_market: true,
trigger_px,
tpsl: HyperliquidTpSl::Sl,
}
}
OrderType::TrailingStopLimit => {
let trigger_px = trigger_price
.context("Trigger price required for TrailingStopLimit order")?
.to_string();
HyperliquidOrderType::Trigger {
is_market: false,
trigger_px,
tpsl: HyperliquidTpSl::Sl,
}
}
_ => anyhow::bail!("Unsupported order type: {order_type:?}"),
};
Ok(result)
}
pub fn hyperliquid_order_type_to_nautilus(hl_order_type: &HyperliquidOrderType) -> OrderType {
match hl_order_type {
HyperliquidOrderType::Limit { .. } => OrderType::Limit,
HyperliquidOrderType::Trigger {
is_market, tpsl, ..
} => match (is_market, tpsl) {
(true, HyperliquidTpSl::Sl) => OrderType::StopMarket,
(false, HyperliquidTpSl::Sl) => OrderType::StopLimit,
(true, HyperliquidTpSl::Tp) => OrderType::MarketIfTouched,
(false, HyperliquidTpSl::Tp) => OrderType::LimitIfTouched,
},
}
}
pub fn hyperliquid_conditional_to_nautilus(
conditional_type: HyperliquidConditionalOrderType,
) -> OrderType {
OrderType::from(conditional_type)
}
pub fn nautilus_to_hyperliquid_conditional(
order_type: OrderType,
) -> HyperliquidConditionalOrderType {
HyperliquidConditionalOrderType::from(order_type)
}
pub fn nautilus_time_in_force_to_hyperliquid(
tif: TimeInForce,
) -> anyhow::Result<HyperliquidTimeInForce> {
match tif {
TimeInForce::Gtc => Ok(HyperliquidTimeInForce::Gtc),
TimeInForce::Ioc => Ok(HyperliquidTimeInForce::Ioc),
TimeInForce::Fok => {
anyhow::bail!("FOK time in force is not supported by Hyperliquid")
}
TimeInForce::Gtd => {
anyhow::bail!("GTD time in force is not supported by Hyperliquid")
}
TimeInForce::Day => {
anyhow::bail!("DAY time in force is not supported by Hyperliquid")
}
TimeInForce::AtTheOpen => {
anyhow::bail!("AT_THE_OPEN time in force is not supported by Hyperliquid")
}
TimeInForce::AtTheClose => {
anyhow::bail!("AT_THE_CLOSE time in force is not supported by Hyperliquid")
}
}
}
pub fn hyperliquid_time_in_force_to_nautilus(hl_tif: HyperliquidTimeInForce) -> TimeInForce {
match hl_tif {
HyperliquidTimeInForce::Gtc => TimeInForce::Gtc,
HyperliquidTimeInForce::Ioc => TimeInForce::Ioc,
HyperliquidTimeInForce::Alo => TimeInForce::Gtc, }
}
pub fn determine_tpsl_type(order_type: OrderType, is_buy: bool) -> HyperliquidTpSl {
match order_type {
OrderType::StopMarket
| OrderType::StopLimit
| OrderType::TrailingStopMarket
| OrderType::TrailingStopLimit => HyperliquidTpSl::Sl,
OrderType::MarketIfTouched | OrderType::LimitIfTouched => HyperliquidTpSl::Tp,
_ => {
if is_buy {
HyperliquidTpSl::Sl
} else {
HyperliquidTpSl::Tp
}
}
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
fn test_nautilus_to_hyperliquid_limit_order() {
let result =
nautilus_order_type_to_hyperliquid(OrderType::Limit, Some(TimeInForce::Gtc), None)
.unwrap();
match result {
HyperliquidOrderType::Limit { tif } => {
assert_eq!(tif, HyperliquidTimeInForce::Gtc);
}
_ => panic!("Expected Limit order type"),
}
}
#[rstest]
fn test_nautilus_to_hyperliquid_stop_market() {
let result = nautilus_order_type_to_hyperliquid(
OrderType::StopMarket,
None,
Some(Decimal::new(49000, 0)),
)
.unwrap();
match result {
HyperliquidOrderType::Trigger {
is_market,
trigger_px,
tpsl,
} => {
assert!(is_market);
assert_eq!(trigger_px, "49000");
assert_eq!(tpsl, HyperliquidTpSl::Sl);
}
_ => panic!("Expected Trigger order type"),
}
}
#[rstest]
fn test_nautilus_to_hyperliquid_stop_limit() {
let result = nautilus_order_type_to_hyperliquid(
OrderType::StopLimit,
None,
Some(Decimal::new(49000, 0)),
)
.unwrap();
match result {
HyperliquidOrderType::Trigger {
is_market,
trigger_px,
tpsl,
} => {
assert!(!is_market);
assert_eq!(trigger_px, "49000");
assert_eq!(tpsl, HyperliquidTpSl::Sl);
}
_ => panic!("Expected Trigger order type"),
}
}
#[rstest]
fn test_nautilus_to_hyperliquid_take_profit_market() {
let result = nautilus_order_type_to_hyperliquid(
OrderType::MarketIfTouched,
None,
Some(Decimal::new(51000, 0)),
)
.unwrap();
match result {
HyperliquidOrderType::Trigger {
is_market,
trigger_px,
tpsl,
} => {
assert!(is_market);
assert_eq!(trigger_px, "51000");
assert_eq!(tpsl, HyperliquidTpSl::Tp);
}
_ => panic!("Expected Trigger order type"),
}
}
#[rstest]
fn test_nautilus_to_hyperliquid_take_profit_limit() {
let result = nautilus_order_type_to_hyperliquid(
OrderType::LimitIfTouched,
None,
Some(Decimal::new(51000, 0)),
)
.unwrap();
match result {
HyperliquidOrderType::Trigger {
is_market,
trigger_px,
tpsl,
} => {
assert!(!is_market);
assert_eq!(trigger_px, "51000");
assert_eq!(tpsl, HyperliquidTpSl::Tp);
}
_ => panic!("Expected Trigger order type"),
}
}
#[rstest]
fn test_hyperliquid_to_nautilus_limit() {
let hl_order = HyperliquidOrderType::Limit {
tif: HyperliquidTimeInForce::Gtc,
};
assert_eq!(
hyperliquid_order_type_to_nautilus(&hl_order),
OrderType::Limit
);
}
#[rstest]
fn test_hyperliquid_to_nautilus_stop_market() {
let hl_order = HyperliquidOrderType::Trigger {
is_market: true,
trigger_px: "49000".to_string(),
tpsl: HyperliquidTpSl::Sl,
};
assert_eq!(
hyperliquid_order_type_to_nautilus(&hl_order),
OrderType::StopMarket
);
}
#[rstest]
fn test_hyperliquid_to_nautilus_stop_limit() {
let hl_order = HyperliquidOrderType::Trigger {
is_market: false,
trigger_px: "49000".to_string(),
tpsl: HyperliquidTpSl::Sl,
};
assert_eq!(
hyperliquid_order_type_to_nautilus(&hl_order),
OrderType::StopLimit
);
}
#[rstest]
fn test_hyperliquid_to_nautilus_take_profit_market() {
let hl_order = HyperliquidOrderType::Trigger {
is_market: true,
trigger_px: "51000".to_string(),
tpsl: HyperliquidTpSl::Tp,
};
assert_eq!(
hyperliquid_order_type_to_nautilus(&hl_order),
OrderType::MarketIfTouched
);
}
#[rstest]
fn test_hyperliquid_to_nautilus_take_profit_limit() {
let hl_order = HyperliquidOrderType::Trigger {
is_market: false,
trigger_px: "51000".to_string(),
tpsl: HyperliquidTpSl::Tp,
};
assert_eq!(
hyperliquid_order_type_to_nautilus(&hl_order),
OrderType::LimitIfTouched
);
}
#[rstest]
fn test_time_in_force_conversions() {
assert_eq!(
nautilus_time_in_force_to_hyperliquid(TimeInForce::Gtc).unwrap(),
HyperliquidTimeInForce::Gtc
);
assert_eq!(
nautilus_time_in_force_to_hyperliquid(TimeInForce::Ioc).unwrap(),
HyperliquidTimeInForce::Ioc
);
assert_eq!(
hyperliquid_time_in_force_to_nautilus(HyperliquidTimeInForce::Gtc),
TimeInForce::Gtc
);
assert_eq!(
hyperliquid_time_in_force_to_nautilus(HyperliquidTimeInForce::Ioc),
TimeInForce::Ioc
);
assert_eq!(
hyperliquid_time_in_force_to_nautilus(HyperliquidTimeInForce::Alo),
TimeInForce::Gtc
);
}
#[rstest]
#[case(TimeInForce::Fok, "FOK")]
#[case(TimeInForce::Gtd, "GTD")]
#[case(TimeInForce::Day, "DAY")]
#[case(TimeInForce::AtTheOpen, "AT_THE_OPEN")]
#[case(TimeInForce::AtTheClose, "AT_THE_CLOSE")]
fn test_unsupported_time_in_force_returns_error(#[case] tif: TimeInForce, #[case] name: &str) {
let result = nautilus_time_in_force_to_hyperliquid(tif);
assert!(result.is_err());
assert!(
result
.unwrap_err()
.to_string()
.contains(&format!("{name} time in force is not supported"))
);
}
#[rstest]
fn test_conditional_order_type_conversions() {
assert_eq!(
hyperliquid_conditional_to_nautilus(HyperliquidConditionalOrderType::StopMarket),
OrderType::StopMarket
);
assert_eq!(
hyperliquid_conditional_to_nautilus(HyperliquidConditionalOrderType::StopLimit),
OrderType::StopLimit
);
assert_eq!(
hyperliquid_conditional_to_nautilus(HyperliquidConditionalOrderType::TakeProfitMarket),
OrderType::MarketIfTouched
);
assert_eq!(
hyperliquid_conditional_to_nautilus(HyperliquidConditionalOrderType::TakeProfitLimit),
OrderType::LimitIfTouched
);
assert_eq!(
nautilus_to_hyperliquid_conditional(OrderType::StopMarket),
HyperliquidConditionalOrderType::StopMarket
);
assert_eq!(
nautilus_to_hyperliquid_conditional(OrderType::StopLimit),
HyperliquidConditionalOrderType::StopLimit
);
assert_eq!(
nautilus_to_hyperliquid_conditional(OrderType::MarketIfTouched),
HyperliquidConditionalOrderType::TakeProfitMarket
);
assert_eq!(
nautilus_to_hyperliquid_conditional(OrderType::LimitIfTouched),
HyperliquidConditionalOrderType::TakeProfitLimit
);
}
#[rstest]
fn test_determine_tpsl_type() {
assert_eq!(
determine_tpsl_type(OrderType::StopMarket, true),
HyperliquidTpSl::Sl
);
assert_eq!(
determine_tpsl_type(OrderType::StopLimit, false),
HyperliquidTpSl::Sl
);
assert_eq!(
determine_tpsl_type(OrderType::MarketIfTouched, true),
HyperliquidTpSl::Tp
);
assert_eq!(
determine_tpsl_type(OrderType::LimitIfTouched, false),
HyperliquidTpSl::Tp
);
assert_eq!(
determine_tpsl_type(OrderType::TrailingStopMarket, true),
HyperliquidTpSl::Sl
);
assert_eq!(
determine_tpsl_type(OrderType::TrailingStopLimit, false),
HyperliquidTpSl::Sl
);
}
}