1use derive_builder::Builder;
2use rust_decimal::Decimal;
3use strum_macros::{Display, EnumString};
4use time::OffsetDateTime;
5
6use crate::{Extensions, MarketKind, Symbol};
7
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9#[non_exhaustive]
10#[derive(Debug, Clone, PartialEq, Eq, Hash)]
11pub struct OrderId(pub String);
12
13impl OrderId {
14 pub fn new(value: impl Into<String>) -> Self {
15 Self(value.into())
16 }
17}
18
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20#[non_exhaustive]
21#[derive(Debug, Clone, PartialEq, Eq, Hash)]
22pub struct ClientOrderId(pub String);
23
24impl ClientOrderId {
25 pub fn new(value: impl Into<String>) -> Self {
26 Self(value.into())
27 }
28}
29
30#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
31#[non_exhaustive]
32#[derive(Debug, Clone, PartialEq, Eq, Hash)]
33pub enum OrderKey {
34 Exchange(OrderId),
35 Client(ClientOrderId),
36}
37
38#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
39#[non_exhaustive]
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Display, EnumString)]
41#[strum(serialize_all = "snake_case", ascii_case_insensitive)]
42pub enum OrderSide {
43 Buy,
44 Sell,
45}
46
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48#[non_exhaustive]
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Display, EnumString)]
50#[strum(serialize_all = "snake_case", ascii_case_insensitive)]
51pub enum OrderType {
52 Market,
53 Limit,
54 StopMarket,
55 StopLimit,
56 PostOnly,
57}
58
59#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
60#[non_exhaustive]
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Display, EnumString)]
62#[strum(serialize_all = "SCREAMING_SNAKE_CASE", ascii_case_insensitive)]
63pub enum TimeInForce {
64 Gtc,
65 Ioc,
66 Fok,
67 Gtx,
68}
69
70#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
71#[non_exhaustive]
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Display, EnumString)]
73#[strum(serialize_all = "snake_case", ascii_case_insensitive)]
74pub enum OrderStatus {
75 New,
76 PartiallyFilled,
77 Filled,
78 Canceled,
79 Rejected,
80 Expired,
81}
82
83#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
84#[non_exhaustive]
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Display, EnumString)]
86#[strum(serialize_all = "snake_case", ascii_case_insensitive)]
87pub enum PositionSide {
88 Long,
89 Short,
90 Both,
91}
92
93#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
94#[non_exhaustive]
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Display, EnumString)]
96#[strum(serialize_all = "snake_case", ascii_case_insensitive)]
97pub enum MarginMode {
98 Cross,
99 Isolated,
100}
101
102#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
103#[non_exhaustive]
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
105pub enum OrderQuantity {
106 Base(Decimal),
107 Quote(Decimal),
108}
109
110#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
111#[non_exhaustive]
112#[derive(Debug, Clone, PartialEq, Builder)]
113#[builder(pattern = "owned", setter(into))]
114#[builder(build_fn(validate = "Self::validate"))]
115pub struct SpotOrderRequest {
116 pub symbol: Symbol,
117 pub side: OrderSide,
118 pub order_type: OrderType,
119 pub quantity: OrderQuantity,
120 #[builder(default)]
121 pub price: Option<Decimal>,
122 #[builder(default)]
123 pub time_in_force: Option<TimeInForce>,
124 #[builder(default)]
125 pub client_order_id: Option<ClientOrderId>,
126 #[builder(default)]
127 pub extensions: Extensions,
128}
129
130impl SpotOrderRequest {
131 pub fn builder() -> SpotOrderRequestBuilder {
132 SpotOrderRequestBuilder::default()
133 }
134}
135
136impl SpotOrderRequestBuilder {
137 fn validate(&self) -> Result<(), String> {
138 let Some(quantity) = self.quantity else {
139 return Err("quantity is required".to_owned());
140 };
141
142 match quantity {
143 OrderQuantity::Base(value) if value <= Decimal::ZERO => {
144 Err("quantity must be greater than zero".to_owned())
145 }
146 OrderQuantity::Quote(value) if value <= Decimal::ZERO => {
147 Err("quote quantity must be greater than zero".to_owned())
148 }
149 _ => Ok(()),
150 }
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::{OrderQuantity, OrderSide, OrderType, SpotOrderRequest};
157 use crate::Symbol;
158 use rust_decimal::Decimal;
159
160 #[test]
161 fn spot_order_request_builder_requires_positive_quantity_mode() {
162 let missing = SpotOrderRequest::builder()
163 .symbol(Symbol::spot("BTCUSDT"))
164 .side(OrderSide::Buy)
165 .order_type(OrderType::Market)
166 .build()
167 .expect_err("missing quantity must be rejected");
168 assert!(missing.to_string().contains("quantity is required"));
169
170 let zero_base = SpotOrderRequest::builder()
171 .symbol(Symbol::spot("BTCUSDT"))
172 .side(OrderSide::Buy)
173 .order_type(OrderType::Market)
174 .quantity(OrderQuantity::Base(Decimal::ZERO))
175 .build()
176 .expect_err("zero base quantity must be rejected");
177 assert!(zero_base
178 .to_string()
179 .contains("quantity must be greater than zero"));
180
181 let zero_quote = SpotOrderRequest::builder()
182 .symbol(Symbol::spot("BTCUSDT"))
183 .side(OrderSide::Buy)
184 .order_type(OrderType::Market)
185 .quantity(OrderQuantity::Quote(Decimal::ZERO))
186 .build()
187 .expect_err("zero quote quantity must be rejected");
188 assert!(zero_quote
189 .to_string()
190 .contains("quote quantity must be greater than zero"));
191 }
192}
193
194#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
195#[non_exhaustive]
196#[derive(Debug, Clone, PartialEq, Builder)]
197#[builder(pattern = "owned", setter(into))]
198pub struct FuturesOrderRequest {
199 pub symbol: Symbol,
200 pub side: OrderSide,
201 pub order_type: OrderType,
202 pub quantity: Decimal,
203 #[builder(default)]
204 pub price: Option<Decimal>,
205 #[builder(default)]
206 pub time_in_force: Option<TimeInForce>,
207 #[builder(default)]
208 pub position_side: Option<PositionSide>,
209 #[builder(default)]
210 pub reduce_only: bool,
211 #[builder(default)]
212 pub client_order_id: Option<ClientOrderId>,
213 #[builder(default)]
214 pub extensions: Extensions,
215}
216
217impl FuturesOrderRequest {
218 pub fn builder() -> FuturesOrderRequestBuilder {
219 FuturesOrderRequestBuilder::default()
220 }
221}
222
223#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
224#[non_exhaustive]
225#[derive(Debug, Clone, PartialEq, Eq)]
226pub struct SpotCancelOrderRequest {
227 pub symbol: Symbol,
228 pub key: OrderKey,
229}
230
231impl SpotCancelOrderRequest {
232 pub fn new(symbol: Symbol, key: OrderKey) -> Self {
233 Self { symbol, key }
234 }
235}
236
237#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
238#[non_exhaustive]
239#[derive(Debug, Clone, PartialEq, Eq)]
240pub struct FuturesCancelOrderRequest {
241 pub symbol: Symbol,
242 pub key: OrderKey,
243}
244
245impl FuturesCancelOrderRequest {
246 pub fn new(symbol: Symbol, key: OrderKey) -> Self {
247 Self { symbol, key }
248 }
249}
250
251#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
252#[non_exhaustive]
253#[derive(Debug, Clone, PartialEq, Eq)]
254pub struct SpotOrderQuery {
255 pub symbol: Symbol,
256 pub key: OrderKey,
257}
258
259impl SpotOrderQuery {
260 pub fn new(symbol: Symbol, key: OrderKey) -> Self {
261 Self { symbol, key }
262 }
263}
264
265#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
266#[non_exhaustive]
267#[derive(Debug, Clone, PartialEq, Eq)]
268pub struct FuturesOrderQuery {
269 pub symbol: Symbol,
270 pub key: OrderKey,
271}
272
273impl FuturesOrderQuery {
274 pub fn new(symbol: Symbol, key: OrderKey) -> Self {
275 Self { symbol, key }
276 }
277}
278
279#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
280#[non_exhaustive]
281#[derive(Debug, Clone, PartialEq, Builder)]
282#[builder(pattern = "owned", setter(into))]
283pub struct Order {
284 pub id: OrderId,
285 #[builder(default)]
286 pub client_order_id: Option<ClientOrderId>,
287 pub symbol: Symbol,
288 pub market_kind: MarketKind,
289 pub side: OrderSide,
290 pub order_type: OrderType,
291 pub status: OrderStatus,
292 #[builder(default)]
293 pub time_in_force: Option<TimeInForce>,
294 #[builder(default)]
295 pub price: Option<Decimal>,
296 #[builder(default)]
297 pub average_price: Option<Decimal>,
298 pub quantity: Decimal,
299 pub filled_quantity: Decimal,
300 #[builder(default)]
301 pub original_quote_quantity: Option<Decimal>,
302 #[builder(default)]
303 pub cumulative_quote_quantity: Option<Decimal>,
304 #[cfg_attr(
305 feature = "serde",
306 serde(with = "time::serde::timestamp::milliseconds")
307 )]
308 pub created_at: OffsetDateTime,
309 #[cfg_attr(
310 feature = "serde",
311 serde(with = "time::serde::timestamp::milliseconds::option")
312 )]
313 #[builder(default)]
314 pub updated_at: Option<OffsetDateTime>,
315 #[builder(default)]
316 pub extensions: Extensions,
317}
318
319impl Order {
320 pub fn builder() -> OrderBuilder {
321 OrderBuilder::default()
322 }
323}
324
325#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
326#[non_exhaustive]
327#[derive(Debug, Clone, PartialEq, Builder)]
328#[builder(pattern = "owned", setter(into))]
329pub struct Fill {
330 #[builder(default)]
331 pub id: Option<String>,
332 pub order_id: OrderId,
333 pub symbol: Symbol,
334 pub side: OrderSide,
335 pub price: Decimal,
336 pub quantity: Decimal,
337 #[builder(default)]
338 pub quote_quantity: Option<Decimal>,
339 #[builder(default)]
340 pub fee: Option<Decimal>,
341 #[builder(default)]
342 pub fee_asset: Option<String>,
343 #[cfg_attr(
344 feature = "serde",
345 serde(with = "time::serde::timestamp::milliseconds")
346 )]
347 pub timestamp: OffsetDateTime,
348 #[builder(default)]
349 pub extensions: Extensions,
350}
351
352impl Fill {
353 pub fn builder() -> FillBuilder {
354 FillBuilder::default()
355 }
356}
357
358#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
359#[non_exhaustive]
360#[derive(Debug, Clone, PartialEq, Builder)]
361#[builder(pattern = "owned", setter(into))]
362pub struct SetLeverageRequest {
363 pub symbol: Symbol,
364 pub leverage: Decimal,
365 #[builder(default)]
366 pub margin_mode: Option<MarginMode>,
367 #[builder(default)]
368 pub position_side: Option<PositionSide>,
369}
370
371impl SetLeverageRequest {
372 pub fn builder() -> SetLeverageRequestBuilder {
373 SetLeverageRequestBuilder::default()
374 }
375}