use serde::Deserialize;
use super::chains::{ExpirationType, SettlementType};
use crate::client::SchwabClient;
use crate::error::Result;
#[derive(Debug)]
pub struct ExpirationChain<'a> {
client: &'a SchwabClient,
}
impl<'a> ExpirationChain<'a> {
pub(crate) fn new(client: &'a SchwabClient) -> Self {
Self { client }
}
pub async fn get(&self, symbol: impl AsRef<str>) -> Result<ExpirationChainResponse> {
self.client
.market_data_http()
.get("/expirationchain")
.query(&[("symbol", symbol.as_ref())])
.send_json()
.await
}
}
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct ExpirationChainResponse {
#[serde(default)]
pub status: Option<String>,
#[serde(rename = "expirationList", default)]
pub expiration_list: Vec<Expiration>,
}
#[derive(Debug, Clone, Default, Deserialize, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct Expiration {
#[serde(rename = "daysToExpiration", default)]
pub days_to_expiration: Option<i32>,
#[serde(rename = "expirationDate", alias = "expiration", default)]
pub expiration_date: Option<String>,
#[serde(rename = "expirationType", default)]
pub expiration_type: Option<ExpirationType>,
#[serde(default)]
pub standard: Option<bool>,
#[serde(rename = "settlementType", default)]
pub settlement_type: Option<SettlementType>,
#[serde(rename = "optionRoots", default)]
pub option_roots: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn expiration_chain_parses() {
let json = r#"{
"status": "SUCCESS",
"expirationList": [
{
"expirationDate": "2022-01-07",
"daysToExpiration": 2,
"expirationType": "W",
"standard": true,
"settlementType": "P"
},
{
"expirationDate": "2022-01-21",
"daysToExpiration": 16,
"expirationType": "S",
"standard": true
}
]
}"#;
let resp: ExpirationChainResponse = serde_json::from_str(json).unwrap();
assert_eq!(resp.expiration_list.len(), 2);
let first = &resp.expiration_list[0];
assert_eq!(first.expiration_date.as_deref(), Some("2022-01-07"));
assert_eq!(first.days_to_expiration, Some(2));
assert_eq!(first.expiration_type, Some(ExpirationType::Weekly));
assert_eq!(first.standard, Some(true));
assert_eq!(first.settlement_type, Some(SettlementType::Pm));
let second = &resp.expiration_list[1];
assert_eq!(second.expiration_type, Some(ExpirationType::Standard));
assert_eq!(second.settlement_type, None);
}
#[test]
fn expiration_field_alias_is_accepted() {
let json = r#"{ "expirationList": [ { "expiration": "2022-01-07" } ] }"#;
let resp: ExpirationChainResponse = serde_json::from_str(json).unwrap();
assert_eq!(
resp.expiration_list[0].expiration_date.as_deref(),
Some("2022-01-07")
);
}
#[test]
fn empty_expiration_chain_parses() {
let resp: ExpirationChainResponse = serde_json::from_str("{}").unwrap();
assert!(resp.expiration_list.is_empty());
assert!(resp.status.is_none());
}
}