use nautilus_model::enums::{OrderType, TimeInForce};
use serde::{Deserialize, Serialize};
use crate::common::enums::BinanceTimeInForce;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BinanceSpotOrderType {
Limit,
Market,
StopLoss,
StopLossLimit,
TakeProfit,
TakeProfitLimit,
LimitMaker,
#[serde(other)]
Unknown,
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BinanceOrderResponseType {
Ack,
Result,
#[default]
Full,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum BinanceCancelReplaceMode {
StopOnFailure,
AllowFailure,
}
pub fn order_type_to_binance_spot(
order_type: OrderType,
post_only: bool,
) -> anyhow::Result<BinanceSpotOrderType> {
match (order_type, post_only) {
(OrderType::Market, _) => Ok(BinanceSpotOrderType::Market),
(OrderType::Limit, true) => Ok(BinanceSpotOrderType::LimitMaker),
(OrderType::Limit, false) => Ok(BinanceSpotOrderType::Limit),
(OrderType::StopMarket, _) => Ok(BinanceSpotOrderType::StopLoss),
(OrderType::StopLimit, _) => Ok(BinanceSpotOrderType::StopLossLimit),
(OrderType::MarketIfTouched, _) => Ok(BinanceSpotOrderType::TakeProfit),
(OrderType::LimitIfTouched, _) => Ok(BinanceSpotOrderType::TakeProfitLimit),
_ => anyhow::bail!("Unsupported order type for Binance Spot: {order_type:?}"),
}
}
pub fn time_in_force_to_binance_spot(tif: TimeInForce) -> anyhow::Result<BinanceTimeInForce> {
match tif {
TimeInForce::Gtc => Ok(BinanceTimeInForce::Gtc),
TimeInForce::Ioc => Ok(BinanceTimeInForce::Ioc),
TimeInForce::Fok => Ok(BinanceTimeInForce::Fok),
_ => anyhow::bail!("Unsupported time in force for Binance Spot: {tif:?}"),
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
#[case(OrderType::Market, false, BinanceSpotOrderType::Market)]
#[case(OrderType::Limit, false, BinanceSpotOrderType::Limit)]
#[case(OrderType::Limit, true, BinanceSpotOrderType::LimitMaker)]
#[case(OrderType::StopMarket, false, BinanceSpotOrderType::StopLoss)]
#[case(OrderType::StopLimit, false, BinanceSpotOrderType::StopLossLimit)]
#[case(OrderType::MarketIfTouched, false, BinanceSpotOrderType::TakeProfit)]
#[case(
OrderType::LimitIfTouched,
false,
BinanceSpotOrderType::TakeProfitLimit
)]
fn test_order_type_to_binance_spot(
#[case] order_type: OrderType,
#[case] post_only: bool,
#[case] expected: BinanceSpotOrderType,
) {
let result = order_type_to_binance_spot(order_type, post_only).unwrap();
assert_eq!(result, expected);
}
#[rstest]
#[case(OrderType::TrailingStopMarket)]
fn test_order_type_to_binance_spot_unsupported(#[case] order_type: OrderType) {
let result = order_type_to_binance_spot(order_type, false);
assert!(result.is_err());
}
#[rstest]
#[case(TimeInForce::Gtc, BinanceTimeInForce::Gtc)]
#[case(TimeInForce::Ioc, BinanceTimeInForce::Ioc)]
#[case(TimeInForce::Fok, BinanceTimeInForce::Fok)]
fn test_time_in_force_to_binance_spot(
#[case] tif: TimeInForce,
#[case] expected: BinanceTimeInForce,
) {
let result = time_in_force_to_binance_spot(tif).unwrap();
assert_eq!(result, expected);
}
#[rstest]
#[case(TimeInForce::Gtd)]
fn test_time_in_force_to_binance_spot_rejects_gtd(#[case] tif: TimeInForce) {
let result = time_in_force_to_binance_spot(tif);
assert!(result.is_err());
}
}