Skip to main content

market_data/
client.rs

1//! Market-Data client implementation
2
3use crate::{
4    errors::MarketResult, indicators::EnhancedMarketSeries, publishers::Publisher, MarketError,
5};
6use chrono::NaiveDateTime;
7use serde::{Deserialize, Serialize};
8use std::fmt;
9use std::str::FromStr;
10
11/// MarketClient holds the Publisher and reqwest::Client
12pub struct MarketClient<T: Publisher> {
13    pub site: T,
14    inner: reqwest::Client,
15}
16
17impl<T: Publisher> MarketClient<T> {
18    pub fn new(site: T) -> Self {
19        Self {
20            site,
21            inner: reqwest::Client::builder()
22                .user_agent("market-data-rust/0.4.0")
23                .build()
24                .unwrap_or_default(),
25        }
26    }
27
28    /// Fetches the data for a single request
29    pub async fn fetch(&self, request: T::Request) -> MarketResult<MarketSeries> {
30        let url = self.site.create_endpoint(&request)?;
31        let response = self.inner.get(url).send().await?;
32
33        if !response.status().is_success() {
34            let status = response.status();
35            let body = response.text().await.unwrap_or_default();
36            return Err(MarketError::HttpError(format!(
37                "HTTP Error: status code {}, body: {}",
38                status, body
39            )));
40        }
41
42        let body = response.text().await?;
43        self.site.transform_data(body, &request)
44    }
45}
46
47/// Holds the parsed data from Publishers
48#[derive(Debug, Serialize, Deserialize, Clone)]
49pub struct MarketSeries {
50    /// holds symbol like: "GOOGL"
51    pub symbol: String,
52    /// inteval from intraday to monthly
53    pub interval: Interval,
54    /// the original series downloaded and parsed from publishers
55    pub data: Vec<Series>,
56}
57
58/// Series part of the MarketSeries
59#[derive(Debug, Serialize, Deserialize, Clone)]
60pub struct Series {
61    /// the date and time of the stock price
62    pub datetime: NaiveDateTime,
63    /// the opening price of the stock for the selected interval
64    pub open: f32,
65    /// the closing price of the stock for the selected interval
66    pub close: f32,
67    /// the highest price of the stock for the selected interval
68    pub high: f32,
69    /// the lowest price of the stock for the selected interval
70    pub low: f32,
71    /// the number of shares traded in the selected interval
72    pub volume: f64,
73}
74
75/// The time interval between two data points
76#[derive(Debug, Serialize, Deserialize, Default, Clone)]
77pub enum Interval {
78    /// 1 minute interval
79    Min1,
80    /// 5 minutes interval
81    Min5,
82    /// 15 minutes interval
83    Min15,
84    /// 30 minutes interval
85    Min30,
86    /// 1 hour interval
87    Hour1,
88    /// 2 hours interval
89    Hour2,
90    /// 4 hours interval
91    Hour4,
92    /// daily interval
93    #[default]
94    Daily,
95    /// weekly interval
96    Weekly,
97    /// monthly interval
98    Monthly,
99}
100
101impl MarketSeries {
102    pub fn enhance_data(self) -> EnhancedMarketSeries {
103        EnhancedMarketSeries {
104            symbol: self.symbol,
105            interval: self.interval,
106            series: self.data,
107            asks: Vec::new(),
108            indicators: Default::default(),
109        }
110    }
111}
112
113impl fmt::Display for MarketSeries {
114    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115        writeln!(
116            f,
117            "MarketSeries: Symbol = {}, Interval = {}",
118            self.symbol, self.interval
119        )?;
120        for series in &self.data {
121            writeln!(f, "  {}", series)?;
122        }
123        Ok(())
124    }
125}
126
127impl fmt::Display for Series {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        write!(
130            f,
131            "DateTime: {}, Open: {:.2}, Close: {:.2}, High: {:.2}, Low: {:.2}, Volume: {:.2}",
132            self.datetime, self.open, self.close, self.high, self.low, self.volume
133        )
134    }
135}
136
137impl fmt::Display for Interval {
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        let interval_str = match self {
140            Interval::Min1 => "1 minute",
141            Interval::Min5 => "5 minutes",
142            Interval::Min15 => "15 minutes",
143            Interval::Min30 => "30 minutes",
144            Interval::Hour1 => "1 hour",
145            Interval::Hour2 => "2 hours",
146            Interval::Hour4 => "4 hours",
147            Interval::Daily => "Daily",
148            Interval::Weekly => "Weekly",
149            Interval::Monthly => "Monthly",
150        };
151
152        write!(f, "{}", interval_str)
153    }
154}
155
156/// AlphaVantage response interval: 1min, 5min, 15min, 30min, 60min,
157/// Twelvedata response interval: 1min, 5min, 15min, 30min, 1h, 2h, 4h, 1day, 1week, 1month
158impl std::str::FromStr for Interval {
159    type Err = &'static str;
160
161    fn from_str(s: &str) -> Result<Self, Self::Err> {
162        match s.to_lowercase().as_str() {
163            "1min" => Ok(Interval::Min1),
164            "5min" => Ok(Interval::Min5),
165            "15min" => Ok(Interval::Min15),
166            "30min" => Ok(Interval::Min30),
167            "60min" => Ok(Interval::Hour1),
168            "1h" => Ok(Interval::Hour1),
169            "2h" => Ok(Interval::Hour2),
170            "4h" => Ok(Interval::Hour4),
171            "1day" => Ok(Interval::Daily),
172            "1week" => Ok(Interval::Weekly),
173            "1month" => Ok(Interval::Monthly),
174            _ => Err("Invalid interval string"),
175        }
176    }
177}
178
179impl From<String> for Interval {
180    fn from(s: String) -> Self {
181        Interval::from_str(&s).unwrap_or(Interval::Daily)
182    }
183}