use serde::{Deserialize, Serialize};
use crate::adapters::common::encode_path_segment;
use crate::error::Result;
use super::build_client;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct TechnicalIndicatorValue {
pub date: Option<String>,
pub open: Option<f64>,
pub high: Option<f64>,
pub low: Option<f64>,
pub close: Option<f64>,
pub volume: Option<f64>,
pub sma: Option<f64>,
pub ema: Option<f64>,
pub rsi: Option<f64>,
pub macd: Option<f64>,
#[serde(rename = "macdSignal")]
pub macd_signal: Option<f64>,
#[serde(rename = "macdHist")]
pub macd_hist: Option<f64>,
pub wma: Option<f64>,
pub dema: Option<f64>,
pub tema: Option<f64>,
pub williams: Option<f64>,
pub adx: Option<f64>,
}
async fn fetch_daily_indicator(
symbol: &str,
indicator_name: &str,
period: u32,
type_: &str,
) -> Result<Vec<TechnicalIndicatorValue>> {
let client = build_client()?;
let path = format!(
"/api/v3/technical_indicator/daily/{}",
encode_path_segment(symbol)
);
let period_str = period.to_string();
client
.get(
&path,
&[
("period", &*period_str),
("type", type_),
("indicator", indicator_name),
],
)
.await
}
async fn fetch_intraday_indicator(
symbol: &str,
interval: &str,
indicator_name: &str,
period: u32,
type_: &str,
) -> Result<Vec<TechnicalIndicatorValue>> {
let client = build_client()?;
let path = format!(
"/api/v3/technical_indicator/{}/{}",
encode_path_segment(interval),
encode_path_segment(symbol)
);
let period_str = period.to_string();
client
.get(
&path,
&[
("period", &*period_str),
("type", type_),
("indicator", indicator_name),
],
)
.await
}
pub async fn daily_sma(
symbol: &str,
period: u32,
type_: &str,
) -> Result<Vec<TechnicalIndicatorValue>> {
fetch_daily_indicator(symbol, "sma", period, type_).await
}
pub async fn daily_ema(
symbol: &str,
period: u32,
type_: &str,
) -> Result<Vec<TechnicalIndicatorValue>> {
fetch_daily_indicator(symbol, "ema", period, type_).await
}
pub async fn daily_rsi(
symbol: &str,
period: u32,
type_: &str,
) -> Result<Vec<TechnicalIndicatorValue>> {
fetch_daily_indicator(symbol, "rsi", period, type_).await
}
pub async fn daily_macd(symbol: &str, type_: &str) -> Result<Vec<TechnicalIndicatorValue>> {
let client = build_client()?;
let path = format!(
"/api/v3/technical_indicator/daily/{}",
encode_path_segment(symbol)
);
client
.get(&path, &[("type", type_), ("indicator", "macd")])
.await
}
pub async fn daily_wma(
symbol: &str,
period: u32,
type_: &str,
) -> Result<Vec<TechnicalIndicatorValue>> {
fetch_daily_indicator(symbol, "wma", period, type_).await
}
pub async fn daily_dema(
symbol: &str,
period: u32,
type_: &str,
) -> Result<Vec<TechnicalIndicatorValue>> {
fetch_daily_indicator(symbol, "dema", period, type_).await
}
pub async fn daily_tema(
symbol: &str,
period: u32,
type_: &str,
) -> Result<Vec<TechnicalIndicatorValue>> {
fetch_daily_indicator(symbol, "tema", period, type_).await
}
pub async fn daily_williams(
symbol: &str,
period: u32,
type_: &str,
) -> Result<Vec<TechnicalIndicatorValue>> {
fetch_daily_indicator(symbol, "williams", period, type_).await
}
pub async fn daily_adx(
symbol: &str,
period: u32,
type_: &str,
) -> Result<Vec<TechnicalIndicatorValue>> {
fetch_daily_indicator(symbol, "adx", period, type_).await
}
pub async fn intraday_sma(
symbol: &str,
interval: &str,
period: u32,
type_: &str,
) -> Result<Vec<TechnicalIndicatorValue>> {
fetch_intraday_indicator(symbol, interval, "sma", period, type_).await
}
pub async fn intraday_ema(
symbol: &str,
interval: &str,
period: u32,
type_: &str,
) -> Result<Vec<TechnicalIndicatorValue>> {
fetch_intraday_indicator(symbol, interval, "ema", period, type_).await
}
pub async fn intraday_rsi(
symbol: &str,
interval: &str,
period: u32,
type_: &str,
) -> Result<Vec<TechnicalIndicatorValue>> {
fetch_intraday_indicator(symbol, interval, "rsi", period, type_).await
}
pub async fn intraday_macd(
symbol: &str,
interval: &str,
type_: &str,
) -> Result<Vec<TechnicalIndicatorValue>> {
let client = build_client()?;
let path = format!(
"/api/v3/technical_indicator/{}/{}",
encode_path_segment(interval),
encode_path_segment(symbol)
);
client
.get(&path, &[("type", type_), ("indicator", "macd")])
.await
}
pub async fn intraday_wma(
symbol: &str,
interval: &str,
period: u32,
type_: &str,
) -> Result<Vec<TechnicalIndicatorValue>> {
fetch_intraday_indicator(symbol, interval, "wma", period, type_).await
}
pub async fn intraday_dema(
symbol: &str,
interval: &str,
period: u32,
type_: &str,
) -> Result<Vec<TechnicalIndicatorValue>> {
fetch_intraday_indicator(symbol, interval, "dema", period, type_).await
}
pub async fn intraday_tema(
symbol: &str,
interval: &str,
period: u32,
type_: &str,
) -> Result<Vec<TechnicalIndicatorValue>> {
fetch_intraday_indicator(symbol, interval, "tema", period, type_).await
}
pub async fn intraday_williams(
symbol: &str,
interval: &str,
period: u32,
type_: &str,
) -> Result<Vec<TechnicalIndicatorValue>> {
fetch_intraday_indicator(symbol, interval, "williams", period, type_).await
}
pub async fn intraday_adx(
symbol: &str,
interval: &str,
period: u32,
type_: &str,
) -> Result<Vec<TechnicalIndicatorValue>> {
fetch_intraday_indicator(symbol, interval, "adx", period, type_).await
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_daily_sma_mock() {
let mut server = mockito::Server::new_async().await;
let _mock = server
.mock("GET", "/api/v3/technical_indicator/daily/AAPL")
.match_query(mockito::Matcher::AllOf(vec![
mockito::Matcher::UrlEncoded("apikey".into(), "test-key".into()),
mockito::Matcher::UrlEncoded("period".into(), "20".into()),
mockito::Matcher::UrlEncoded("type".into(), "close".into()),
mockito::Matcher::UrlEncoded("indicator".into(), "sma".into()),
]))
.with_status(200)
.with_body(
serde_json::json!([
{
"date": "2024-01-15",
"open": 182.0,
"high": 185.0,
"low": 181.0,
"close": 184.0,
"volume": 50000000.0,
"sma": 183.5
}
])
.to_string(),
)
.create_async()
.await;
let client = super::super::build_test_client(&server.url()).unwrap();
let path = "/api/v3/technical_indicator/daily/AAPL";
let resp: Vec<TechnicalIndicatorValue> = client
.get(
path,
&[("period", "20"), ("type", "close"), ("indicator", "sma")],
)
.await
.unwrap();
assert_eq!(resp.len(), 1);
assert!((resp[0].sma.unwrap() - 183.5).abs() < 0.01);
}
#[tokio::test]
async fn test_intraday_ema_mock() {
let mut server = mockito::Server::new_async().await;
let _mock = server
.mock("GET", "/api/v3/technical_indicator/5min/AAPL")
.match_query(mockito::Matcher::AllOf(vec![
mockito::Matcher::UrlEncoded("apikey".into(), "test-key".into()),
mockito::Matcher::UrlEncoded("period".into(), "10".into()),
mockito::Matcher::UrlEncoded("type".into(), "close".into()),
mockito::Matcher::UrlEncoded("indicator".into(), "ema".into()),
]))
.with_status(200)
.with_body(
serde_json::json!([
{
"date": "2024-01-15 10:05:00",
"open": 182.5,
"high": 183.0,
"low": 182.0,
"close": 182.8,
"volume": 1200000.0,
"ema": 182.6
}
])
.to_string(),
)
.create_async()
.await;
let client = super::super::build_test_client(&server.url()).unwrap();
let path = "/api/v3/technical_indicator/5min/AAPL";
let resp: Vec<TechnicalIndicatorValue> = client
.get(
path,
&[("period", "10"), ("type", "close"), ("indicator", "ema")],
)
.await
.unwrap();
assert_eq!(resp.len(), 1);
assert!((resp[0].ema.unwrap() - 182.6).abs() < 0.01);
}
}