use super::order::Symbol;
use crate::api::quote_streaming::DxFeedSymbol;
use chrono::{DateTime, Utc};
use pretty_simple_display::{DebugPretty, DisplaySimple};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::Display;
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
pub struct CompactOptionChainResponse {
pub data: CompactOptionChainData,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
pub struct CompactOptionChainData {
pub items: Vec<CompactOptionChain>,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct CompactOptionChain {
pub underlying_symbol: Symbol,
pub root_symbol: Symbol,
pub option_chain_type: String,
pub settlement_type: Option<String>,
pub shares_per_contract: u64,
pub expiration_type: Option<String>,
pub symbols: Option<Vec<String>>,
pub streamer_symbols: Option<Vec<String>>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum InstrumentType {
Equity,
#[serde(rename = "Equity Option")]
EquityOption,
#[serde(rename = "Equity Offering")]
EquityOffering,
Future,
#[serde(rename = "Future Option")]
FutureOption,
Cryptocurrency,
Bond,
#[serde(rename = "Fixed Income Security")]
FixedIncomeSecurity,
#[serde(rename = "Liquidity Pool")]
LiquidityPool,
Warrant,
}
impl Display for InstrumentType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
InstrumentType::Equity => write!(f, "Equity"),
InstrumentType::EquityOption => write!(f, "Equity Option"),
InstrumentType::EquityOffering => write!(f, "Equity Offering"),
InstrumentType::Future => write!(f, "Future"),
InstrumentType::FutureOption => write!(f, "Future Option"),
InstrumentType::Cryptocurrency => write!(f, "Cryptocurrency"),
InstrumentType::Bond => write!(f, "Bond"),
InstrumentType::FixedIncomeSecurity => write!(f, "Fixed Income Security"),
InstrumentType::LiquidityPool => write!(f, "Liquidity Pool"),
InstrumentType::Warrant => write!(f, "Warrant"),
}
}
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct EquityInstrumentInfo {
pub symbol: Symbol,
pub streamer_symbol: DxFeedSymbol,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct TickSize {
pub value: String,
pub threshold: Option<String>,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct EquityInstrument {
pub id: u64,
pub symbol: Symbol,
pub instrument_type: InstrumentType,
pub cusip: Option<String>,
pub short_description: String,
pub is_index: bool,
pub listed_market: String,
pub description: String,
pub lendability: Option<String>,
pub borrow_rate: Option<String>,
pub market_time_instrument_collection: String,
pub is_closing_only: bool,
pub is_options_closing_only: bool,
pub active: bool,
#[serde(default)]
pub is_fractional_quantity_eligible: bool,
pub is_illiquid: bool,
pub is_etf: bool,
pub bypass_manual_review: bool,
pub is_fraud_risk: bool,
pub streamer_symbol: DxFeedSymbol,
pub tick_sizes: Option<Vec<TickSize>>,
pub option_tick_sizes: Option<Vec<TickSize>>,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Strike {
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub strike_price: Decimal,
pub call: Symbol,
pub call_streamer_symbol: DxFeedSymbol,
pub put: Symbol,
pub put_streamer_symbol: DxFeedSymbol,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Expiration {
pub expiration_type: String,
pub expiration_date: String,
pub days_to_expiration: u64,
pub settlement_type: String,
pub strikes: Vec<Strike>,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct NestedOptionChain {
pub underlying_symbol: Symbol,
pub root_symbol: Symbol,
pub option_chain_type: String,
pub shares_per_contract: u64,
pub expirations: Vec<Expiration>,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct FuturesNestedOptionChain {
pub futures: Vec<FuturesInfo>,
pub option_chains: Vec<FuturesOptionChains>,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct FuturesInfo {
pub symbol: String,
pub root_symbol: String,
pub expiration_date: String,
pub days_to_expiration: i32,
pub active_month: bool,
pub next_active_month: bool,
pub stops_trading_at: String,
pub expires_at: String,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct FuturesOptionChains {
pub underlying_symbol: String,
pub root_symbol: String,
pub exercise_style: String,
pub expirations: Vec<FuturesExpiration>,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct FuturesExpiration {
pub underlying_symbol: String,
pub root_symbol: String,
pub option_root_symbol: String,
pub option_contract_symbol: String,
pub asset: String,
pub expiration_date: String,
pub days_to_expiration: i32,
pub expiration_type: String,
pub settlement_type: String,
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub notional_value: Decimal,
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub display_factor: Decimal,
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub strike_factor: Decimal,
pub stops_trading_at: String,
pub expires_at: String,
pub tick_sizes: Vec<FuturesTickSize>,
pub strikes: Vec<FuturesStrike>,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct FuturesTickSize {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub threshold: Option<String>,
pub value: String,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct FuturesStrike {
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub strike_price: Decimal,
pub call: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub call_streamer_symbol: Option<String>,
pub put: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub put_streamer_symbol: Option<String>,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct EquityOption {
pub symbol: Symbol,
pub instrument_type: InstrumentType,
pub active: bool,
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub strike_price: Decimal,
pub root_symbol: Symbol,
pub underlying_symbol: Symbol,
pub expiration_date: String,
pub exercise_style: String,
pub shares_per_contract: u64,
pub option_type: String,
pub option_chain_type: String,
pub expiration_type: String,
pub settlement_type: String,
pub stops_trading_at: String,
pub market_time_instrument_collection: String,
pub days_to_expiration: i64,
pub expires_at: String,
pub is_closing_only: bool,
pub streamer_symbol: Option<DxFeedSymbol>,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Future {
pub symbol: Symbol,
pub product_code: String,
pub contract_size: String,
pub tick_size: String,
pub notional_multiplier: String,
pub main_fraction: String,
pub sub_fraction: String,
pub display_factor: String,
pub last_trade_date: String,
pub expiration_date: String,
pub closing_only_date: Option<String>,
pub active: bool,
pub active_month: bool,
pub next_active_month: bool,
pub is_closing_only: bool,
pub stops_trading_at: String,
pub expires_at: String,
pub product_group: String,
pub exchange: String,
pub roll_target_symbol: Option<Symbol>,
pub streamer_exchange_code: String,
pub streamer_symbol: DxFeedSymbol,
pub back_month_first_calendar_symbol: bool,
pub is_tradeable: bool,
pub future_product: FutureProduct,
#[serde(default)]
pub tick_sizes: Vec<TickSize>,
#[serde(default)]
pub option_tick_sizes: Vec<TickSize>,
pub spread_tick_sizes: Option<Vec<HashMap<String, String>>>,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct FutureProduct {
pub root_symbol: Symbol,
pub code: String,
pub description: String,
pub clearing_code: String,
pub clearing_exchange_code: String,
pub clearport_code: Option<String>,
pub legacy_code: Option<String>,
pub exchange: String,
pub legacy_exchange_code: Option<String>,
pub product_type: String,
pub listed_months: Vec<String>,
pub active_months: Vec<String>,
pub notional_multiplier: String,
pub tick_size: String,
pub display_factor: String,
pub streamer_exchange_code: String,
pub small_notional: bool,
pub back_month_first_calendar_symbol: bool,
pub first_notice: bool,
pub cash_settled: bool,
pub security_group: Option<String>,
pub market_sector: String,
pub roll: FutureRoll,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct FutureRoll {
pub name: String,
pub active_count: u32,
pub cash_settled: bool,
pub business_days_offset: u32,
pub first_notice: bool,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct FutureOption {
pub symbol: Symbol,
pub underlying_symbol: Symbol,
pub product_code: String,
pub expiration_date: String,
pub root_symbol: Symbol,
pub option_root_symbol: String,
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub strike_price: Decimal,
pub exchange: String,
pub exchange_symbol: String,
pub streamer_symbol: Option<DxFeedSymbol>,
pub option_type: String,
pub exercise_style: String,
pub is_vanilla: bool,
pub is_primary_deliverable: bool,
pub future_price_ratio: String,
pub multiplier: String,
pub underlying_count: String,
pub is_confirmed: bool,
pub notional_value: String,
pub display_factor: String,
pub security_exchange: String,
pub sx_id: String,
pub settlement_type: String,
pub strike_factor: String,
pub maturity_date: String,
pub is_exercisable_weekly: bool,
pub last_trade_time: String,
pub days_to_expiration: i32,
pub is_closing_only: bool,
pub active: bool,
pub stops_trading_at: String,
pub expires_at: String,
pub future_option_product: FutureOptionProduct,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct FutureOptionProduct {
pub root_symbol: String,
pub cash_settled: bool,
pub code: String,
pub legacy_code: Option<String>,
pub clearport_code: Option<String>,
pub clearing_code: String,
pub clearing_exchange_code: String,
pub clearing_price_multiplier: String,
pub display_factor: String,
pub exchange: String,
pub product_type: String,
pub expiration_type: String,
pub settlement_delay_days: u32,
pub is_rollover: bool,
pub market_sector: String,
pub supported: Option<bool>,
pub futures_trading_cutoff_times: Option<Vec<serde_json::Value>>,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Cryptocurrency {
pub id: u64,
pub symbol: Symbol,
pub instrument_type: InstrumentType,
pub short_description: String,
pub description: String,
pub is_closing_only: bool,
pub active: bool,
pub tick_size: String,
pub streamer_symbol: DxFeedSymbol,
pub destination_venue_symbols: Vec<DestinationVenueSymbol>,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DestinationVenueSymbol {
pub id: u64,
pub symbol: Symbol,
pub destination_venue: String,
pub max_quantity_precision: Option<u32>,
pub max_price_precision: Option<u32>,
pub routable: bool,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Warrant {
pub symbol: Symbol,
pub instrument_type: InstrumentType,
pub listed_market: String,
pub description: String,
pub is_closing_only: bool,
pub active: bool,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct QuantityDecimalPrecision {
pub instrument_type: InstrumentType,
pub symbol: Option<Symbol>,
pub value: u32,
pub minimum_increment_precision: u32,
}
#[derive(Clone, Serialize, Deserialize, DebugPretty, DisplaySimple)]
pub struct SymbolEntry {
pub symbol: String,
pub epic: String,
pub name: String,
pub instrument_type: InstrumentType,
pub exchange: String,
pub expiry: DateTime<Utc>,
pub last_update: DateTime<Utc>,
}
impl PartialEq for SymbolEntry {
fn eq(&self, other: &Self) -> bool {
self.symbol == other.symbol && self.epic == other.epic
}
}
impl Eq for SymbolEntry {}
impl std::hash::Hash for SymbolEntry {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.symbol.hash(state);
self.epic.hash(state);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::str::FromStr;
#[test]
fn test_equity_option_deserialization() {
let json = r#"{
"active": true,
"strike-price": "150.00",
"root-symbol": "AAPL",
"underlying-symbol": "AAPL",
"expiration-date": "2024-01-19",
"exercise-style": "American",
"shares-per-contract": 100,
"option-type": "C",
"option-chain-type": "Standard",
"symbol": "AAPL 240119C00150000",
"instrument-type": "Equity Option",
"expiration-type": "Regular",
"settlement-type": "PM",
"stops-trading-at": "2024-01-19T21:00:00.000+00:00",
"market-time-instrument-collection": "Equity Option",
"is-closing-only": false,
"days-to-expiration": 30,
"expires-at": "2024-01-19T21:00:00.000+00:00",
"streamer-symbol": "AAPL_011924C150"
}"#;
let option: EquityOption = serde_json::from_str(json).unwrap();
assert_eq!(option.symbol.0, "AAPL 240119C00150000");
assert_eq!(option.underlying_symbol.0, "AAPL");
assert_eq!(option.strike_price, Decimal::from_str("150.00").unwrap());
assert_eq!(option.option_type, "C");
assert_eq!(option.shares_per_contract, 100);
}
#[test]
fn test_futures_nested_option_chain_deserialization() {
let json = r#"{
"futures": [
{
"symbol": "/ESU5",
"root-symbol": "/ES",
"expiration-date": "2025-09-19",
"days-to-expiration": 18,
"active-month": true,
"next-active-month": false,
"stops-trading-at": "2025-09-19T13:30:00.000+00:00",
"expires-at": "2025-09-19T13:30:00.000+00:00"
}
],
"option-chains": [
{
"underlying-symbol": "/ES",
"root-symbol": "/ES",
"exercise-style": "American",
"expirations": [
{
"underlying-symbol": "/ESZ5",
"root-symbol": "/ES",
"option-root-symbol": "ES",
"option-contract-symbol": "ESZ5",
"asset": "ES",
"expiration-date": "2025-12-19",
"days-to-expiration": 109,
"expiration-type": "Regular",
"settlement-type": "AM",
"notional-value": "0.5",
"display-factor": "0.01",
"strike-factor": "1.0",
"stops-trading-at": "2025-12-19T14:30:00.000+00:00",
"expires-at": "2025-12-19T14:30:00.000+00:00",
"tick-sizes": [
{
"threshold": "10.0",
"value": "0.05"
},
{
"value": "0.25"
}
],
"strikes": [
{
"strike-price": "800.0",
"call": "./ESZ5 ESZ5 251219C800",
"call-streamer-symbol": "./ESZ25C800:XCME",
"put": "./ESZ5 ESZ5 251219P800",
"put-streamer-symbol": "./ESZ25P800:XCME"
},
{
"strike-price": "4300.0",
"call": "./ESZ5 ESZ5 251219C4300",
"put": "./ESZ5 ESZ5 251219P4300"
}
]
}
]
}
]
}"#;
let chain: FuturesNestedOptionChain = serde_json::from_str(json).unwrap();
assert_eq!(chain.futures.len(), 1);
assert_eq!(chain.futures[0].symbol, "/ESU5");
assert_eq!(chain.futures[0].root_symbol, "/ES");
assert_eq!(chain.futures[0].days_to_expiration, 18);
assert!(chain.futures[0].active_month);
assert!(!chain.futures[0].next_active_month);
assert_eq!(chain.option_chains.len(), 1);
assert_eq!(chain.option_chains[0].underlying_symbol, "/ES");
assert_eq!(chain.option_chains[0].exercise_style, "American");
assert_eq!(chain.option_chains[0].expirations.len(), 1);
let expiration = &chain.option_chains[0].expirations[0];
assert_eq!(expiration.underlying_symbol, "/ESZ5");
assert_eq!(expiration.days_to_expiration, 109);
assert_eq!(expiration.tick_sizes.len(), 2);
assert_eq!(expiration.tick_sizes[0].threshold, Some("10.0".to_string()));
assert_eq!(expiration.tick_sizes[0].value, "0.05");
assert_eq!(expiration.tick_sizes[1].threshold, None);
assert_eq!(expiration.tick_sizes[1].value, "0.25");
assert_eq!(expiration.strikes.len(), 2);
assert_eq!(
expiration.strikes[0].strike_price,
Decimal::from_str("800.0").unwrap()
);
assert_eq!(expiration.strikes[0].call, "./ESZ5 ESZ5 251219C800");
assert_eq!(
expiration.strikes[0].call_streamer_symbol,
Some("./ESZ25C800:XCME".to_string())
);
assert_eq!(
expiration.strikes[1].strike_price,
Decimal::from_str("4300.0").unwrap()
);
assert_eq!(expiration.strikes[1].call_streamer_symbol, None);
assert_eq!(expiration.strikes[1].put_streamer_symbol, None);
}
}