use crate::ToField;
use std::fmt;
use time::{Date, Duration, Month, OffsetDateTime, Weekday};
#[cfg(test)]
#[path = "types_tests.rs"]
mod tests;
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Symbol(pub String);
impl Symbol {
pub fn new(s: impl Into<String>) -> Self {
Symbol(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for Symbol {
fn from(s: &str) -> Self {
Symbol(s.to_string())
}
}
impl From<String> for Symbol {
fn from(s: String) -> Self {
Symbol(s)
}
}
impl From<&String> for Symbol {
fn from(s: &String) -> Self {
Symbol(s.clone())
}
}
impl fmt::Display for Symbol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl ToField for Symbol {
fn to_field(&self) -> String {
self.0.clone()
}
}
impl_str_partial_eq!(Symbol);
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Exchange(pub String);
impl Exchange {
pub fn new(s: impl Into<String>) -> Self {
Exchange(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl Default for Exchange {
fn default() -> Self {
Exchange("SMART".to_string())
}
}
impl From<&str> for Exchange {
fn from(s: &str) -> Self {
Exchange(s.to_string())
}
}
impl From<String> for Exchange {
fn from(s: String) -> Self {
Exchange(s)
}
}
impl From<&String> for Exchange {
fn from(s: &String) -> Self {
Exchange(s.clone())
}
}
impl fmt::Display for Exchange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl ToField for Exchange {
fn to_field(&self) -> String {
self.0.clone()
}
}
impl_str_partial_eq!(Exchange);
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Currency(pub String);
impl Currency {
pub fn new(s: impl Into<String>) -> Self {
Currency(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Default for Currency {
fn default() -> Self {
Currency("USD".to_string())
}
}
impl From<&str> for Currency {
fn from(s: &str) -> Self {
Currency(s.to_string())
}
}
impl From<String> for Currency {
fn from(s: String) -> Self {
Currency(s)
}
}
impl From<&String> for Currency {
fn from(s: &String) -> Self {
Currency(s.clone())
}
}
impl fmt::Display for Currency {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl ToField for Currency {
fn to_field(&self) -> String {
self.0.clone()
}
}
impl_str_partial_eq!(Currency);
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum OptionRight {
Call,
Put,
}
impl OptionRight {
pub fn as_str(&self) -> &'static str {
match self {
OptionRight::Call => "C",
OptionRight::Put => "P",
}
}
fn from_wire(s: &str) -> Option<Self> {
match s {
"C" => Some(Self::Call),
"P" => Some(Self::Put),
_ => None,
}
}
}
impl_wire_enum!(OptionRight);
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum SecurityIdType {
Cusip,
Isin,
Sedol,
Ric,
Figi,
}
impl SecurityIdType {
pub fn as_str(&self) -> &'static str {
match self {
SecurityIdType::Cusip => "CUSIP",
SecurityIdType::Isin => "ISIN",
SecurityIdType::Sedol => "SEDOL",
SecurityIdType::Ric => "RIC",
SecurityIdType::Figi => "FIGI",
}
}
fn from_wire(s: &str) -> Option<Self> {
match s {
"CUSIP" => Some(Self::Cusip),
"ISIN" => Some(Self::Isin),
"SEDOL" => Some(Self::Sedol),
"RIC" => Some(Self::Ric),
"FIGI" => Some(Self::Figi),
_ => None,
}
}
}
impl_wire_enum!(SecurityIdType);
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Strike(f64);
impl Strike {
pub fn new(price: f64) -> Result<Self, String> {
if price <= 0.0 {
Err("Strike price must be positive".to_string())
} else {
Ok(Strike(price))
}
}
pub(crate) fn new_unchecked(price: f64) -> Self {
Strike::new(price).expect("Strike price must be positive")
}
pub fn value(&self) -> f64 {
self.0
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExpirationDate {
year: u16,
month: u8,
day: u8,
}
impl ExpirationDate {
pub fn new(year: u16, month: u8, day: u8) -> Self {
ExpirationDate { year, month, day }
}
fn days_until_friday(from_weekday: Weekday) -> i64 {
match from_weekday {
Weekday::Saturday => 6,
Weekday::Sunday => 5,
Weekday::Monday => 4,
Weekday::Tuesday => 3,
Weekday::Wednesday => 2,
Weekday::Thursday => 1,
Weekday::Friday => 0,
}
}
pub fn next_friday() -> Self {
Self::next_friday_from(OffsetDateTime::now_utc().date())
}
fn next_friday_from(today: Date) -> Self {
let days_to_add = match today.weekday() {
Weekday::Friday => 7,
other => Self::days_until_friday(other),
};
let next_friday = today + Duration::days(days_to_add);
ExpirationDate {
year: next_friday.year() as u16,
month: next_friday.month() as u8,
day: next_friday.day(),
}
}
pub fn third_friday_of_month() -> Self {
Self::third_friday_from(OffsetDateTime::now_utc().date())
}
fn third_friday_from(today: Date) -> Self {
let year = today.year();
let month = today.month();
let first_of_month = Date::from_calendar_date(year, month, 1).expect("Valid date");
let days_to_first_friday = Self::days_until_friday(first_of_month.weekday());
let third_friday = first_of_month + Duration::days(days_to_first_friday + 14);
if today > third_friday {
let next_month = if month == Month::December {
Date::from_calendar_date(year + 1, Month::January, 1)
} else {
Date::from_calendar_date(year, month.next(), 1)
}
.expect("Valid date");
let days_to_first_friday_next = Self::days_until_friday(next_month.weekday());
let third_friday_next = next_month + Duration::days(days_to_first_friday_next + 14);
ExpirationDate {
year: third_friday_next.year() as u16,
month: third_friday_next.month() as u8,
day: third_friday_next.day(),
}
} else {
ExpirationDate {
year: third_friday.year() as u16,
month: third_friday.month() as u8,
day: third_friday.day(),
}
}
}
}
impl fmt::Display for ExpirationDate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:04}{:02}{:02}", self.year, self.month, self.day)
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ContractMonth {
year: u16,
month: u8,
}
impl ContractMonth {
pub fn new(year: u16, month: u8) -> Self {
ContractMonth { year, month }
}
pub fn front() -> Self {
let now = OffsetDateTime::now_utc();
Self::front_from(now.year() as u16, now.month() as u8, now.day())
}
fn front_from(current_year: u16, current_month: u8, current_day: u8) -> Self {
if current_day > 15 {
if current_month == 12 {
ContractMonth::new(current_year + 1, 1)
} else {
ContractMonth::new(current_year, current_month + 1)
}
} else {
ContractMonth::new(current_year, current_month)
}
}
pub fn next_quarter() -> Self {
let now = OffsetDateTime::now_utc();
Self::next_quarter_from(now.year() as u16, now.month() as u8, now.day())
}
fn next_quarter_from(current_year: u16, current_month: u8, current_day: u8) -> Self {
let next_quarter_month = match current_month {
1 | 2 => 3,
3 if current_day > 15 => 6,
3 => 3,
4 | 5 => 6,
6 if current_day > 15 => 9,
6 => 6,
7 | 8 => 9,
9 if current_day > 15 => 12,
9 => 9,
10 | 11 => 12,
12 if current_day > 15 => 3,
12 => 12,
_ => 3,
};
let year = if current_month == 12 && next_quarter_month == 3 {
current_year + 1
} else {
current_year
};
ContractMonth::new(year, next_quarter_month)
}
}
impl fmt::Display for ContractMonth {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:04}{:02}", self.year, self.month)
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Cusip(pub String);
impl Cusip {
pub fn new(s: impl Into<String>) -> Self {
Cusip(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for Cusip {
fn from(s: &str) -> Self {
Cusip(s.to_string())
}
}
impl From<String> for Cusip {
fn from(s: String) -> Self {
Cusip(s)
}
}
impl From<&String> for Cusip {
fn from(s: &String) -> Self {
Cusip(s.clone())
}
}
impl fmt::Display for Cusip {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Isin(pub String);
impl Isin {
pub fn new(s: impl Into<String>) -> Self {
Isin(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for Isin {
fn from(s: &str) -> Self {
Isin(s.to_string())
}
}
impl From<String> for Isin {
fn from(s: String) -> Self {
Isin(s)
}
}
impl From<&String> for Isin {
fn from(s: &String) -> Self {
Isin(s.clone())
}
}
impl fmt::Display for Isin {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BondIdentifier {
Cusip(Cusip),
Isin(Isin),
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum LegAction {
#[default]
Buy,
Sell,
SellShort,
}
impl LegAction {
pub fn as_str(&self) -> &'static str {
match self {
LegAction::Buy => "BUY",
LegAction::Sell => "SELL",
LegAction::SellShort => "SSHORT",
}
}
fn from_wire(s: &str) -> Option<Self> {
match s {
"BUY" => Some(Self::Buy),
"SELL" => Some(Self::Sell),
"SSHORT" => Some(Self::SellShort),
_ => None,
}
}
}
impl_wire_enum!(LegAction);
#[derive(Debug, Clone, Copy)]
pub struct Missing;
#[derive(Debug, Clone, Copy)]
pub struct AnyMonth;