use serde::{Deserialize, Deserializer, Serialize};
use crate::models::common::Epic;
pub use crate::models::common::{InstrumentType, MarketStatus};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum MarketDetailFilter {
All,
SnapshotOnly,
}
impl MarketDetailFilter {
pub fn as_query_str(self) -> &'static str {
match self {
Self::All => "ALL",
Self::SnapshotOnly => "SNAPSHOT_ONLY",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MarketSummary {
pub epic: Epic,
pub instrument_name: String,
pub instrument_type: InstrumentType,
pub expiry: String,
pub bid: Option<f64>,
pub offer: Option<f64>,
pub market_status: MarketStatus,
pub streaming_prices_available: bool,
pub high: Option<f64>,
pub low: Option<f64>,
pub net_change: Option<f64>,
pub percentage_change: Option<f64>,
pub update_time: Option<String>,
pub update_time_utc: Option<String>,
pub delay_time: Option<i32>,
pub scaling_factor: Option<i32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DealingRuleValue {
pub unit: String,
pub value: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InstrumentCurrency {
pub code: String,
pub name: String,
pub symbol: String,
pub is_default: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Instrument {
pub epic: Epic,
pub name: String,
pub status: Option<MarketStatus>,
#[serde(rename = "type")]
pub instrument_type: InstrumentType,
pub expiry: String,
pub lot_size: Option<f64>,
pub unit: Option<String>,
pub high: Option<f64>,
pub low: Option<f64>,
pub percentage_change: Option<f64>,
pub net_change: Option<f64>,
pub bid: Option<f64>,
pub offer: Option<f64>,
pub update_time: Option<String>,
pub update_time_utc: Option<String>,
pub delay_time: Option<i32>,
pub streaming_prices_available: bool,
pub market_status: Option<MarketStatus>,
pub scaling_factor: Option<i32>,
#[serde(default)]
pub currencies: Vec<InstrumentCurrency>,
pub margin_factor: Option<f64>,
pub margin_factor_unit: Option<String>,
pub slippage_factor: Option<DealingRuleValue>,
pub limited_risk_premium: Option<DealingRuleValue>,
#[serde(default)]
pub special_info: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DealingRules {
pub min_step_distance: DealingRuleValue,
pub min_deal_size: DealingRuleValue,
pub min_controlled_risk_stop_distance: DealingRuleValue,
pub min_normal_stop_or_limit_distance: DealingRuleValue,
pub max_stop_or_limit_distance: DealingRuleValue,
pub controlled_risk_spacing: DealingRuleValue,
pub market_order_preference: String,
pub trailing_stops_preference: String,
}
pub use crate::models::common::MarketSnapshot;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MarketDetails {
pub instrument: Instrument,
pub dealing_rules: DealingRules,
pub snapshot: MarketSnapshot,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NavigationChild {
pub id: String,
pub name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NavigationNode {
#[serde(default, deserialize_with = "null_as_empty_vec")]
pub markets: Vec<MarketSummary>,
#[serde(default, deserialize_with = "null_as_empty_vec")]
pub nodes: Vec<NavigationChild>,
}
fn null_as_empty_vec<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error>
where
T: Deserialize<'de>,
D: Deserializer<'de>,
{
Ok(Option::<Vec<T>>::deserialize(deserializer)?.unwrap_or_default())
}
#[cfg(feature = "polars")]
impl crate::dataframe::IntoDataFrame for Vec<MarketSummary> {
fn to_dataframe(&self) -> crate::Result<polars::prelude::DataFrame> {
use polars::prelude::*;
let epic: Vec<&str> = self.iter().map(|m| m.epic.as_str()).collect();
let instrument_name: Vec<&str> = self.iter().map(|m| m.instrument_name.as_str()).collect();
let instrument_type: Vec<&str> = self
.iter()
.map(|m| instrument_type_str(m.instrument_type))
.collect();
let expiry: Vec<&str> = self.iter().map(|m| m.expiry.as_str()).collect();
let bid: Vec<Option<f64>> = self.iter().map(|m| m.bid).collect();
let offer: Vec<Option<f64>> = self.iter().map(|m| m.offer).collect();
let market_status: Vec<&str> = self
.iter()
.map(|m| market_status_str(m.market_status))
.collect();
let streaming_prices_available: Vec<bool> =
self.iter().map(|m| m.streaming_prices_available).collect();
let high: Vec<Option<f64>> = self.iter().map(|m| m.high).collect();
let low: Vec<Option<f64>> = self.iter().map(|m| m.low).collect();
let net_change: Vec<Option<f64>> = self.iter().map(|m| m.net_change).collect();
let percentage_change: Vec<Option<f64>> =
self.iter().map(|m| m.percentage_change).collect();
let update_time: Vec<Option<&str>> =
self.iter().map(|m| m.update_time.as_deref()).collect();
let update_time_utc: Vec<Option<&str>> =
self.iter().map(|m| m.update_time_utc.as_deref()).collect();
DataFrame::new(vec![
Column::new("epic".into(), epic),
Column::new("instrument_name".into(), instrument_name),
Column::new("instrument_type".into(), instrument_type),
Column::new("expiry".into(), expiry),
Column::new("bid".into(), bid),
Column::new("offer".into(), offer),
Column::new("market_status".into(), market_status),
Column::new(
"streaming_prices_available".into(),
streaming_prices_available,
),
Column::new("high".into(), high),
Column::new("low".into(), low),
Column::new("net_change".into(), net_change),
Column::new("percentage_change".into(), percentage_change),
Column::new("update_time".into(), update_time),
Column::new("update_time_utc".into(), update_time_utc),
])
.map_err(|e| crate::Error::Config(format!("polars conversion failed: {e}")))
}
}
#[cfg(feature = "polars")]
fn instrument_type_str(t: InstrumentType) -> &'static str {
use crate::models::common::InstrumentType;
match t {
InstrumentType::Currencies => "CURRENCIES",
InstrumentType::Shares => "SHARES",
InstrumentType::Indices => "INDICES",
InstrumentType::Commodities => "COMMODITIES",
InstrumentType::Options => "OPTIONS",
InstrumentType::Bonds => "BONDS",
InstrumentType::Rates => "RATES",
InstrumentType::Sectors => "SECTORS",
InstrumentType::Funds => "FUNDS",
InstrumentType::SprintMarkets => "SPRINT_MARKETS",
InstrumentType::Unknown => "UNKNOWN",
}
}
#[cfg(feature = "polars")]
fn market_status_str(s: MarketStatus) -> &'static str {
use crate::models::common::MarketStatus;
match s {
MarketStatus::Tradeable => "TRADEABLE",
MarketStatus::EditsOnly => "EDITS_ONLY",
MarketStatus::Closed => "CLOSED",
MarketStatus::Offline => "OFFLINE",
MarketStatus::OnAuction => "ON_AUCTION",
MarketStatus::OnAuctionNoEdits => "ON_AUCTION_NO_EDITS",
MarketStatus::Suspended => "SUSPENDED",
MarketStatus::Unknown => "UNKNOWN",
}
}