use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Resolution {
Second,
Minute,
Minute2,
Minute3,
Minute5,
Minute10,
Minute15,
Minute30,
Hour,
Hour2,
Hour3,
Hour4,
Day,
Week,
Month,
}
impl std::fmt::Display for Resolution {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = serde_json::to_value(self)
.ok()
.and_then(|v| v.as_str().map(ToOwned::to_owned))
.unwrap_or_else(|| format!("{self:?}").to_ascii_uppercase());
f.write_str(&s)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct HistoricalPricesRequest {
pub resolution: Option<Resolution>,
pub from: Option<NaiveDateTime>,
pub to: Option<NaiveDateTime>,
pub max: Option<u32>,
pub page_size: Option<u32>,
pub page_number: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PriceCandle {
pub bid: Option<f64>,
pub ask: Option<f64>,
pub last_traded: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PricePoint {
pub snapshot_time: String,
#[serde(rename = "snapshotTimeUTC")]
pub snapshot_time_utc: NaiveDateTime,
pub open_price: PriceCandle,
pub close_price: PriceCandle,
pub high_price: PriceCandle,
pub low_price: PriceCandle,
pub last_traded_volume: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PageData {
pub page_number: u32,
pub page_size: u32,
pub total_pages: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PriceAllowance {
pub remaining_allowance: u32,
pub total_allowance: u32,
pub allowance_expiry: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PricesMetadata {
pub page_data: PageData,
pub allowance: PriceAllowance,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HistoricalPrices {
pub instrument_type: String,
pub prices: Vec<PricePoint>,
pub metadata: PricesMetadata,
}
#[cfg(feature = "polars")]
impl crate::dataframe::IntoDataFrame for HistoricalPrices {
fn to_dataframe(&self) -> crate::Result<polars::prelude::DataFrame> {
use polars::prelude::*;
let snapshot_time: Vec<&str> = self
.prices
.iter()
.map(|p| p.snapshot_time.as_str())
.collect();
let snapshot_time_utc: Vec<NaiveDateTime> =
self.prices.iter().map(|p| p.snapshot_time_utc).collect();
let open_bid: Vec<Option<f64>> = self.prices.iter().map(|p| p.open_price.bid).collect();
let open_ask: Vec<Option<f64>> = self.prices.iter().map(|p| p.open_price.ask).collect();
let high_bid: Vec<Option<f64>> = self.prices.iter().map(|p| p.high_price.bid).collect();
let high_ask: Vec<Option<f64>> = self.prices.iter().map(|p| p.high_price.ask).collect();
let low_bid: Vec<Option<f64>> = self.prices.iter().map(|p| p.low_price.bid).collect();
let low_ask: Vec<Option<f64>> = self.prices.iter().map(|p| p.low_price.ask).collect();
let close_bid: Vec<Option<f64>> = self.prices.iter().map(|p| p.close_price.bid).collect();
let close_ask: Vec<Option<f64>> = self.prices.iter().map(|p| p.close_price.ask).collect();
let last_traded_volume: Vec<Option<u64>> =
self.prices.iter().map(|p| p.last_traded_volume).collect();
let snapshot_time_utc_series = Series::new("snapshot_time_utc".into(), snapshot_time_utc);
DataFrame::new(vec![
Column::new("snapshot_time".into(), snapshot_time),
snapshot_time_utc_series.into(),
Column::new("open_bid".into(), open_bid),
Column::new("open_ask".into(), open_ask),
Column::new("high_bid".into(), high_bid),
Column::new("high_ask".into(), high_ask),
Column::new("low_bid".into(), low_bid),
Column::new("low_ask".into(), low_ask),
Column::new("close_bid".into(), close_bid),
Column::new("close_ask".into(), close_ask),
Column::new("last_traded_volume".into(), last_traded_volume),
])
.map_err(|e| crate::Error::Config(format!("polars conversion failed: {e}")))
}
}