1use 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
11pub 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 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#[derive(Debug, Serialize, Deserialize, Clone)]
49pub struct MarketSeries {
50 pub symbol: String,
52 pub interval: Interval,
54 pub data: Vec<Series>,
56}
57
58#[derive(Debug, Serialize, Deserialize, Clone)]
60pub struct Series {
61 pub datetime: NaiveDateTime,
63 pub open: f32,
65 pub close: f32,
67 pub high: f32,
69 pub low: f32,
71 pub volume: f64,
73}
74
75#[derive(Debug, Serialize, Deserialize, Default, Clone)]
77pub enum Interval {
78 Min1,
80 Min5,
82 Min15,
84 Min30,
86 Hour1,
88 Hour2,
90 Hour4,
92 #[default]
94 Daily,
95 Weekly,
97 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
156impl 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}