use std::collections::HashMap;
use polars::prelude::*;
use crate::data::enums::Exchange;
use crate::data::models::{Bar, DailyAuctions, ForexRate, Quote, Trade};
pub trait ToFrame {
fn df(&self) -> PolarsResult<DataFrame>;
}
trait FrameRows: Sized {
const KEY: &'static str;
fn key(&self) -> &str;
fn rows(&self) -> usize {
1
}
fn columns(records: &[&Self]) -> Vec<Column>;
}
fn name(column: &'static str) -> PlSmallStr {
PlSmallStr::from_static(column)
}
fn floats(column: &'static str, values: impl Iterator<Item = f64>) -> Column {
Column::new(name(column), values.collect::<Vec<_>>())
}
fn strings<'a>(column: &'static str, values: impl Iterator<Item = Option<&'a str>>) -> Column {
Column::new(name(column), values.collect::<Vec<_>>())
}
fn string_lists<'a>(
column: &'static str,
values: impl Iterator<Item = Option<&'a Vec<String>>> + Clone,
) -> Column {
let rows = values.clone().count();
let mut builder = ListStringChunkedBuilder::new(name(column), rows, rows * 2);
for value in values {
match value {
Some(codes) => builder.append_values_iter(codes.iter().map(String::as_str)),
None => builder.append_null(),
}
}
builder.finish().into_column()
}
fn timestamps(
column: &'static str,
values: impl Iterator<Item = chrono::DateTime<chrono::Utc>>,
) -> Column {
let nanos: Vec<Option<i64>> = values.map(|at| at.timestamp_nanos_opt()).collect();
Int64Chunked::from_iter_options(name(column), nanos.into_iter())
.into_datetime(TimeUnit::Nanoseconds, Some(TimeZone::UTC))
.into_column()
}
fn frame<T: FrameRows>(records: &[&T], keys: Vec<&str>) -> PolarsResult<DataFrame> {
let height = keys.len();
let mut columns = vec![Column::new(name(T::KEY), keys)];
columns.extend(T::columns(records));
DataFrame::new(height, columns)
}
impl<T: FrameRows> ToFrame for [T] {
fn df(&self) -> PolarsResult<DataFrame> {
let records: Vec<&T> = self.iter().collect();
let keys: Vec<&str> = self
.iter()
.flat_map(|record| std::iter::repeat_n(record.key(), record.rows()))
.collect();
frame(&records, keys)
}
}
impl<T: FrameRows> ToFrame for HashMap<String, Vec<T>> {
fn df(&self) -> PolarsResult<DataFrame> {
let mut symbols: Vec<&String> = self.keys().collect();
symbols.sort();
let mut records = Vec::new();
let mut keys = Vec::new();
for symbol in symbols {
for record in &self[symbol] {
keys.extend(std::iter::repeat_n(symbol.as_str(), record.rows()));
records.push(record);
}
}
frame(&records, keys)
}
}
impl FrameRows for Bar {
const KEY: &'static str = "symbol";
fn key(&self) -> &str {
&self.symbol
}
fn columns(records: &[&Self]) -> Vec<Column> {
vec![
timestamps("timestamp", records.iter().map(|bar| bar.timestamp)),
floats("open", records.iter().map(|bar| bar.open)),
floats("high", records.iter().map(|bar| bar.high)),
floats("low", records.iter().map(|bar| bar.low)),
floats("close", records.iter().map(|bar| bar.close)),
floats("volume", records.iter().map(|bar| bar.volume)),
Column::new(
name("trade_count"),
records
.iter()
.map(|bar| bar.trade_count)
.collect::<Vec<_>>(),
),
Column::new(
name("vwap"),
records.iter().map(|bar| bar.vwap).collect::<Vec<_>>(),
),
]
}
}
impl FrameRows for Quote {
const KEY: &'static str = "symbol";
fn key(&self) -> &str {
&self.symbol
}
fn columns(records: &[&Self]) -> Vec<Column> {
vec![
timestamps("timestamp", records.iter().map(|quote| quote.timestamp)),
floats("bid_price", records.iter().map(|quote| quote.bid_price)),
floats("bid_size", records.iter().map(|quote| quote.bid_size)),
strings(
"bid_exchange",
records
.iter()
.map(|quote| quote.bid_exchange.as_ref().map(Exchange::as_str)),
),
floats("ask_price", records.iter().map(|quote| quote.ask_price)),
floats("ask_size", records.iter().map(|quote| quote.ask_size)),
strings(
"ask_exchange",
records
.iter()
.map(|quote| quote.ask_exchange.as_ref().map(Exchange::as_str)),
),
string_lists(
"conditions",
records.iter().map(|quote| quote.conditions.as_ref()),
),
strings("tape", records.iter().map(|quote| quote.tape.as_deref())),
]
}
}
impl FrameRows for Trade {
const KEY: &'static str = "symbol";
fn key(&self) -> &str {
&self.symbol
}
fn columns(records: &[&Self]) -> Vec<Column> {
vec![
timestamps("timestamp", records.iter().map(|trade| trade.timestamp)),
strings(
"exchange",
records
.iter()
.map(|trade| trade.exchange.as_ref().map(Exchange::as_str)),
),
floats("price", records.iter().map(|trade| trade.price)),
floats("size", records.iter().map(|trade| trade.size)),
Column::new(
name("id"),
records.iter().map(|trade| trade.id).collect::<Vec<_>>(),
),
string_lists(
"conditions",
records.iter().map(|trade| trade.conditions.as_ref()),
),
strings("tape", records.iter().map(|trade| trade.tape.as_deref())),
strings(
"taker_side",
records.iter().map(|trade| trade.taker_side.as_deref()),
),
]
}
}
impl FrameRows for ForexRate {
const KEY: &'static str = "currency_pair";
fn key(&self) -> &str {
&self.currency_pair
}
fn columns(records: &[&Self]) -> Vec<Column> {
vec![
timestamps("timestamp", records.iter().map(|rate| rate.timestamp)),
floats("bid_price", records.iter().map(|rate| rate.bid_price)),
floats("mid_price", records.iter().map(|rate| rate.mid_price)),
floats("ask_price", records.iter().map(|rate| rate.ask_price)),
]
}
}
impl FrameRows for DailyAuctions {
const KEY: &'static str = "symbol";
fn key(&self) -> &str {
&self.symbol
}
fn rows(&self) -> usize {
self.opening.len() + self.closing.len()
}
fn columns(records: &[&Self]) -> Vec<Column> {
let prints = || {
records.iter().flat_map(|day| {
let opening = day.opening.iter().map(move |print| (day, "opening", print));
let closing = day.closing.iter().map(move |print| (day, "closing", print));
opening.chain(closing)
})
};
let days: Vec<Option<i32>> = prints()
.map(|(day, _, _)| {
i32::try_from(
day.date
.signed_duration_since(chrono::NaiveDate::default())
.num_days(),
)
.ok()
})
.collect();
vec![
Int32Chunked::from_iter_options(name("date"), days.into_iter())
.into_date()
.into_column(),
Column::new(
name("session"),
prints().map(|(_, session, _)| session).collect::<Vec<_>>(),
),
timestamps("timestamp", prints().map(|(_, _, print)| print.timestamp)),
Column::new(
name("exchange"),
prints()
.map(|(_, _, print)| print.exchange.as_str())
.collect::<Vec<_>>(),
),
floats("price", prints().map(|(_, _, print)| print.price)),
Column::new(
name("size"),
prints().map(|(_, _, print)| print.size).collect::<Vec<_>>(),
),
Column::new(
name("condition"),
prints()
.map(|(_, _, print)| print.condition.as_str())
.collect::<Vec<_>>(),
),
]
}
}