use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct FutOptTicker {
#[serde(default)]
pub date: Option<String>,
#[serde(rename = "type")]
pub contract_type: Option<String>,
pub exchange: Option<String>,
pub symbol: String,
pub name: Option<String>,
#[serde(rename = "referencePrice")]
pub reference_price: Option<f64>,
#[serde(rename = "startDate")]
pub start_date: Option<String>,
#[serde(rename = "endDate")]
pub end_date: Option<String>,
#[serde(rename = "settlementDate")]
pub settlement_date: Option<String>,
#[serde(rename = "contractType")]
pub contract_sub_type: Option<String>,
#[serde(rename = "isDynamicBanding", default)]
pub is_dynamic_banding: bool,
#[serde(rename = "flowGroup")]
pub flow_group: Option<i32>,
}
impl FutOptTicker {
pub fn has_contract_dates(&self) -> bool {
self.start_date.is_some() && self.end_date.is_some()
}
pub fn is_future(&self) -> bool {
self.contract_type
.as_ref()
.is_some_and(|t| t.to_uppercase() == "FUTURE")
}
pub fn is_option(&self) -> bool {
self.contract_type
.as_ref()
.is_some_and(|t| t.to_uppercase() == "OPTION")
}
pub fn is_expired_on(&self, date: &str) -> Option<bool> {
self.end_date.as_ref().map(|end| date > end.as_str())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_futopt_ticker_deserialization() {
let json = r#"{
"date": "2024-01-15",
"type": "FUTURE",
"exchange": "TAIFEX",
"symbol": "TXFC4",
"name": "臺股期貨 03",
"referencePrice": 17500.0,
"startDate": "2023-12-20",
"endDate": "2024-03-20",
"settlementDate": "2024-03-20"
}"#;
let ticker: FutOptTicker = serde_json::from_str(json).unwrap();
assert_eq!(ticker.symbol, "TXFC4");
assert_eq!(ticker.name.as_deref(), Some("臺股期貨 03"));
assert_eq!(ticker.contract_type.as_deref(), Some("FUTURE"));
assert_eq!(ticker.exchange.as_deref(), Some("TAIFEX"));
assert_eq!(ticker.reference_price, Some(17500.0));
assert_eq!(ticker.start_date.as_deref(), Some("2023-12-20"));
assert_eq!(ticker.end_date.as_deref(), Some("2024-03-20"));
assert_eq!(ticker.settlement_date.as_deref(), Some("2024-03-20"));
}
#[test]
fn test_futopt_ticker_with_tickers_fields() {
let json = r#"{
"date": "2024-01-15",
"type": "FUTURE",
"contractType": "I",
"symbol": "TXFC4",
"name": "臺股期貨 03",
"referencePrice": 17500.0,
"isDynamicBanding": true,
"flowGroup": 1,
"startDate": "2023-12-20",
"endDate": "2024-03-20",
"settlementDate": "2024-03-20"
}"#;
let ticker: FutOptTicker = serde_json::from_str(json).unwrap();
assert_eq!(ticker.contract_sub_type.as_deref(), Some("I"));
assert!(ticker.is_dynamic_banding);
assert_eq!(ticker.flow_group, Some(1));
}
#[test]
fn test_futopt_ticker_minimal() {
let json = r#"{"date": "2024-01-15", "symbol": "TXFC4"}"#;
let ticker: FutOptTicker = serde_json::from_str(json).unwrap();
assert_eq!(ticker.symbol, "TXFC4");
assert!(ticker.name.is_none());
assert!(ticker.reference_price.is_none());
assert!(!ticker.has_contract_dates());
}
#[test]
fn test_futopt_ticker_has_contract_dates() {
let mut ticker = FutOptTicker::default();
assert!(!ticker.has_contract_dates());
ticker.start_date = Some("2023-12-20".to_string());
assert!(!ticker.has_contract_dates());
ticker.end_date = Some("2024-03-20".to_string());
assert!(ticker.has_contract_dates());
}
#[test]
fn test_futopt_ticker_is_future() {
let mut ticker = FutOptTicker::default();
assert!(!ticker.is_future());
ticker.contract_type = Some("FUTURE".to_string());
assert!(ticker.is_future());
ticker.contract_type = Some("Future".to_string());
assert!(ticker.is_future());
ticker.contract_type = Some("OPTION".to_string());
assert!(!ticker.is_future());
}
#[test]
fn test_futopt_ticker_is_option() {
let mut ticker = FutOptTicker::default();
assert!(!ticker.is_option());
ticker.contract_type = Some("OPTION".to_string());
assert!(ticker.is_option());
ticker.contract_type = Some("Option".to_string());
assert!(ticker.is_option());
ticker.contract_type = Some("FUTURE".to_string());
assert!(!ticker.is_option());
}
#[test]
fn test_futopt_ticker_is_expired_on() {
let mut ticker = FutOptTicker::default();
assert!(ticker.is_expired_on("2024-01-15").is_none());
ticker.end_date = Some("2024-03-20".to_string());
assert_eq!(ticker.is_expired_on("2024-03-19"), Some(false));
assert_eq!(ticker.is_expired_on("2024-03-20"), Some(false));
assert_eq!(ticker.is_expired_on("2024-03-21"), Some(true));
}
#[test]
fn test_option_ticker_example() {
let json = r#"{
"date": "2024-01-15",
"type": "OPTION",
"exchange": "TAIFEX",
"symbol": "TXO18000C4",
"name": "臺指選擇權 18000C 03",
"referencePrice": 150.0,
"startDate": "2023-12-20",
"endDate": "2024-03-20",
"settlementDate": "2024-03-20"
}"#;
let ticker: FutOptTicker = serde_json::from_str(json).unwrap();
assert_eq!(ticker.symbol, "TXO18000C4");
assert!(ticker.is_option());
assert!(!ticker.is_future());
}
}