1use nautilus_model::enums::{OrderType, TimeInForce};
19use serde::{Deserialize, Serialize};
20
21use crate::common::enums::BinanceTimeInForce;
22
23#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
25#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
26pub enum BinanceSpotOrderType {
27 Limit,
29 Market,
31 StopLoss,
33 StopLossLimit,
35 TakeProfit,
37 TakeProfitLimit,
39 LimitMaker,
41 #[serde(other)]
43 Unknown,
44}
45
46#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
48#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
49pub enum BinanceOrderResponseType {
50 Ack,
52 Result,
54 #[default]
56 Full,
57}
58
59#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
61#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
62pub enum BinanceCancelReplaceMode {
63 StopOnFailure,
65 AllowFailure,
67}
68
69#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
77#[serde(rename_all = "camelCase")]
78pub enum BinanceSpotUserDataEventType {
79 ExecutionReport,
81 OutboundAccountPosition,
83 BalanceUpdate,
85 ServerShutdown,
87 ListenKeyExpired,
89 ExternalLockUpdate,
91 EventStreamTerminated,
93 #[serde(other)]
95 Unknown,
96}
97
98pub fn order_type_to_binance_spot(
104 order_type: OrderType,
105 post_only: bool,
106) -> anyhow::Result<BinanceSpotOrderType> {
107 match (order_type, post_only) {
108 (OrderType::Market, _) => Ok(BinanceSpotOrderType::Market),
109 (OrderType::Limit, true) => Ok(BinanceSpotOrderType::LimitMaker),
110 (OrderType::Limit, false) => Ok(BinanceSpotOrderType::Limit),
111 (OrderType::StopMarket, _) => Ok(BinanceSpotOrderType::StopLoss),
112 (OrderType::StopLimit, _) => Ok(BinanceSpotOrderType::StopLossLimit),
113 (OrderType::MarketIfTouched, _) => Ok(BinanceSpotOrderType::TakeProfit),
114 (OrderType::LimitIfTouched, _) => Ok(BinanceSpotOrderType::TakeProfitLimit),
115 _ => anyhow::bail!("Unsupported order type for Binance Spot: {order_type:?}"),
116 }
117}
118
119pub fn time_in_force_to_binance_spot(
128 tif: TimeInForce,
129 use_gtd: bool,
130) -> anyhow::Result<BinanceTimeInForce> {
131 match tif {
132 TimeInForce::Gtc => Ok(BinanceTimeInForce::Gtc),
133 TimeInForce::Ioc => Ok(BinanceTimeInForce::Ioc),
134 TimeInForce::Fok => Ok(BinanceTimeInForce::Fok),
135 TimeInForce::Gtd if !use_gtd => {
136 log::warn!(
137 "Binance Spot does not support GTD; submitting as GTC because use_gtd=false. Enable manage_gtd_expiry on the submitting strategy"
138 );
139 Ok(BinanceTimeInForce::Gtc)
140 }
141 TimeInForce::Gtd => anyhow::bail!(
142 "Binance Spot does not support native GTD; set use_gtd=false and enable manage_gtd_expiry on the submitting strategy"
143 ),
144 _ => anyhow::bail!("Unsupported time in force for Binance Spot: {tif:?}"),
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use rstest::rstest;
151
152 use super::*;
153
154 #[rstest]
155 #[case(OrderType::Market, false, BinanceSpotOrderType::Market)]
156 #[case(OrderType::Limit, false, BinanceSpotOrderType::Limit)]
157 #[case(OrderType::Limit, true, BinanceSpotOrderType::LimitMaker)]
158 #[case(OrderType::StopMarket, false, BinanceSpotOrderType::StopLoss)]
159 #[case(OrderType::StopLimit, false, BinanceSpotOrderType::StopLossLimit)]
160 #[case(OrderType::MarketIfTouched, false, BinanceSpotOrderType::TakeProfit)]
161 #[case(
162 OrderType::LimitIfTouched,
163 false,
164 BinanceSpotOrderType::TakeProfitLimit
165 )]
166 fn test_order_type_to_binance_spot(
167 #[case] order_type: OrderType,
168 #[case] post_only: bool,
169 #[case] expected: BinanceSpotOrderType,
170 ) {
171 let result = order_type_to_binance_spot(order_type, post_only).unwrap();
172 assert_eq!(result, expected);
173 }
174
175 #[rstest]
176 #[case(OrderType::TrailingStopMarket)]
177 fn test_order_type_to_binance_spot_unsupported(#[case] order_type: OrderType) {
178 let result = order_type_to_binance_spot(order_type, false);
179 result.unwrap_err();
180 }
181
182 #[rstest]
183 #[case(TimeInForce::Gtc, BinanceTimeInForce::Gtc)]
184 #[case(TimeInForce::Ioc, BinanceTimeInForce::Ioc)]
185 #[case(TimeInForce::Fok, BinanceTimeInForce::Fok)]
186 fn test_time_in_force_to_binance_spot(
187 #[case] tif: TimeInForce,
188 #[case] expected: BinanceTimeInForce,
189 ) {
190 let result = time_in_force_to_binance_spot(tif, true).unwrap();
191 assert_eq!(result, expected);
192 }
193
194 #[rstest]
195 #[case(TimeInForce::Gtd)]
196 fn test_time_in_force_to_binance_spot_rejects_gtd(#[case] tif: TimeInForce) {
197 let result = time_in_force_to_binance_spot(tif, true);
198 result.unwrap_err();
199 }
200
201 #[rstest]
202 fn test_time_in_force_to_binance_spot_maps_locally_managed_gtd_to_gtc() {
203 let result = time_in_force_to_binance_spot(TimeInForce::Gtd, false).unwrap();
204 assert_eq!(result, BinanceTimeInForce::Gtc);
205 }
206}