use anyhow::Result;
use clap::Parser;
use crate::client::{build_client, get_json};
use crate::config::canonical_base_url;
use crate::output::print_json;
#[derive(Debug, Parser)]
pub struct IndicatorCmd {
pub name: String,
pub symbol: String,
#[arg(long, default_value = "5m")]
pub tf: String,
#[arg(long)]
pub period: Option<u32>,
#[arg(long, default_value = "verbose")]
pub format: String,
#[arg(long)]
pub exchange: Option<String>,
}
impl IndicatorCmd {
pub async fn run(self, api_url: &str, pretty: bool) -> Result<()> {
let base = canonical_base_url(api_url);
let (client, token) = build_client()?;
let mut params: Vec<(&str, String)> = vec![
("symbol", self.symbol.to_uppercase()),
("tf", self.tf.clone()),
("format", self.format.clone()),
];
if let Some(p) = self.period {
params.push(("period", p.to_string()));
}
if let Some(ex) = &self.exchange {
params.push(("exchange", ex.clone()));
}
let url = format!("{base}/v1/indicator/{}", self.name.to_lowercase());
let rb = client.get(&url).bearer_auth(&token).query(¶ms);
let json = get_json(rb).await?;
print_json(&json, pretty)
}
}
#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
#[test]
fn parse_rsi_minimal() {
let cmd = IndicatorCmd::try_parse_from(["indicator", "rsi", "BTC-USD"]).unwrap();
assert_eq!(cmd.name, "rsi");
assert_eq!(cmd.symbol, "BTC-USD");
assert_eq!(cmd.tf, "5m");
assert!(cmd.period.is_none());
}
#[test]
fn parse_rsi_with_period() {
let cmd = IndicatorCmd::try_parse_from([
"indicator",
"rsi",
"ETH-USD",
"--tf",
"1h",
"--period",
"21",
])
.unwrap();
assert_eq!(cmd.tf, "1h");
assert_eq!(cmd.period, Some(21));
}
#[test]
fn parse_macd_with_exchange() {
let cmd = IndicatorCmd::try_parse_from([
"indicator",
"macd",
"BTC-USD",
"--exchange",
"coinbase",
])
.unwrap();
assert_eq!(cmd.name, "macd");
assert_eq!(cmd.exchange.as_deref(), Some("coinbase"));
}
#[test]
fn parse_compact_format() {
let cmd =
IndicatorCmd::try_parse_from(["indicator", "rsi", "BTC-USD", "--format", "compact"])
.unwrap();
assert_eq!(cmd.format, "compact");
}
#[test]
fn all_indicator_names_parse() {
let names = [
"sma",
"ema",
"rsi",
"macd",
"bb",
"atr",
"adx",
"stoch",
"vwap",
"obv",
"ichimoku",
"supertrend",
"pivot",
"fib",
"volume_profile",
"donchian",
"keltner",
"cci",
"psar",
];
for name in names {
let cmd = IndicatorCmd::try_parse_from(["indicator", name, "BTC-USD"])
.unwrap_or_else(|e| panic!("failed to parse '{name}': {e}"));
assert_eq!(cmd.name, name);
}
}
}