1use chrono::{Utc};
11use governor::{DefaultDirectRateLimiter, Quota, RateLimiter};
12use reqwest::Client;
13use rust_decimal::Decimal;
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16use std::num::NonZeroU32;
17use std::time::Duration;
18use tracing::{debug, error};
19
20#[derive(Debug, Clone)]
22pub struct AlphaVantageConfig {
23 pub api_key: String,
25 pub timeout: Duration,
27}
28
29impl Default for AlphaVantageConfig {
30 fn default() -> Self {
31 Self {
32 api_key: String::new(),
33 timeout: Duration::from_secs(30),
34 }
35 }
36}
37
38pub struct AlphaVantageClient {
40 client: Client,
41 config: AlphaVantageConfig,
42 base_url: String,
43 rate_limiter: DefaultDirectRateLimiter,
44}
45
46impl AlphaVantageClient {
47 pub fn new(config: AlphaVantageConfig) -> Self {
49 let client = Client::builder()
50 .timeout(config.timeout)
51 .build()
52 .expect("Failed to create HTTP client");
53
54 let quota = Quota::per_minute(NonZeroU32::new(5).unwrap());
56 let rate_limiter = RateLimiter::direct(quota);
57
58 Self {
59 client,
60 config,
61 base_url: "https://www.alphavantage.co/query".to_string(),
62 rate_limiter,
63 }
64 }
65
66 pub async fn get_quote(&self, symbol: &str) -> Result<AlphaVantageQuote, AlphaVantageError> {
68 self.rate_limiter.until_ready().await;
69
70 let params = [
71 ("function", "GLOBAL_QUOTE"),
72 ("symbol", symbol),
73 ("apikey", &self.config.api_key),
74 ];
75
76 debug!("Alpha Vantage request: GLOBAL_QUOTE for {}", symbol);
77
78 let response = self
79 .client
80 .get(&self.base_url)
81 .query(¶ms)
82 .send()
83 .await?;
84
85 if response.status().is_success() {
86 let data: serde_json::Value = response.json().await?;
87
88 if let Some(quote) = data.get("Global Quote") {
89 Ok(AlphaVantageQuote {
90 symbol: quote.get("01. symbol")
91 .and_then(|v| v.as_str())
92 .unwrap_or(symbol)
93 .to_string(),
94 price: quote.get("05. price")
95 .and_then(|v| v.as_str())
96 .and_then(|s| Decimal::from_str_exact(s).ok())
97 .unwrap_or_default(),
98 volume: quote.get("06. volume")
99 .and_then(|v| v.as_str())
100 .and_then(|s| s.parse().ok())
101 .unwrap_or(0),
102 change: quote.get("09. change")
103 .and_then(|v| v.as_str())
104 .and_then(|s| Decimal::from_str_exact(s).ok())
105 .unwrap_or_default(),
106 change_percent: quote.get("10. change percent")
107 .and_then(|v| v.as_str())
108 .map(|s| s.replace("%", ""))
109 .and_then(|s| Decimal::from_str_exact(&s).ok())
110 .unwrap_or_default(),
111 })
112 } else {
113 Err(AlphaVantageError::ApiError("No quote data found".to_string()))
114 }
115 } else {
116 let error_text = response.text().await.unwrap_or_default();
117 error!("Alpha Vantage API error: {}", error_text);
118 Err(AlphaVantageError::ApiError(error_text))
119 }
120 }
121
122 pub async fn get_daily(
124 &self,
125 symbol: &str,
126 outputsize: &str, ) -> Result<Vec<AlphaVantageBar>, AlphaVantageError> {
128 self.rate_limiter.until_ready().await;
129
130 let params = [
131 ("function", "TIME_SERIES_DAILY"),
132 ("symbol", symbol),
133 ("outputsize", outputsize),
134 ("apikey", &self.config.api_key),
135 ];
136
137 let response = self
138 .client
139 .get(&self.base_url)
140 .query(¶ms)
141 .send()
142 .await?;
143
144 if response.status().is_success() {
145 let data: serde_json::Value = response.json().await?;
146
147 if let Some(time_series) = data.get("Time Series (Daily)").and_then(|v| v.as_object()) {
148 let mut bars = Vec::new();
149
150 for (date, values) in time_series {
151 if let Some(obj) = values.as_object() {
152 bars.push(AlphaVantageBar {
153 date: date.clone(),
154 open: obj.get("1. open")
155 .and_then(|v| v.as_str())
156 .and_then(|s| Decimal::from_str_exact(s).ok())
157 .unwrap_or_default(),
158 high: obj.get("2. high")
159 .and_then(|v| v.as_str())
160 .and_then(|s| Decimal::from_str_exact(s).ok())
161 .unwrap_or_default(),
162 low: obj.get("3. low")
163 .and_then(|v| v.as_str())
164 .and_then(|s| Decimal::from_str_exact(s).ok())
165 .unwrap_or_default(),
166 close: obj.get("4. close")
167 .and_then(|v| v.as_str())
168 .and_then(|s| Decimal::from_str_exact(s).ok())
169 .unwrap_or_default(),
170 volume: obj.get("5. volume")
171 .and_then(|v| v.as_str())
172 .and_then(|s| s.parse().ok())
173 .unwrap_or(0),
174 });
175 }
176 }
177
178 Ok(bars)
179 } else {
180 Err(AlphaVantageError::ApiError("No time series data found".to_string()))
181 }
182 } else {
183 let error_text = response.text().await.unwrap_or_default();
184 Err(AlphaVantageError::ApiError(error_text))
185 }
186 }
187
188 pub async fn get_indicator(
190 &self,
191 symbol: &str,
192 indicator: &str, interval: &str, time_period: u32,
195 ) -> Result<HashMap<String, Decimal>, AlphaVantageError> {
196 self.rate_limiter.until_ready().await;
197
198 let params = [
199 ("function", indicator),
200 ("symbol", symbol),
201 ("interval", interval),
202 ("time_period", &time_period.to_string()),
203 ("series_type", "close"),
204 ("apikey", &self.config.api_key),
205 ];
206
207 let response = self
208 .client
209 .get(&self.base_url)
210 .query(¶ms)
211 .send()
212 .await?;
213
214 if response.status().is_success() {
215 let data: serde_json::Value = response.json().await?;
216
217 let key = format!("Technical Analysis: {}", indicator);
218 if let Some(indicator_data) = data.get(&key).and_then(|v| v.as_object()) {
219 let mut results = HashMap::new();
220
221 for (date, values) in indicator_data {
222 if let Some(value) = values.get(indicator).and_then(|v| v.as_str()) {
223 if let Ok(decimal_value) = Decimal::from_str_exact(value) {
224 results.insert(date.clone(), decimal_value);
225 }
226 }
227 }
228
229 Ok(results)
230 } else {
231 Err(AlphaVantageError::ApiError("No indicator data found".to_string()))
232 }
233 } else {
234 let error_text = response.text().await.unwrap_or_default();
235 Err(AlphaVantageError::ApiError(error_text))
236 }
237 }
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct AlphaVantageQuote {
242 pub symbol: String,
243 pub price: Decimal,
244 pub volume: i64,
245 pub change: Decimal,
246 pub change_percent: Decimal,
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct AlphaVantageBar {
251 pub date: String,
252 pub open: Decimal,
253 pub high: Decimal,
254 pub low: Decimal,
255 pub close: Decimal,
256 pub volume: i64,
257}
258
259#[derive(Debug, thiserror::Error)]
260pub enum AlphaVantageError {
261 #[error("API error: {0}")]
262 ApiError(String),
263
264 #[error("Network error: {0}")]
265 Network(#[from] reqwest::Error),
266
267 #[error("Parse error: {0}")]
268 Parse(#[from] serde_json::Error),
269
270 #[error("Rate limit exceeded")]
271 RateLimit,
272
273 #[error(transparent)]
274 Other(#[from] anyhow::Error),
275}