1use crate::ToField;
4use std::fmt;
5use time::{Date, Duration, Month, OffsetDateTime, Weekday};
6
7#[cfg(test)]
8#[path = "types_tests.rs"]
9mod tests;
10
11#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
13#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
14pub struct Symbol(pub String);
15
16impl Symbol {
17 pub fn new(s: impl Into<String>) -> Self {
19 Symbol(s.into())
20 }
21
22 pub fn as_str(&self) -> &str {
24 &self.0
25 }
26}
27
28impl From<&str> for Symbol {
29 fn from(s: &str) -> Self {
30 Symbol(s.to_string())
31 }
32}
33
34impl From<String> for Symbol {
35 fn from(s: String) -> Self {
36 Symbol(s)
37 }
38}
39
40impl From<&String> for Symbol {
41 fn from(s: &String) -> Self {
42 Symbol(s.clone())
43 }
44}
45
46impl fmt::Display for Symbol {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 write!(f, "{}", self.0)
49 }
50}
51
52impl ToField for Symbol {
53 fn to_field(&self) -> String {
54 self.0.clone()
55 }
56}
57
58impl_str_partial_eq!(Symbol);
59
60#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
65#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
66pub struct Exchange(pub String);
67
68impl Exchange {
69 pub fn new(s: impl Into<String>) -> Self {
71 Exchange(s.into())
72 }
73
74 pub fn as_str(&self) -> &str {
76 &self.0
77 }
78
79 pub fn is_empty(&self) -> bool {
81 self.0.is_empty()
82 }
83}
84
85impl Default for Exchange {
86 fn default() -> Self {
87 Exchange("SMART".to_string())
88 }
89}
90
91impl From<&str> for Exchange {
92 fn from(s: &str) -> Self {
93 Exchange(s.to_string())
94 }
95}
96
97impl From<String> for Exchange {
98 fn from(s: String) -> Self {
99 Exchange(s)
100 }
101}
102
103impl From<&String> for Exchange {
104 fn from(s: &String) -> Self {
105 Exchange(s.clone())
106 }
107}
108
109impl fmt::Display for Exchange {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 write!(f, "{}", self.0)
112 }
113}
114
115impl ToField for Exchange {
116 fn to_field(&self) -> String {
117 self.0.clone()
118 }
119}
120
121impl_str_partial_eq!(Exchange);
122
123#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
128#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
129pub struct Currency(pub String);
130
131impl Currency {
132 pub fn new(s: impl Into<String>) -> Self {
134 Currency(s.into())
135 }
136
137 pub fn as_str(&self) -> &str {
139 &self.0
140 }
141}
142
143impl Default for Currency {
144 fn default() -> Self {
145 Currency("USD".to_string())
146 }
147}
148
149impl From<&str> for Currency {
150 fn from(s: &str) -> Self {
151 Currency(s.to_string())
152 }
153}
154
155impl From<String> for Currency {
156 fn from(s: String) -> Self {
157 Currency(s)
158 }
159}
160
161impl From<&String> for Currency {
162 fn from(s: &String) -> Self {
163 Currency(s.clone())
164 }
165}
166
167impl fmt::Display for Currency {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 write!(f, "{}", self.0)
170 }
171}
172
173impl ToField for Currency {
174 fn to_field(&self) -> String {
175 self.0.clone()
176 }
177}
178
179impl_str_partial_eq!(Currency);
180
181#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
186#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
187#[non_exhaustive]
188pub enum OptionRight {
189 Call,
191 Put,
193}
194
195impl OptionRight {
196 pub fn as_str(&self) -> &'static str {
198 match self {
199 OptionRight::Call => "C",
200 OptionRight::Put => "P",
201 }
202 }
203
204 fn from_wire(s: &str) -> Option<Self> {
205 match s {
206 "C" => Some(Self::Call),
207 "P" => Some(Self::Put),
208 _ => None,
209 }
210 }
211}
212
213impl_wire_enum!(OptionRight);
214
215#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
221#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
222#[non_exhaustive]
223pub enum SecurityIdType {
224 Cusip,
226 Isin,
228 Sedol,
230 Ric,
232 Figi,
234}
235
236impl SecurityIdType {
237 pub fn as_str(&self) -> &'static str {
239 match self {
240 SecurityIdType::Cusip => "CUSIP",
241 SecurityIdType::Isin => "ISIN",
242 SecurityIdType::Sedol => "SEDOL",
243 SecurityIdType::Ric => "RIC",
244 SecurityIdType::Figi => "FIGI",
245 }
246 }
247
248 fn from_wire(s: &str) -> Option<Self> {
249 match s {
250 "CUSIP" => Some(Self::Cusip),
251 "ISIN" => Some(Self::Isin),
252 "SEDOL" => Some(Self::Sedol),
253 "RIC" => Some(Self::Ric),
254 "FIGI" => Some(Self::Figi),
255 _ => None,
256 }
257 }
258}
259
260impl_wire_enum!(SecurityIdType);
261
262#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
264#[derive(Debug, Clone, Copy, PartialEq)]
265pub struct Strike(f64);
266
267impl Strike {
268 pub fn new(price: f64) -> Result<Self, String> {
270 if price <= 0.0 {
271 Err("Strike price must be positive".to_string())
272 } else {
273 Ok(Strike(price))
274 }
275 }
276
277 pub(crate) fn new_unchecked(price: f64) -> Self {
279 Strike::new(price).expect("Strike price must be positive")
280 }
281
282 pub fn value(&self) -> f64 {
284 self.0
285 }
286}
287
288#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
290#[derive(Debug, Clone, PartialEq, Eq)]
291pub struct ExpirationDate {
292 year: u16,
293 month: u8,
294 day: u8,
295}
296
297impl ExpirationDate {
298 pub fn new(year: u16, month: u8, day: u8) -> Self {
300 ExpirationDate { year, month, day }
301 }
302
303 fn days_until_friday(from_weekday: Weekday) -> i64 {
305 match from_weekday {
306 Weekday::Saturday => 6,
307 Weekday::Sunday => 5,
308 Weekday::Monday => 4,
309 Weekday::Tuesday => 3,
310 Weekday::Wednesday => 2,
311 Weekday::Thursday => 1,
312 Weekday::Friday => 0,
313 }
314 }
315
316 pub fn next_friday() -> Self {
318 Self::next_friday_from(OffsetDateTime::now_utc().date())
319 }
320
321 fn next_friday_from(today: Date) -> Self {
322 let days_to_add = match today.weekday() {
323 Weekday::Friday => 7,
324 other => Self::days_until_friday(other),
325 };
326 let next_friday = today + Duration::days(days_to_add);
327
328 ExpirationDate {
329 year: next_friday.year() as u16,
330 month: next_friday.month() as u8,
331 day: next_friday.day(),
332 }
333 }
334
335 pub fn third_friday_of_month() -> Self {
337 Self::third_friday_from(OffsetDateTime::now_utc().date())
338 }
339
340 fn third_friday_from(today: Date) -> Self {
341 let year = today.year();
342 let month = today.month();
343
344 let first_of_month = Date::from_calendar_date(year, month, 1).expect("Valid date");
346
347 let days_to_first_friday = Self::days_until_friday(first_of_month.weekday());
349 let third_friday = first_of_month + Duration::days(days_to_first_friday + 14);
350
351 if today > third_friday {
353 let next_month = if month == Month::December {
354 Date::from_calendar_date(year + 1, Month::January, 1)
355 } else {
356 Date::from_calendar_date(year, month.next(), 1)
357 }
358 .expect("Valid date");
359
360 let days_to_first_friday_next = Self::days_until_friday(next_month.weekday());
361 let third_friday_next = next_month + Duration::days(days_to_first_friday_next + 14);
362
363 ExpirationDate {
364 year: third_friday_next.year() as u16,
365 month: third_friday_next.month() as u8,
366 day: third_friday_next.day(),
367 }
368 } else {
369 ExpirationDate {
370 year: third_friday.year() as u16,
371 month: third_friday.month() as u8,
372 day: third_friday.day(),
373 }
374 }
375 }
376}
377
378impl fmt::Display for ExpirationDate {
379 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
380 write!(f, "{:04}{:02}{:02}", self.year, self.month, self.day)
381 }
382}
383
384#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
386#[derive(Debug, Clone, PartialEq, Eq)]
387pub struct ContractMonth {
388 year: u16,
389 month: u8,
390}
391
392impl ContractMonth {
393 pub fn new(year: u16, month: u8) -> Self {
395 ContractMonth { year, month }
396 }
397
398 pub fn front() -> Self {
400 let now = OffsetDateTime::now_utc();
401 Self::front_from(now.year() as u16, now.month() as u8, now.day())
402 }
403
404 fn front_from(current_year: u16, current_month: u8, current_day: u8) -> Self {
405 if current_day > 15 {
408 if current_month == 12 {
409 ContractMonth::new(current_year + 1, 1)
410 } else {
411 ContractMonth::new(current_year, current_month + 1)
412 }
413 } else {
414 ContractMonth::new(current_year, current_month)
415 }
416 }
417
418 pub fn next_quarter() -> Self {
420 let now = OffsetDateTime::now_utc();
421 Self::next_quarter_from(now.year() as u16, now.month() as u8, now.day())
422 }
423
424 fn next_quarter_from(current_year: u16, current_month: u8, current_day: u8) -> Self {
425 let next_quarter_month = match current_month {
427 1 | 2 => 3,
428 3 if current_day > 15 => 6,
429 3 => 3,
430 4 | 5 => 6,
431 6 if current_day > 15 => 9,
432 6 => 6,
433 7 | 8 => 9,
434 9 if current_day > 15 => 12,
435 9 => 9,
436 10 | 11 => 12,
437 12 if current_day > 15 => 3,
438 12 => 12,
439 _ => 3,
440 };
441
442 let year = if current_month == 12 && next_quarter_month == 3 {
444 current_year + 1
445 } else {
446 current_year
447 };
448
449 ContractMonth::new(year, next_quarter_month)
450 }
451}
452
453impl fmt::Display for ContractMonth {
454 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
455 write!(f, "{:04}{:02}", self.year, self.month)
456 }
457}
458
459#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
461#[derive(Debug, Clone, PartialEq, Eq)]
462pub struct Cusip(pub String);
463
464impl Cusip {
465 pub fn new(s: impl Into<String>) -> Self {
467 Cusip(s.into())
468 }
469
470 pub fn as_str(&self) -> &str {
472 &self.0
473 }
474}
475
476impl From<&str> for Cusip {
477 fn from(s: &str) -> Self {
478 Cusip(s.to_string())
479 }
480}
481
482impl From<String> for Cusip {
483 fn from(s: String) -> Self {
484 Cusip(s)
485 }
486}
487
488impl From<&String> for Cusip {
489 fn from(s: &String) -> Self {
490 Cusip(s.clone())
491 }
492}
493
494impl fmt::Display for Cusip {
495 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
496 write!(f, "{}", self.0)
497 }
498}
499
500#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
502#[derive(Debug, Clone, PartialEq, Eq)]
503pub struct Isin(pub String);
504
505impl Isin {
506 pub fn new(s: impl Into<String>) -> Self {
508 Isin(s.into())
509 }
510
511 pub fn as_str(&self) -> &str {
513 &self.0
514 }
515}
516
517impl From<&str> for Isin {
518 fn from(s: &str) -> Self {
519 Isin(s.to_string())
520 }
521}
522
523impl From<String> for Isin {
524 fn from(s: String) -> Self {
525 Isin(s)
526 }
527}
528
529impl From<&String> for Isin {
530 fn from(s: &String) -> Self {
531 Isin(s.clone())
532 }
533}
534
535impl fmt::Display for Isin {
536 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
537 write!(f, "{}", self.0)
538 }
539}
540
541#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
543#[derive(Debug, Clone, PartialEq, Eq)]
544pub enum BondIdentifier {
545 Cusip(Cusip),
547 Isin(Isin),
549}
550
551#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
556#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
557#[non_exhaustive]
558pub enum LegAction {
559 #[default]
561 Buy,
562 Sell,
564 SellShort,
566}
567
568impl LegAction {
569 pub fn as_str(&self) -> &'static str {
571 match self {
572 LegAction::Buy => "BUY",
573 LegAction::Sell => "SELL",
574 LegAction::SellShort => "SSHORT",
575 }
576 }
577
578 fn from_wire(s: &str) -> Option<Self> {
579 match s {
580 "BUY" => Some(Self::Buy),
581 "SELL" => Some(Self::Sell),
582 "SSHORT" => Some(Self::SellShort),
583 _ => None,
584 }
585 }
586}
587
588impl_wire_enum!(LegAction);
589
590#[derive(Debug, Clone, Copy)]
592pub struct Missing;
593
594#[derive(Debug, Clone, Copy)]
599pub struct AnyMonth;