1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub struct OrderId(pub i32);
8
9impl OrderId {
10 pub fn new(id: i32) -> Self {
12 Self(id)
13 }
14
15 pub fn value(&self) -> i32 {
17 self.0
18 }
19}
20
21impl fmt::Display for OrderId {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 write!(f, "{}", self.0)
24 }
25}
26
27impl From<i32> for OrderId {
28 fn from(id: i32) -> Self {
29 Self(id)
30 }
31}
32
33impl From<OrderId> for i32 {
34 fn from(id: OrderId) -> i32 {
35 id.0
36 }
37}
38
39#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
42pub struct BracketOrderIds {
43 pub parent: OrderId,
45 pub take_profit: OrderId,
47 pub stop_loss: OrderId,
49}
50
51impl BracketOrderIds {
52 pub fn new(parent: i32, take_profit: i32, stop_loss: i32) -> Self {
54 Self {
55 parent: OrderId(parent),
56 take_profit: OrderId(take_profit),
57 stop_loss: OrderId(stop_loss),
58 }
59 }
60
61 pub fn as_vec(&self) -> Vec<OrderId> {
63 vec![self.parent, self.take_profit, self.stop_loss]
64 }
65
66 pub fn as_i32_vec(&self) -> Vec<i32> {
68 vec![self.parent.0, self.take_profit.0, self.stop_loss.0]
69 }
70}
71
72impl fmt::Display for BracketOrderIds {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 write!(
75 f,
76 "BracketOrder(parent: {}, tp: {}, sl: {})",
77 self.parent, self.take_profit, self.stop_loss
78 )
79 }
80}
81
82impl From<Vec<i32>> for BracketOrderIds {
83 fn from(ids: Vec<i32>) -> Self {
84 assert_eq!(ids.len(), 3, "BracketOrderIds requires exactly 3 order IDs");
85 Self::new(ids[0], ids[1], ids[2])
86 }
87}
88
89impl From<[i32; 3]> for BracketOrderIds {
90 fn from(ids: [i32; 3]) -> Self {
91 Self::new(ids[0], ids[1], ids[2])
92 }
93}
94
95#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
97#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
98pub struct Quantity(f64);
99
100impl Quantity {
101 pub fn new(value: f64) -> Result<Self, ValidationError> {
103 if value <= 0.0 {
104 return Err(ValidationError::InvalidQuantity(value));
105 }
106 if value.is_nan() || value.is_infinite() {
107 return Err(ValidationError::InvalidQuantity(value));
108 }
109 Ok(Self(value))
110 }
111
112 pub fn value(&self) -> f64 {
114 self.0
115 }
116}
117
118#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
120#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
121pub struct Price(f64);
122
123impl Price {
124 pub fn new(value: f64) -> Result<Self, ValidationError> {
126 if value.is_nan() || value.is_infinite() {
127 return Err(ValidationError::InvalidPrice(value));
128 }
129 Ok(Self(value))
130 }
131
132 pub fn value(&self) -> f64 {
134 self.0
135 }
136}
137
138#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
140#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
141pub enum TimeInForce {
142 Day,
144 GoodTillCancel,
146 ImmediateOrCancel,
148 GoodTillDate {
150 date: String,
152 },
153 FillOrKill,
155 GoodTillCrossing,
157 DayTillCanceled,
159 Auction,
161 OpeningAuction,
163}
164
165impl TimeInForce {
166 pub fn as_str(&self) -> &str {
168 match self {
169 Self::Day => "DAY",
170 Self::GoodTillCancel => "GTC",
171 Self::ImmediateOrCancel => "IOC",
172 Self::GoodTillDate { .. } => "GTD",
173 Self::FillOrKill => "FOK",
174 Self::GoodTillCrossing => "GTX",
175 Self::DayTillCanceled => "DTC",
176 Self::Auction => "AUC",
177 Self::OpeningAuction => "OPG",
178 }
179 }
180}
181
182#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
184#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
185pub enum AuctionType {
186 Opening,
188 Closing,
190 Volatility,
192}
193
194impl AuctionType {
195 pub fn to_strategy(&self) -> i32 {
197 match self {
198 Self::Opening => 1,
199 Self::Closing => 2,
200 Self::Volatility => 4,
201 }
202 }
203}
204
205#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
207#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
208pub enum OrderType {
209 Market,
212 Limit,
214 Stop,
216 StopLimit,
218
219 TrailingStop,
222 TrailingStopLimit,
224
225 MarketOnClose,
228 LimitOnClose,
230 MarketOnOpen,
232 LimitOnOpen,
234 AtAuction,
236
237 MarketIfTouched,
240 LimitIfTouched,
242
243 MarketWithProtection,
246 StopWithProtection,
248
249 MarketToLimit,
252 Midprice,
254
255 PeggedToMarket,
258 PeggedToStock,
260 PeggedToMidpoint,
262 PeggedToBenchmark,
264 PegBest,
266
267 Relative,
270 PassiveRelative,
272
273 Volatility,
276 BoxTop,
278 AuctionLimit,
280 AuctionRelative,
282
283 ComboLimit,
286 ComboMarket,
288 RelativeLimitCombo,
290 RelativeMarketCombo,
292}
293
294impl OrderType {
295 pub fn as_str(&self) -> &str {
297 match self {
298 Self::Market => "MKT",
300 Self::Limit => "LMT",
301 Self::Stop => "STP",
302 Self::StopLimit => "STP LMT",
303
304 Self::TrailingStop => "TRAIL",
306 Self::TrailingStopLimit => "TRAIL LIMIT",
307
308 Self::MarketOnClose => "MOC",
310 Self::LimitOnClose => "LOC",
311 Self::MarketOnOpen => "MKT",
312 Self::LimitOnOpen => "LMT",
313 Self::AtAuction => "MTL",
314
315 Self::MarketIfTouched => "MIT",
317 Self::LimitIfTouched => "LIT",
318
319 Self::MarketWithProtection => "MKT PRT",
321 Self::StopWithProtection => "STP PRT",
322
323 Self::MarketToLimit => "MTL",
325 Self::Midprice => "MIDPRICE",
326
327 Self::PeggedToMarket => "PEG MKT",
329 Self::PeggedToStock => "PEG STK",
330 Self::PeggedToMidpoint => "PEG MID",
331 Self::PeggedToBenchmark => "PEG BENCH",
332 Self::PegBest => "PEG BEST",
333
334 Self::Relative => "REL",
336 Self::PassiveRelative => "PASSV REL",
337
338 Self::Volatility => "VOL",
340 Self::BoxTop => "BOX TOP",
341 Self::AuctionLimit => "LMT",
342 Self::AuctionRelative => "REL",
343
344 Self::ComboLimit => "LMT",
346 Self::ComboMarket => "MKT",
347 Self::RelativeLimitCombo => "REL + LMT",
348 Self::RelativeMarketCombo => "REL + MKT",
349 }
350 }
351
352 pub fn requires_limit_price(&self) -> bool {
354 matches!(
355 self,
356 Self::Limit
357 | Self::StopLimit
358 | Self::LimitOnClose
359 | Self::LimitOnOpen
360 | Self::LimitIfTouched
361 | Self::AuctionLimit
362 | Self::ComboLimit
363 | Self::RelativeLimitCombo
364 | Self::AtAuction )
366 }
367
368 pub fn requires_aux_price(&self) -> bool {
370 matches!(
371 self,
372 Self::Stop
373 | Self::StopLimit
374 | Self::MarketIfTouched
375 | Self::LimitIfTouched
376 | Self::StopWithProtection
377 | Self::TrailingStop
378 | Self::TrailingStopLimit
379 | Self::Relative
380 | Self::PassiveRelative
381 | Self::AuctionRelative
382 | Self::PeggedToMarket
383 )
384 }
385}
386
387#[derive(Debug, Clone, PartialEq)]
389pub enum ValidationError {
390 InvalidQuantity(f64),
392 InvalidPrice(f64),
394 MissingRequiredField(&'static str),
396 InvalidCombination(String),
398 InvalidStopPrice {
400 stop: f64,
402 current: f64,
404 },
405 InvalidLimitPrice {
407 limit: f64,
409 current: f64,
411 },
412 InvalidBracketOrder(String),
414 InvalidPercentage {
416 field: &'static str,
418 value: f64,
420 min: f64,
422 max: f64,
424 },
425}
426
427impl fmt::Display for ValidationError {
428 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
429 match self {
430 Self::InvalidQuantity(q) => write!(f, "Invalid quantity: {}", q),
431 Self::InvalidPrice(p) => write!(f, "Invalid price: {}", p),
432 Self::MissingRequiredField(field) => write!(f, "Missing required field: {}", field),
433 Self::InvalidCombination(msg) => write!(f, "Invalid combination: {}", msg),
434 Self::InvalidStopPrice { stop, current } => {
435 write!(f, "Invalid stop price {} for current price {}", stop, current)
436 }
437 Self::InvalidLimitPrice { limit, current } => {
438 write!(f, "Invalid limit price {} for current price {}", limit, current)
439 }
440 Self::InvalidBracketOrder(msg) => write!(f, "Invalid bracket order: {}", msg),
441 Self::InvalidPercentage { field, value, min, max } => {
442 write!(f, "Invalid {}: {} (must be between {} and {})", field, value, min, max)
443 }
444 }
445 }
446}
447
448impl std::error::Error for ValidationError {}
449
450#[derive(Debug, Clone, PartialEq)]
452pub struct OrderAnalysis {
453 pub initial_margin: Option<f64>,
455 pub maintenance_margin: Option<f64>,
457 pub commission: Option<f64>,
459 pub commission_currency: String,
461 pub warning_text: String,
463}
464
465#[cfg(test)]
466mod tests;