1use chrono::DateTime;
4use serde::{Deserialize, Serialize};
5use url::Url;
6
7use crate::{
8 client::{Interval, MarketSeries, Series},
9 errors::{MarketError, MarketResult},
10 publishers::Publisher,
11};
12
13const BASE_URL: &str = "https://api.massive.com/v2/aggs/ticker/";
14
15#[derive(Debug)]
17pub struct Massive {
18 token: String,
19}
20
21#[derive(Debug, Clone)]
22pub struct MassiveRequest {
23 symbol: String,
24 timespan: String,
25 multiplier: i32,
26 from_date: String,
27 to_date: String,
28 limit: i32,
29 interval: Interval,
30}
31
32impl Massive {
33 pub fn new(token: impl Into<String>) -> Self {
35 Massive {
36 token: token.into(),
37 }
38 }
39
40 pub fn intraday_series(
42 &self,
43 symbol: impl Into<String>,
44 from_date: impl Into<String>,
45 to_date: impl Into<String>,
46 interval: Interval,
47 limit: i32,
48 ) -> MarketResult<MassiveRequest> {
49 let (timespan, multiplier) = match interval {
50 Interval::Min1 => ("minute", 1),
51 Interval::Min5 => ("minute", 5),
52 Interval::Min15 => ("minute", 15),
53 Interval::Min30 => ("minute", 30),
54 Interval::Hour1 => ("hour", 1),
55 Interval::Hour2 => ("hour", 2),
56 Interval::Hour4 => ("hour", 4),
57 _ => {
58 return Err(MarketError::UnsuportedInterval(format!(
59 "{} interval is not supported by Massive",
60 interval
61 )))
62 }
63 };
64 Ok(MassiveRequest {
65 symbol: symbol.into(),
66 timespan: timespan.into(),
67 multiplier,
68 from_date: from_date.into(),
69 to_date: to_date.into(),
70 limit,
71 interval,
72 })
73 }
74
75 pub fn daily_series(
77 &self,
78 symbol: impl Into<String>,
79 from_date: impl Into<String>,
80 to_date: impl Into<String>,
81 limit: i32,
82 ) -> MassiveRequest {
83 MassiveRequest {
84 symbol: symbol.into(),
85 timespan: "day".to_string(),
86 multiplier: 1,
87 from_date: from_date.into(),
88 to_date: to_date.into(),
89 limit,
90 interval: Interval::Daily,
91 }
92 }
93
94 pub fn weekly_series(
96 &self,
97 symbol: impl Into<String>,
98 from_date: impl Into<String>,
99 to_date: impl Into<String>,
100 limit: i32,
101 ) -> MassiveRequest {
102 MassiveRequest {
103 symbol: symbol.into(),
104 timespan: "week".to_string(),
105 multiplier: 1,
106 from_date: from_date.into(),
107 to_date: to_date.into(),
108 limit,
109 interval: Interval::Weekly,
110 }
111 }
112
113 pub fn monthly_series(
115 &self,
116 symbol: impl Into<String>,
117 from_date: impl Into<String>,
118 to_date: impl Into<String>,
119 limit: i32,
120 ) -> MassiveRequest {
121 MassiveRequest {
122 symbol: symbol.into(),
123 timespan: "month".to_string(),
124 multiplier: 1,
125 from_date: from_date.into(),
126 to_date: to_date.into(),
127 limit,
128 interval: Interval::Monthly,
129 }
130 }
131}
132
133impl Publisher for Massive {
134 type Request = MassiveRequest;
135
136 fn create_endpoint(&self, request: &Self::Request) -> MarketResult<Url> {
137 let base_url = Url::parse(BASE_URL)?;
138 let mut url = base_url.join(&format!(
139 "{}/range/{}/{}/{}/{}",
140 request.symbol,
141 request.multiplier,
142 request.timespan,
143 request.from_date,
144 request.to_date,
145 ))?;
146 {
147 let mut pairs = url.query_pairs_mut();
148 pairs.append_pair("sort", "asc");
149 pairs.append_pair("limit", &request.limit.to_string());
150 pairs.append_pair("apiKey", &self.token);
151 }
152 Ok(url)
153 }
154
155 fn transform_data(&self, data: String, request: &Self::Request) -> MarketResult<MarketSeries> {
156 let prices: MassivePrices = serde_json::from_str(&data)?;
157
158 if prices.status != "OK" {
159 return Err(MarketError::DownloadedData(format!(
160 "Downloaded data status is: {}",
161 prices.status
162 )));
163 }
164
165 let mut data_series: Vec<Series> = Vec::with_capacity(prices.time_series.len());
166
167 for series in prices.time_series.iter() {
168 let datetime = DateTime::from_timestamp_millis(series.t).ok_or_else(|| {
169 MarketError::ParsingError("Unable to parse the timestamp".to_string())
170 })?;
171
172 data_series.push(Series {
173 datetime: datetime.naive_utc(),
174 open: series.o,
175 close: series.c,
176 high: series.h,
177 low: series.l,
178 volume: series.v,
179 })
180 }
181
182 Ok(MarketSeries {
183 symbol: prices.ticker.clone(),
184 interval: request.interval.clone(),
185 data: data_series,
186 })
187 }
188}
189
190#[derive(Debug, Serialize, Deserialize)]
191struct MassivePrices {
192 #[serde(rename = "results")]
193 time_series: Vec<TimeSeriesData>,
194 status: String,
195 ticker: String,
196}
197
198#[derive(Debug, Serialize, Deserialize)]
199struct TimeSeriesData {
200 c: f32,
201 h: f32,
202 l: f32,
203 o: f32,
204 t: i64,
205 v: f64,
206}