use serde::{Deserialize, Serialize};
use crate::source::SourceId;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct IndicatorField {
pub name: String,
pub value: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct IndicatorValue {
pub name: String,
pub value: Option<f64>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub fields: Vec<IndicatorField>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub series: Vec<f64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct OhlcBar {
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
pub timestamp: i64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChartView {
pub symbol: String,
pub last: f64,
pub series: Vec<f64>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub bars: Vec<OhlcBar>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub forming: Option<OhlcBar>,
pub indicators: Vec<IndicatorValue>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Level {
pub price: f64,
pub quantity: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BookView {
pub symbol: String,
pub bids: Vec<Level>,
pub asks: Vec<Level>,
pub spread: Option<f64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TapePrint {
pub price: f64,
pub quantity: f64,
pub side: String,
pub timestamp: i64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TapeView {
pub symbol: String,
pub prints: Vec<TapePrint>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WatchRow {
pub source: SourceId,
pub symbol: String,
pub last: f64,
pub bid: f64,
pub ask: f64,
pub volume: f64,
pub change: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct WatchlistView {
pub rows: Vec<WatchRow>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FootprintLevel {
pub price: f64,
pub buy: f64,
pub sell: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FootprintView {
pub symbol: String,
pub levels: Vec<FootprintLevel>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ProfileRow {
pub label: String,
pub bins: Vec<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub price_low: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub price_high: Option<f64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ProfileView {
pub symbol: String,
pub profiles: Vec<ProfileRow>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BarStreamView {
pub label: String,
pub bars: Vec<crate::registry::AltBar>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BarsView {
pub symbol: String,
pub streams: Vec<BarStreamView>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "panel", rename_all = "snake_case")]
pub enum PanelView {
Chart(ChartView),
Book(BookView),
Tape(TapeView),
Watchlist(WatchlistView),
Footprint(FootprintView),
Profile(ProfileView),
Bars(BarsView),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Frame {
pub panels: Vec<PanelView>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn panel_view_is_tagged_in_json() {
let view = PanelView::Chart(ChartView {
symbol: "BTC/USDT".to_string(),
last: 100.0,
series: vec![99.0, 100.0],
bars: vec![OhlcBar {
open: 99.0,
high: 101.0,
low: 98.0,
close: 100.0,
volume: 3.0,
timestamp: 0,
}],
forming: None,
indicators: vec![IndicatorValue {
name: "Sma(20)".to_string(),
value: None,
fields: Vec::new(),
series: Vec::new(),
}],
});
let json = serde_json::to_string(&view).unwrap();
assert!(json.contains("\"panel\":\"chart\""));
assert_eq!(serde_json::from_str::<PanelView>(&json).unwrap(), view);
}
#[test]
fn frame_round_trips() {
let frame = Frame {
panels: vec![PanelView::Watchlist(WatchlistView { rows: vec![] })],
};
let json = serde_json::to_string(&frame).unwrap();
assert_eq!(serde_json::from_str::<Frame>(&json).unwrap(), frame);
}
}