Skip to main content

finance_query/domains/
crypto.rs

1//! Cryptocurrency coin query handle.
2//!
3//! Created via [`Providers::crypto`](crate::Providers::crypto).
4
5use crate::constants::{Interval, TimeRange};
6use crate::error::Result;
7use crate::models::chart::Chart;
8
9domain_handle! {
10    /// A cryptocurrency coin backed by configured data providers.
11    ///
12    /// Created via [`Providers::crypto`](crate::Providers::crypto).
13    pub struct CryptoCoin { id, id }
14    cache: crate::models::crypto::CryptoQuote, chart
15}
16
17impl CryptoCoin {
18    /// Fetch the current quote for this coin priced in `vs_currency` (e.g., `"usd"`).
19    pub async fn quote(&self, vs_currency: &str) -> Result<crate::models::crypto::CryptoQuote> {
20        fetch_via_with!(
21            self,
22            id,
23            CRYPTO,
24            fetch_crypto_quote,
25            vs_currency,
26            crate::models::crypto::CryptoQuote
27        )
28    }
29
30    /// Fetch historical OHLCV candles for this coin priced in `vs_currency`.
31    ///
32    /// Unlike [`quote`](Self::quote) (which uses the coin *id*, e.g.
33    /// `"bitcoin"`), the `CHART` route is symbol-based, so the chart symbol is
34    /// built as `"{ID}-{VS}"` uppercased (e.g. `"BTC-USD"`). This resolves on
35    /// the default Yahoo route only when the handle's id is the coin's *ticker*
36    /// (`providers.crypto("BTC")`); coins identified by a CoinGecko id should
37    /// route `Capability::CHART` to a crypto-aware provider.
38    pub async fn chart(
39        &self,
40        vs_currency: &str,
41        interval: Interval,
42        range: TimeRange,
43    ) -> Result<Chart> {
44        let symbol = chart_symbol(self.id(), vs_currency);
45        fetch_chart_via!(self, symbol, interval, range)
46    }
47
48    /// Fetch historical candles over `range` at a sensible default interval
49    /// ([`TimeRange::default_interval`]).
50    pub async fn history(&self, vs_currency: &str, range: TimeRange) -> Result<Chart> {
51        self.chart(vs_currency, range.default_interval(), range)
52            .await
53    }
54
55    /// Compute all technical indicators from this coin's chart data (priced in
56    /// `vs_currency`).
57    #[cfg(feature = "indicators")]
58    pub async fn indicators(
59        &self,
60        vs_currency: &str,
61        interval: Interval,
62        range: TimeRange,
63    ) -> Result<crate::indicators::IndicatorsSummary> {
64        let chart = self.chart(vs_currency, interval, range).await?;
65        Ok(crate::indicators::summary::calculate_indicators(
66            &chart.candles,
67        ))
68    }
69
70    /// Compute a single technical indicator from this coin's chart data.
71    #[cfg(feature = "indicators")]
72    pub async fn indicator(
73        &self,
74        indicator: crate::indicators::Indicator,
75        vs_currency: &str,
76        interval: Interval,
77        range: TimeRange,
78    ) -> Result<crate::indicators::IndicatorResult> {
79        let chart = self.chart(vs_currency, interval, range).await?;
80        Ok(crate::indicators::compute_indicator(indicator, &chart)?)
81    }
82
83    /// Compute a risk summary from this coin's chart data, annualised with the
84    /// 24/7 crypto calendar (365 days/year). `beta` is always `None`.
85    #[cfg(feature = "risk")]
86    pub async fn risk(
87        &self,
88        vs_currency: &str,
89        interval: Interval,
90        range: TimeRange,
91    ) -> Result<crate::risk::RiskSummary> {
92        let chart = self.chart(vs_currency, interval, range).await?;
93        Ok(crate::risk::compute_risk_summary_with_periods(
94            &chart.candles,
95            None,
96            crate::risk::periods_per_year(interval, crate::risk::TradingCalendar::Crypto),
97        ))
98    }
99}
100
101/// Build the `CHART` route symbol `"{ID}-{VS}"`, uppercased (e.g. `"BTC-USD"`)
102/// — the Yahoo crypto convention, valid when the handle id is the coin ticker.
103fn chart_symbol(id: &str, vs_currency: &str) -> String {
104    format!("{}-{}", id.to_uppercase(), vs_currency.to_uppercase())
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn chart_symbol_uppercases_ticker_and_vs() {
113        assert_eq!(chart_symbol("BTC", "USD"), "BTC-USD");
114        assert_eq!(chart_symbol("eth", "eur"), "ETH-EUR");
115    }
116}