use super::order::Symbol;
use crate::api::quote_streaming::DxFeedSymbol;
use crate::types::wire::{ExerciseStyle, ExpirationType, ProductType, SettlementType};
use chrono::{DateTime, FixedOffset, NaiveDate, Utc};
use pretty_simple_display::{DebugPretty, DisplaySimple};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
#[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<SettlementType>,
pub shares_per_contract: u64,
pub expiration_type: Option<ExpirationType>,
pub symbols: Option<Vec<String>>,
pub streamer_symbols: Option<Vec<String>>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
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 {
#[serde(with = "crate::types::wire::decimal")]
pub value: Decimal,
#[serde(default, with = "crate::types::wire::decimal_option")]
pub threshold: Option<Decimal>,
}
#[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>,
#[serde(default, with = "crate::types::wire::decimal_option")]
pub borrow_rate: Option<Decimal>,
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: ExpirationType,
#[serde(with = "crate::types::wire::date")]
pub expiration_date: NaiveDate,
pub days_to_expiration: u64,
pub settlement_type: SettlementType,
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,
#[serde(with = "crate::types::wire::date")]
pub expiration_date: NaiveDate,
pub days_to_expiration: i32,
pub active_month: bool,
pub next_active_month: bool,
#[serde(with = "crate::types::wire::datetime")]
pub stops_trading_at: DateTime<FixedOffset>,
#[serde(with = "crate::types::wire::datetime")]
pub expires_at: DateTime<FixedOffset>,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct FuturesOptionChains {
pub underlying_symbol: String,
pub root_symbol: String,
pub exercise_style: ExerciseStyle,
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,
#[serde(with = "crate::types::wire::date")]
pub expiration_date: NaiveDate,
pub days_to_expiration: i32,
pub expiration_type: ExpirationType,
pub settlement_type: SettlementType,
#[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,
#[serde(with = "crate::types::wire::datetime")]
pub stops_trading_at: DateTime<FixedOffset>,
#[serde(with = "crate::types::wire::datetime")]
pub expires_at: DateTime<FixedOffset>,
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",
with = "crate::types::wire::decimal_option"
)]
pub threshold: Option<Decimal>,
#[serde(with = "crate::types::wire::decimal")]
pub value: Decimal,
}
#[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,
#[serde(with = "crate::types::wire::date")]
pub expiration_date: NaiveDate,
pub exercise_style: ExerciseStyle,
pub shares_per_contract: u64,
pub option_type: String,
pub option_chain_type: String,
pub expiration_type: ExpirationType,
pub settlement_type: SettlementType,
#[serde(with = "crate::types::wire::datetime")]
pub stops_trading_at: DateTime<FixedOffset>,
pub market_time_instrument_collection: String,
pub days_to_expiration: i64,
#[serde(with = "crate::types::wire::datetime")]
pub expires_at: DateTime<FixedOffset>,
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,
#[serde(with = "crate::types::wire::decimal")]
pub contract_size: Decimal,
#[serde(with = "crate::types::wire::decimal")]
pub tick_size: Decimal,
#[serde(with = "crate::types::wire::decimal")]
pub notional_multiplier: Decimal,
#[serde(with = "crate::types::wire::decimal")]
pub main_fraction: Decimal,
#[serde(with = "crate::types::wire::decimal")]
pub sub_fraction: Decimal,
#[serde(with = "crate::types::wire::decimal")]
pub display_factor: Decimal,
#[serde(with = "crate::types::wire::date")]
pub last_trade_date: NaiveDate,
#[serde(with = "crate::types::wire::date")]
pub expiration_date: NaiveDate,
#[serde(default, with = "crate::types::wire::date_option")]
pub closing_only_date: Option<NaiveDate>,
pub active: bool,
pub active_month: bool,
pub next_active_month: bool,
pub is_closing_only: bool,
#[serde(with = "crate::types::wire::datetime")]
pub stops_trading_at: DateTime<FixedOffset>,
#[serde(with = "crate::types::wire::datetime")]
pub expires_at: DateTime<FixedOffset>,
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>,
#[serde(default)]
pub spread_tick_sizes: Option<Vec<TickSize>>,
}
#[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: ProductType,
pub listed_months: Vec<String>,
pub active_months: Vec<String>,
#[serde(with = "crate::types::wire::decimal")]
pub notional_multiplier: Decimal,
#[serde(with = "crate::types::wire::decimal")]
pub tick_size: Decimal,
#[serde(with = "crate::types::wire::decimal")]
pub display_factor: Decimal,
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,
#[serde(with = "crate::types::wire::date")]
pub expiration_date: NaiveDate,
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: ExerciseStyle,
pub is_vanilla: bool,
pub is_primary_deliverable: bool,
#[serde(with = "crate::types::wire::decimal")]
pub future_price_ratio: Decimal,
#[serde(with = "crate::types::wire::decimal")]
pub multiplier: Decimal,
#[serde(with = "crate::types::wire::decimal")]
pub underlying_count: Decimal,
pub is_confirmed: bool,
#[serde(with = "crate::types::wire::decimal")]
pub notional_value: Decimal,
#[serde(with = "crate::types::wire::decimal")]
pub display_factor: Decimal,
pub security_exchange: String,
pub sx_id: String,
pub settlement_type: SettlementType,
#[serde(with = "crate::types::wire::decimal")]
pub strike_factor: Decimal,
#[serde(with = "crate::types::wire::date")]
pub maturity_date: NaiveDate,
pub is_exercisable_weekly: bool,
pub last_trade_time: String,
pub days_to_expiration: i32,
pub is_closing_only: bool,
pub active: bool,
#[serde(with = "crate::types::wire::datetime")]
pub stops_trading_at: DateTime<FixedOffset>,
#[serde(with = "crate::types::wire::datetime")]
pub expires_at: DateTime<FixedOffset>,
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,
#[serde(with = "crate::types::wire::decimal")]
pub clearing_price_multiplier: Decimal,
#[serde(with = "crate::types::wire::decimal")]
pub display_factor: Decimal,
pub exchange: String,
pub product_type: ProductType,
pub expiration_type: ExpirationType,
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,
#[serde(with = "crate::types::wire::decimal")]
pub tick_size: Decimal,
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,
ExerciseStyle::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(Decimal::from_str("10.0").unwrap())
);
assert_eq!(
expiration.tick_sizes[0].value,
Decimal::from_str("0.05").unwrap()
);
assert_eq!(expiration.tick_sizes[1].threshold, None);
assert_eq!(
expiration.tick_sizes[1].value,
Decimal::from_str("0.25").unwrap()
);
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);
}
}
#[cfg(test)]
mod numeric_field_tests {
use super::*;
use std::str::FromStr;
#[test]
fn a_quoted_tick_size_becomes_a_decimal() {
let tick: TickSize = serde_json::from_str(r#"{"value":"0.05","threshold":"10.0"}"#)
.expect("tick sizes parse");
assert_eq!(tick.value, Decimal::from_str("0.05").unwrap());
assert_eq!(tick.threshold, Some(Decimal::from_str("10.0").unwrap()));
let coarse: TickSize = serde_json::from_str(r#"{"value":"0.25"}"#).expect("parses");
assert!(coarse.value > tick.value, "0.25 must exceed 0.05");
assert_eq!(coarse.threshold, None);
}
#[test]
fn an_unquoted_tick_size_parses_the_same_way() {
let quoted: TickSize = serde_json::from_str(r#"{"value":"0.05"}"#).expect("parses");
let bare: TickSize = serde_json::from_str(r#"{"value":0.05}"#).expect("parses");
assert_eq!(quoted.value, bare.value);
}
}
#[cfg(test)]
mod converted_payload_tests {
use super::*;
use std::str::FromStr;
fn decimal(text: &str) -> Decimal {
Decimal::from_str(text).expect("the fixture is a valid decimal")
}
#[test]
fn an_equity_payload_with_a_quoted_borrow_rate_parses() {
const EQUITY: &str = r#"{
"id": 1,
"symbol": "AAPL",
"instrument-type": "Equity",
"short-description": "APPLE INC",
"is-index": false,
"listed-market": "XNAS",
"description": "Apple Inc. Common Stock",
"lendability": "Easy To Borrow",
"borrow-rate": "0.25",
"market-time-instrument-collection": "Equity",
"is-closing-only": false,
"is-options-closing-only": false,
"active": true,
"is-fractional-quantity-eligible": true,
"is-illiquid": false,
"is-etf": false,
"bypass-manual-review": false,
"is-fraud-risk": false,
"streamer-symbol": "AAPL"
}"#;
let equity: EquityInstrument = serde_json::from_str(EQUITY).expect("equities parse");
assert_eq!(equity.borrow_rate, Some(decimal("0.25")));
}
#[test]
fn an_equity_payload_without_a_borrow_rate_still_parses() {
const EQUITY: &str = r#"{
"id": 1,
"symbol": "AAPL",
"instrument-type": "Equity",
"short-description": "APPLE INC",
"is-index": false,
"listed-market": "XNAS",
"description": "Apple Inc. Common Stock",
"market-time-instrument-collection": "Equity",
"is-closing-only": false,
"is-options-closing-only": false,
"active": true,
"is-fractional-quantity-eligible": true,
"is-illiquid": false,
"is-etf": false,
"bypass-manual-review": false,
"is-fraud-risk": false,
"streamer-symbol": "AAPL"
}"#;
let equity: EquityInstrument = serde_json::from_str(EQUITY).expect("equities parse");
assert_eq!(equity.borrow_rate, None);
assert_eq!(equity.lendability, None);
}
#[test]
fn a_spread_tick_size_is_typed_rather_than_a_string_map() {
const FUTURE_TICKS: &str = r#"{
"tick-sizes": [{"value": "0.25"}],
"option-tick-sizes": [{"value": "0.05", "threshold": "10.0"}],
"spread-tick-sizes": [{"value": "0.05", "threshold": "5.0"}]
}"#;
#[derive(serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
struct Ticks {
tick_sizes: Vec<TickSize>,
option_tick_sizes: Vec<TickSize>,
spread_tick_sizes: Option<Vec<TickSize>>,
}
let ticks: Ticks = serde_json::from_str(FUTURE_TICKS).expect("tick sizes parse");
assert_eq!(ticks.tick_sizes[0].value, decimal("0.25"));
assert_eq!(ticks.option_tick_sizes[0].threshold, Some(decimal("10.0")));
let spread = ticks.spread_tick_sizes.expect("spread ticks are present");
assert_eq!(spread[0].value, decimal("0.05"));
assert_eq!(
spread[0].threshold,
Some(decimal("5.0")),
"the spread entries carry the same numeric contract as the others"
);
}
#[test]
fn a_future_option_product_payload_parses_its_numeric_fields() {
const PRODUCT: &str = r#"{
"root-symbol": "/ES",
"cash-settled": false,
"code": "ES",
"clearing-code": "ES",
"clearing-exchange-code": "16",
"clearing-price-multiplier": "1.0",
"display-factor": "0.01",
"exchange": "CME",
"product-type": "Financial",
"expiration-type": "Regular",
"settlement-delay-days": 0,
"is-rollover": true,
"market-sector": "Equity Index"
}"#;
let product: FutureOptionProduct =
serde_json::from_str(PRODUCT).expect("future option products parse");
assert_eq!(product.clearing_price_multiplier, decimal("1.0"));
assert_eq!(product.display_factor, decimal("0.01"));
assert_eq!(product.clearing_exchange_code, "16");
}
}