1use serde::{Deserialize, Serialize};
4
5use crate::adapters::common::encode_path_segment;
6use crate::error::Result;
7
8use super::build_client;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
16#[non_exhaustive]
17pub struct TechnicalIndicatorValue {
18 pub date: Option<String>,
20 pub open: Option<f64>,
22 pub high: Option<f64>,
24 pub low: Option<f64>,
26 pub close: Option<f64>,
28 pub volume: Option<f64>,
30 pub sma: Option<f64>,
32 pub ema: Option<f64>,
34 pub rsi: Option<f64>,
36 pub macd: Option<f64>,
38 #[serde(rename = "macdSignal")]
40 pub macd_signal: Option<f64>,
41 #[serde(rename = "macdHist")]
43 pub macd_hist: Option<f64>,
44 pub wma: Option<f64>,
46 pub dema: Option<f64>,
48 pub tema: Option<f64>,
50 pub williams: Option<f64>,
52 pub adx: Option<f64>,
54}
55
56async fn fetch_daily_indicator(
62 symbol: &str,
63 indicator_name: &str,
64 period: u32,
65 type_: &str,
66) -> Result<Vec<TechnicalIndicatorValue>> {
67 let client = build_client()?;
68 let path = format!(
69 "/api/v3/technical_indicator/daily/{}",
70 encode_path_segment(symbol)
71 );
72 let period_str = period.to_string();
73 client
74 .get(
75 &path,
76 &[
77 ("period", &*period_str),
78 ("type", type_),
79 ("indicator", indicator_name),
80 ],
81 )
82 .await
83}
84
85async fn fetch_intraday_indicator(
87 symbol: &str,
88 interval: &str,
89 indicator_name: &str,
90 period: u32,
91 type_: &str,
92) -> Result<Vec<TechnicalIndicatorValue>> {
93 let client = build_client()?;
94 let path = format!(
95 "/api/v3/technical_indicator/{}/{}",
96 encode_path_segment(interval),
97 encode_path_segment(symbol)
98 );
99 let period_str = period.to_string();
100 client
101 .get(
102 &path,
103 &[
104 ("period", &*period_str),
105 ("type", type_),
106 ("indicator", indicator_name),
107 ],
108 )
109 .await
110}
111
112pub async fn daily_sma(
118 symbol: &str,
119 period: u32,
120 type_: &str,
121) -> Result<Vec<TechnicalIndicatorValue>> {
122 fetch_daily_indicator(symbol, "sma", period, type_).await
123}
124
125pub async fn daily_ema(
127 symbol: &str,
128 period: u32,
129 type_: &str,
130) -> Result<Vec<TechnicalIndicatorValue>> {
131 fetch_daily_indicator(symbol, "ema", period, type_).await
132}
133
134pub async fn daily_rsi(
136 symbol: &str,
137 period: u32,
138 type_: &str,
139) -> Result<Vec<TechnicalIndicatorValue>> {
140 fetch_daily_indicator(symbol, "rsi", period, type_).await
141}
142
143pub async fn daily_macd(symbol: &str, type_: &str) -> Result<Vec<TechnicalIndicatorValue>> {
145 let client = build_client()?;
146 let path = format!(
147 "/api/v3/technical_indicator/daily/{}",
148 encode_path_segment(symbol)
149 );
150 client
151 .get(&path, &[("type", type_), ("indicator", "macd")])
152 .await
153}
154
155pub async fn daily_wma(
157 symbol: &str,
158 period: u32,
159 type_: &str,
160) -> Result<Vec<TechnicalIndicatorValue>> {
161 fetch_daily_indicator(symbol, "wma", period, type_).await
162}
163
164pub async fn daily_dema(
166 symbol: &str,
167 period: u32,
168 type_: &str,
169) -> Result<Vec<TechnicalIndicatorValue>> {
170 fetch_daily_indicator(symbol, "dema", period, type_).await
171}
172
173pub async fn daily_tema(
175 symbol: &str,
176 period: u32,
177 type_: &str,
178) -> Result<Vec<TechnicalIndicatorValue>> {
179 fetch_daily_indicator(symbol, "tema", period, type_).await
180}
181
182pub async fn daily_williams(
184 symbol: &str,
185 period: u32,
186 type_: &str,
187) -> Result<Vec<TechnicalIndicatorValue>> {
188 fetch_daily_indicator(symbol, "williams", period, type_).await
189}
190
191pub async fn daily_adx(
193 symbol: &str,
194 period: u32,
195 type_: &str,
196) -> Result<Vec<TechnicalIndicatorValue>> {
197 fetch_daily_indicator(symbol, "adx", period, type_).await
198}
199
200pub async fn intraday_sma(
208 symbol: &str,
209 interval: &str,
210 period: u32,
211 type_: &str,
212) -> Result<Vec<TechnicalIndicatorValue>> {
213 fetch_intraday_indicator(symbol, interval, "sma", period, type_).await
214}
215
216pub async fn intraday_ema(
218 symbol: &str,
219 interval: &str,
220 period: u32,
221 type_: &str,
222) -> Result<Vec<TechnicalIndicatorValue>> {
223 fetch_intraday_indicator(symbol, interval, "ema", period, type_).await
224}
225
226pub async fn intraday_rsi(
228 symbol: &str,
229 interval: &str,
230 period: u32,
231 type_: &str,
232) -> Result<Vec<TechnicalIndicatorValue>> {
233 fetch_intraday_indicator(symbol, interval, "rsi", period, type_).await
234}
235
236pub async fn intraday_macd(
238 symbol: &str,
239 interval: &str,
240 type_: &str,
241) -> Result<Vec<TechnicalIndicatorValue>> {
242 let client = build_client()?;
243 let path = format!(
244 "/api/v3/technical_indicator/{}/{}",
245 encode_path_segment(interval),
246 encode_path_segment(symbol)
247 );
248 client
249 .get(&path, &[("type", type_), ("indicator", "macd")])
250 .await
251}
252
253pub async fn intraday_wma(
255 symbol: &str,
256 interval: &str,
257 period: u32,
258 type_: &str,
259) -> Result<Vec<TechnicalIndicatorValue>> {
260 fetch_intraday_indicator(symbol, interval, "wma", period, type_).await
261}
262
263pub async fn intraday_dema(
265 symbol: &str,
266 interval: &str,
267 period: u32,
268 type_: &str,
269) -> Result<Vec<TechnicalIndicatorValue>> {
270 fetch_intraday_indicator(symbol, interval, "dema", period, type_).await
271}
272
273pub async fn intraday_tema(
275 symbol: &str,
276 interval: &str,
277 period: u32,
278 type_: &str,
279) -> Result<Vec<TechnicalIndicatorValue>> {
280 fetch_intraday_indicator(symbol, interval, "tema", period, type_).await
281}
282
283pub async fn intraday_williams(
285 symbol: &str,
286 interval: &str,
287 period: u32,
288 type_: &str,
289) -> Result<Vec<TechnicalIndicatorValue>> {
290 fetch_intraday_indicator(symbol, interval, "williams", period, type_).await
291}
292
293pub async fn intraday_adx(
295 symbol: &str,
296 interval: &str,
297 period: u32,
298 type_: &str,
299) -> Result<Vec<TechnicalIndicatorValue>> {
300 fetch_intraday_indicator(symbol, interval, "adx", period, type_).await
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306
307 #[tokio::test]
308 async fn test_daily_sma_mock() {
309 let mut server = mockito::Server::new_async().await;
310 let _mock = server
311 .mock("GET", "/api/v3/technical_indicator/daily/AAPL")
312 .match_query(mockito::Matcher::AllOf(vec![
313 mockito::Matcher::UrlEncoded("apikey".into(), "test-key".into()),
314 mockito::Matcher::UrlEncoded("period".into(), "20".into()),
315 mockito::Matcher::UrlEncoded("type".into(), "close".into()),
316 mockito::Matcher::UrlEncoded("indicator".into(), "sma".into()),
317 ]))
318 .with_status(200)
319 .with_body(
320 serde_json::json!([
321 {
322 "date": "2024-01-15",
323 "open": 182.0,
324 "high": 185.0,
325 "low": 181.0,
326 "close": 184.0,
327 "volume": 50000000.0,
328 "sma": 183.5
329 }
330 ])
331 .to_string(),
332 )
333 .create_async()
334 .await;
335
336 let client = super::super::build_test_client(&server.url()).unwrap();
337 let path = "/api/v3/technical_indicator/daily/AAPL";
338 let resp: Vec<TechnicalIndicatorValue> = client
339 .get(
340 path,
341 &[("period", "20"), ("type", "close"), ("indicator", "sma")],
342 )
343 .await
344 .unwrap();
345 assert_eq!(resp.len(), 1);
346 assert!((resp[0].sma.unwrap() - 183.5).abs() < 0.01);
347 }
348
349 #[tokio::test]
350 async fn test_intraday_ema_mock() {
351 let mut server = mockito::Server::new_async().await;
352 let _mock = server
353 .mock("GET", "/api/v3/technical_indicator/5min/AAPL")
354 .match_query(mockito::Matcher::AllOf(vec![
355 mockito::Matcher::UrlEncoded("apikey".into(), "test-key".into()),
356 mockito::Matcher::UrlEncoded("period".into(), "10".into()),
357 mockito::Matcher::UrlEncoded("type".into(), "close".into()),
358 mockito::Matcher::UrlEncoded("indicator".into(), "ema".into()),
359 ]))
360 .with_status(200)
361 .with_body(
362 serde_json::json!([
363 {
364 "date": "2024-01-15 10:05:00",
365 "open": 182.5,
366 "high": 183.0,
367 "low": 182.0,
368 "close": 182.8,
369 "volume": 1200000.0,
370 "ema": 182.6
371 }
372 ])
373 .to_string(),
374 )
375 .create_async()
376 .await;
377
378 let client = super::super::build_test_client(&server.url()).unwrap();
379 let path = "/api/v3/technical_indicator/5min/AAPL";
380 let resp: Vec<TechnicalIndicatorValue> = client
381 .get(
382 path,
383 &[("period", "10"), ("type", "close"), ("indicator", "ema")],
384 )
385 .await
386 .unwrap();
387 assert_eq!(resp.len(), 1);
388 assert!((resp[0].ema.unwrap() - 182.6).abs() < 0.01);
389 }
390}