use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "python", pyo3::prelude::pyclass)]
#[cfg_attr(feature = "js", napi_derive::napi(object))]
pub struct SmaResponse {
pub symbol: String,
#[serde(rename = "type", default)]
pub data_type: Option<String>,
#[serde(default)]
pub exchange: Option<String>,
#[serde(default)]
pub market: Option<String>,
#[serde(default)]
pub timeframe: Option<String>,
pub period: u32,
pub data: Vec<SmaDataPoint>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "python", pyo3::prelude::pyclass)]
#[cfg_attr(feature = "js", napi_derive::napi(object))]
pub struct SmaDataPoint {
pub date: String,
pub sma: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "python", pyo3::prelude::pyclass)]
#[cfg_attr(feature = "js", napi_derive::napi(object))]
pub struct RsiResponse {
pub symbol: String,
#[serde(rename = "type", default)]
pub data_type: Option<String>,
#[serde(default)]
pub exchange: Option<String>,
#[serde(default)]
pub market: Option<String>,
#[serde(default)]
pub timeframe: Option<String>,
pub period: u32,
pub data: Vec<RsiDataPoint>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "python", pyo3::prelude::pyclass)]
#[cfg_attr(feature = "js", napi_derive::napi(object))]
pub struct RsiDataPoint {
pub date: String,
pub rsi: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "python", pyo3::prelude::pyclass)]
#[cfg_attr(feature = "js", napi_derive::napi(object))]
pub struct KdjResponse {
pub symbol: String,
#[serde(rename = "type", default)]
pub data_type: Option<String>,
#[serde(default)]
pub exchange: Option<String>,
#[serde(default)]
pub market: Option<String>,
#[serde(default)]
pub timeframe: Option<String>,
#[serde(rename = "rPeriod", default)]
pub r_period: Option<u32>,
#[serde(rename = "kPeriod", default)]
pub k_period: Option<u32>,
#[serde(rename = "dPeriod", default)]
pub d_period: Option<u32>,
pub data: Vec<KdjDataPoint>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "python", pyo3::prelude::pyclass)]
#[cfg_attr(feature = "js", napi_derive::napi(object))]
pub struct KdjDataPoint {
pub date: String,
pub k: f64,
pub d: f64,
pub j: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "python", pyo3::prelude::pyclass)]
#[cfg_attr(feature = "js", napi_derive::napi(object))]
pub struct MacdResponse {
pub symbol: String,
#[serde(rename = "type", default)]
pub data_type: Option<String>,
#[serde(default)]
pub exchange: Option<String>,
#[serde(default)]
pub market: Option<String>,
#[serde(default)]
pub timeframe: Option<String>,
pub fast: u32,
pub slow: u32,
pub signal: u32,
pub data: Vec<MacdDataPoint>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "python", pyo3::prelude::pyclass)]
#[cfg_attr(feature = "js", napi_derive::napi(object))]
pub struct MacdDataPoint {
pub date: String,
#[serde(rename = "macdLine")]
pub macd: f64,
#[serde(rename = "signalLine")]
pub signal_value: f64,
#[serde(default)]
pub histogram: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "python", pyo3::prelude::pyclass)]
#[cfg_attr(feature = "js", napi_derive::napi(object))]
pub struct BbResponse {
pub symbol: String,
#[serde(rename = "type", default)]
pub data_type: Option<String>,
#[serde(default)]
pub exchange: Option<String>,
#[serde(default)]
pub market: Option<String>,
#[serde(default)]
pub timeframe: Option<String>,
pub period: u32,
#[serde(default)]
pub stddev: Option<f64>,
pub data: Vec<BbDataPoint>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "python", pyo3::prelude::pyclass)]
#[cfg_attr(feature = "js", napi_derive::napi(object))]
pub struct BbDataPoint {
pub date: String,
pub upper: f64,
pub middle: f64,
pub lower: f64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sma_response_deserialization() {
let json = r#"{
"symbol": "2330",
"type": "EQUITY",
"exchange": "TWSE",
"market": "TSE",
"timeframe": "D",
"period": 20,
"data": [
{"date": "2024-01-15", "sma": 580.5},
{"date": "2024-01-16", "sma": 581.2}
]
}"#;
let response: SmaResponse = serde_json::from_str(json).unwrap();
assert_eq!(response.symbol, "2330");
assert_eq!(response.data_type.as_deref(), Some("EQUITY"));
assert_eq!(response.period, 20);
assert_eq!(response.data.len(), 2);
assert_eq!(response.data[0].sma, 580.5);
}
#[test]
fn test_sma_response_prod_shape() {
let json = r#"{"symbol":"2330","period":20,
"data":[{"date":"2026-04-16","sma":1895.0}]}"#;
let r: SmaResponse = serde_json::from_str(json).unwrap();
assert_eq!(r.data_type, None);
assert_eq!(r.exchange, None);
assert_eq!(r.timeframe, None);
assert_eq!(r.data[0].sma, 1895.0);
}
#[test]
fn test_rsi_response_deserialization() {
let json = r#"{
"symbol": "2330",
"type": "EQUITY",
"exchange": "TWSE",
"market": "TSE",
"timeframe": "D",
"period": 14,
"data": [
{"date": "2024-01-15", "rsi": 65.5},
{"date": "2024-01-16", "rsi": 68.2}
]
}"#;
let response: RsiResponse = serde_json::from_str(json).unwrap();
assert_eq!(response.symbol, "2330");
assert_eq!(response.period, 14);
assert_eq!(response.data.len(), 2);
assert_eq!(response.data[0].rsi, 65.5);
}
#[test]
fn test_kdj_response_deserialization() {
let json = r#"{
"symbol": "2330",
"rPeriod": 9,
"kPeriod": 3,
"dPeriod": 3,
"data": [
{"date": "2024-01-15", "k": 75.5, "d": 70.2, "j": 86.1},
{"date": "2024-01-16", "k": 78.3, "d": 72.8, "j": 89.3}
]
}"#;
let response: KdjResponse = serde_json::from_str(json).unwrap();
assert_eq!(response.symbol, "2330");
assert_eq!(response.r_period, Some(9));
assert_eq!(response.k_period, Some(3));
assert_eq!(response.d_period, Some(3));
assert_eq!(response.data_type, None);
assert_eq!(response.data.len(), 2);
assert_eq!(response.data[0].k, 75.5);
assert_eq!(response.data[0].d, 70.2);
assert_eq!(response.data[0].j, 86.1);
}
#[test]
fn test_macd_response_deserialization() {
let json = r#"{
"symbol": "2330",
"fast": 12,
"slow": 26,
"signal": 9,
"data": [
{"date": "2024-01-15", "macdLine": 5.5, "signalLine": 4.2},
{"date": "2024-01-16", "macdLine": 6.2, "signalLine": 4.8}
]
}"#;
let response: MacdResponse = serde_json::from_str(json).unwrap();
assert_eq!(response.symbol, "2330");
assert_eq!(response.fast, 12);
assert_eq!(response.slow, 26);
assert_eq!(response.signal, 9);
assert_eq!(response.data_type, None);
assert_eq!(response.data.len(), 2);
assert_eq!(response.data[0].macd, 5.5);
assert_eq!(response.data[0].signal_value, 4.2);
assert_eq!(response.data[0].histogram, None);
}
#[test]
fn test_bb_response_deserialization() {
let json = r#"{
"symbol": "2330",
"type": "EQUITY",
"exchange": "TWSE",
"market": "TSE",
"timeframe": "D",
"period": 20,
"stddev": 2.0,
"data": [
{"date": "2024-01-15", "upper": 600.5, "middle": 580.0, "lower": 559.5},
{"date": "2024-01-16", "upper": 602.2, "middle": 581.0, "lower": 559.8}
]
}"#;
let response: BbResponse = serde_json::from_str(json).unwrap();
assert_eq!(response.symbol, "2330");
assert_eq!(response.period, 20);
assert_eq!(response.stddev, Some(2.0));
assert_eq!(response.data.len(), 2);
}
#[test]
fn test_bb_response_prod_shape() {
let json = r#"{"symbol":"2330","period":20,
"data":[{"date":"2026-04-16","upper":2092.4,"middle":1895.0,"lower":1697.6}]}"#;
let r: BbResponse = serde_json::from_str(json).unwrap();
assert_eq!(r.stddev, None);
assert_eq!(r.data_type, None);
assert_eq!(r.data[0].middle, 1895.0);
}
}