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,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum BinanceSpotUserDataEventType {
ExecutionReport,
OutboundAccountPosition,
BalanceUpdate,
ServerShutdown,
ListenKeyExpired,
ExternalLockUpdate,
EventStreamTerminated,
#[serde(other)]
Unknown,
}
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,
use_gtd: bool,
) -> anyhow::Result<BinanceTimeInForce> {
match tif {
TimeInForce::Gtc => Ok(BinanceTimeInForce::Gtc),
TimeInForce::Ioc => Ok(BinanceTimeInForce::Ioc),
TimeInForce::Fok => Ok(BinanceTimeInForce::Fok),
TimeInForce::Gtd if !use_gtd => {
log::warn!(
"Binance Spot does not support GTD; submitting as GTC because use_gtd=false. Enable manage_gtd_expiry on the submitting strategy"
);
Ok(BinanceTimeInForce::Gtc)
}
TimeInForce::Gtd => anyhow::bail!(
"Binance Spot does not support native GTD; set use_gtd=false and enable manage_gtd_expiry on the submitting strategy"
),
_ => 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);
result.unwrap_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, true).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, true);
result.unwrap_err();
}
#[rstest]
fn test_time_in_force_to_binance_spot_maps_locally_managed_gtd_to_gtc() {
let result = time_in_force_to_binance_spot(TimeInForce::Gtd, false).unwrap();
assert_eq!(result, BinanceTimeInForce::Gtc);
}
}