use super::super::Number;
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum FuturesOptionField {
Symbol = 0,
BidPrice = 1,
AskPrice = 2,
LastPrice = 3,
BidSize = 4,
AskSize = 5,
BidId = 6,
AskId = 7,
TotalVolume = 8,
LastSize = 9,
QuoteTime = 10,
TradeTime = 11,
HighPrice = 12,
LowPrice = 13,
ClosePrice = 14,
ExchangeId = 15,
Description = 16,
LastId = 17,
OpenPrice = 18,
NetChange = 19,
FuturePercentChange = 20,
ExchangeName = 21,
SecurityStatus = 22,
OpenInterest = 23,
Mark = 24,
Tick = 25,
TickAmount = 26,
Product = 27,
ExpirationDate = 28,
ExpirationStyle = 29,
StrikePrice = 30,
ContractType = 31,
}
impl FuturesOptionField {
pub fn index(&self) -> u32 {
*self as u32
}
pub fn all() -> &'static [FuturesOptionField] {
&[
Self::Symbol,
Self::BidPrice,
Self::AskPrice,
Self::LastPrice,
Self::BidSize,
Self::AskSize,
Self::BidId,
Self::AskId,
Self::TotalVolume,
Self::LastSize,
Self::QuoteTime,
Self::TradeTime,
Self::HighPrice,
Self::LowPrice,
Self::ClosePrice,
Self::ExchangeId,
Self::Description,
Self::LastId,
Self::OpenPrice,
Self::NetChange,
Self::FuturePercentChange,
Self::ExchangeName,
Self::SecurityStatus,
Self::OpenInterest,
Self::Mark,
Self::Tick,
Self::TickAmount,
Self::Product,
Self::ExpirationDate,
Self::ExpirationStyle,
Self::StrikePrice,
Self::ContractType,
]
}
}
fn parse_num(v: &serde_json::Value) -> Option<Number> {
serde_json::from_value::<Number>(v.clone()).ok()
}
#[derive(Clone, Debug, Default, PartialEq)]
#[allow(missing_docs)]
pub struct LevelOneFuturesOption {
pub key: Option<String>,
pub delayed: Option<bool>,
pub asset_main_type: Option<String>,
pub asset_sub_type: Option<String>,
pub cusip: Option<String>,
pub symbol: Option<String>,
pub bid_price: Option<Number>,
pub ask_price: Option<Number>,
pub last_price: Option<Number>,
pub bid_size: Option<i64>,
pub ask_size: Option<i64>,
pub bid_id: Option<String>,
pub ask_id: Option<String>,
pub total_volume: Option<i64>,
pub last_size: Option<i64>,
pub quote_time: Option<i64>,
pub trade_time: Option<i64>,
pub high_price: Option<Number>,
pub low_price: Option<Number>,
pub close_price: Option<Number>,
pub exchange_id: Option<String>,
pub description: Option<String>,
pub last_id: Option<String>,
pub open_price: Option<Number>,
pub net_change: Option<Number>,
pub future_percent_change: Option<Number>,
pub exchange_name: Option<String>,
pub security_status: Option<String>,
pub open_interest: Option<i64>,
pub mark: Option<Number>,
pub tick: Option<Number>,
pub tick_amount: Option<Number>,
pub product: Option<String>,
pub expiration_date: Option<Number>,
pub expiration_style: Option<String>,
pub strike_price: Option<Number>,
pub contract_type: Option<String>,
}
impl LevelOneFuturesOption {
pub(crate) fn from_value(value: &serde_json::Value) -> Option<Self> {
let obj = value.as_object()?;
Some(Self {
key: obj.get("key").and_then(|v| v.as_str()).map(String::from),
delayed: obj.get("delayed").and_then(|v| v.as_bool()),
asset_main_type: obj
.get("assetMainType")
.and_then(|v| v.as_str())
.map(String::from),
asset_sub_type: obj
.get("assetSubType")
.and_then(|v| v.as_str())
.map(String::from),
cusip: obj.get("cusip").and_then(|v| v.as_str()).map(String::from),
symbol: obj.get("0").and_then(|v| v.as_str()).map(String::from),
bid_price: obj.get("1").and_then(parse_num),
ask_price: obj.get("2").and_then(parse_num),
last_price: obj.get("3").and_then(parse_num),
bid_size: obj.get("4").and_then(|v| v.as_i64()),
ask_size: obj.get("5").and_then(|v| v.as_i64()),
bid_id: obj.get("6").and_then(|v| v.as_str()).map(String::from),
ask_id: obj.get("7").and_then(|v| v.as_str()).map(String::from),
total_volume: obj.get("8").and_then(|v| v.as_i64()),
last_size: obj.get("9").and_then(|v| v.as_i64()),
quote_time: obj.get("10").and_then(|v| v.as_i64()),
trade_time: obj.get("11").and_then(|v| v.as_i64()),
high_price: obj.get("12").and_then(parse_num),
low_price: obj.get("13").and_then(parse_num),
close_price: obj.get("14").and_then(parse_num),
exchange_id: obj.get("15").and_then(|v| v.as_str()).map(String::from),
description: obj.get("16").and_then(|v| v.as_str()).map(String::from),
last_id: obj.get("17").and_then(|v| v.as_str()).map(String::from),
open_price: obj.get("18").and_then(parse_num),
net_change: obj.get("19").and_then(parse_num),
future_percent_change: obj.get("20").and_then(parse_num),
exchange_name: obj.get("21").and_then(|v| v.as_str()).map(String::from),
security_status: obj.get("22").and_then(|v| v.as_str()).map(String::from),
open_interest: obj.get("23").and_then(|v| v.as_i64()),
mark: obj.get("24").and_then(parse_num),
tick: obj.get("25").and_then(parse_num),
tick_amount: obj.get("26").and_then(parse_num),
product: obj.get("27").and_then(|v| v.as_str()).map(String::from),
expiration_date: obj.get("28").and_then(parse_num),
expiration_style: obj.get("29").and_then(|v| v.as_str()).map(String::from),
strike_price: obj.get("30").and_then(parse_num),
contract_type: obj.get("31").and_then(|v| v.as_str()).map(String::from),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn field_all_has_32_variants() {
assert_eq!(FuturesOptionField::all().len(), 32);
}
#[test]
fn field_indices_are_sequential() {
for (i, field) in FuturesOptionField::all().iter().enumerate() {
assert_eq!(field.index(), i as u32);
}
}
#[test]
fn from_value_parses_futures_option() {
let value = json!({
"key": "/ESM25C5500",
"1": 25.50,
"30": 5500.0,
"31": "C"
});
let parsed = LevelOneFuturesOption::from_value(&value).unwrap();
assert_eq!(parsed.key, Some("/ESM25C5500".to_string()));
assert_eq!(parsed.bid_price, Some("25.5".parse().unwrap()));
assert_eq!(parsed.strike_price, Some("5500.0".parse().unwrap()));
assert_eq!(parsed.contract_type, Some("C".to_string()));
assert_eq!(parsed.symbol, None);
assert_eq!(parsed.last_price, None);
}
#[test]
fn from_value_returns_none_for_non_object() {
assert!(LevelOneFuturesOption::from_value(&json!("not an object")).is_none());
assert!(LevelOneFuturesOption::from_value(&json!(42)).is_none());
assert!(LevelOneFuturesOption::from_value(&json!(null)).is_none());
}
}