Skip to main content

tvdata_rs/crypto/
mod.rs

1use crate::batch::BatchResult;
2use crate::client::TradingViewClient;
3use crate::error::Result;
4use crate::market_data::{
5    QuoteSnapshot, RowDecoder, SnapshotLoader, TechnicalSummary, decode_quote, decode_technical,
6    quote_columns, technical_columns,
7};
8use crate::scanner::{Column, PriceConversion, ScanQuery, SortOrder, Ticker};
9
10#[cfg(test)]
11mod tests;
12
13/// High-level crypto market facade for quote snapshots, technicals, and movers.
14///
15/// # Examples
16///
17/// ```no_run
18/// use tvdata_rs::{Result, TradingViewClient};
19///
20/// #[tokio::main]
21/// async fn main() -> Result<()> {
22///     let client = TradingViewClient::builder().build()?;
23///     let quote = client.crypto().quote("BINANCE:BTCUSDT").await?;
24///
25///     println!("{:?}", quote.close);
26///     Ok(())
27/// }
28/// ```
29#[derive(Debug, Clone, Copy)]
30pub struct CryptoClient<'a> {
31    client: &'a TradingViewClient,
32}
33
34impl<'a> CryptoClient<'a> {
35    pub const fn new(client: &'a TradingViewClient) -> Self {
36        Self { client }
37    }
38
39    pub fn client(&self) -> &'a TradingViewClient {
40        self.client
41    }
42
43    /// Fetches a typed crypto quote snapshot for a single symbol.
44    pub async fn quote(&self, symbol: impl Into<Ticker>) -> Result<QuoteSnapshot> {
45        let columns = quote_columns();
46        let decoder = RowDecoder::new(&columns);
47        let row = self.loader().fetch_one(symbol, columns).await?;
48        Ok(decode_quote(&decoder, &row))
49    }
50
51    /// Fetches typed crypto quote snapshots for multiple symbols while preserving the requested
52    /// order.
53    pub async fn quotes<I, T>(&self, symbols: I) -> Result<Vec<QuoteSnapshot>>
54    where
55        I: IntoIterator<Item = T>,
56        T: Into<Ticker>,
57    {
58        let columns = quote_columns();
59        let decoder = RowDecoder::new(&columns);
60        let rows = self.loader().fetch_many(symbols, columns).await?;
61
62        Ok(rows
63            .iter()
64            .map(|row| decode_quote(&decoder, row))
65            .collect::<Vec<_>>())
66    }
67
68    pub async fn quotes_batch<I, T>(&self, symbols: I) -> Result<BatchResult<QuoteSnapshot>>
69    where
70        I: IntoIterator<Item = T>,
71        T: Into<Ticker>,
72    {
73        let columns = quote_columns();
74        let decoder = RowDecoder::new(&columns);
75        let rows = self.loader().fetch_many_detailed(symbols, columns).await?;
76
77        Ok(BatchResult {
78            successes: rows
79                .successes
80                .into_iter()
81                .map(|(ticker, row)| (ticker, decode_quote(&decoder, &row)))
82                .collect(),
83            missing: rows.missing,
84            failures: rows.failures,
85        })
86    }
87
88    pub async fn technical_summary(&self, symbol: impl Into<Ticker>) -> Result<TechnicalSummary> {
89        let columns = technical_columns();
90        let decoder = RowDecoder::new(&columns);
91        let row = self.loader().fetch_one(symbol, columns).await?;
92        Ok(decode_technical(&decoder, &row))
93    }
94
95    pub async fn technical_summaries<I, T>(&self, symbols: I) -> Result<Vec<TechnicalSummary>>
96    where
97        I: IntoIterator<Item = T>,
98        T: Into<Ticker>,
99    {
100        let columns = technical_columns();
101        let decoder = RowDecoder::new(&columns);
102        let rows = self.loader().fetch_many(symbols, columns).await?;
103
104        Ok(rows
105            .iter()
106            .map(|row| decode_technical(&decoder, row))
107            .collect::<Vec<_>>())
108    }
109
110    pub async fn overview(&self, symbol: impl Into<Ticker>) -> Result<CryptoOverview> {
111        let columns = overview_columns();
112        let decoder = RowDecoder::new(&columns);
113        let row = self.loader().fetch_one(symbol, columns).await?;
114        Ok(CryptoOverview {
115            quote: decode_quote(&decoder, &row),
116            technicals: decode_technical(&decoder, &row),
117        })
118    }
119
120    pub async fn overviews<I, T>(&self, symbols: I) -> Result<Vec<CryptoOverview>>
121    where
122        I: IntoIterator<Item = T>,
123        T: Into<Ticker>,
124    {
125        let columns = overview_columns();
126        let decoder = RowDecoder::new(&columns);
127        let rows = self.loader().fetch_many(symbols, columns).await?;
128
129        Ok(rows
130            .iter()
131            .map(|row| CryptoOverview {
132                quote: decode_quote(&decoder, row),
133                technicals: decode_technical(&decoder, row),
134            })
135            .collect::<Vec<_>>())
136    }
137
138    /// Fetches the strongest crypto movers by percentage change.
139    ///
140    /// # Examples
141    ///
142    /// ```no_run
143    /// use tvdata_rs::{Result, TradingViewClient};
144    ///
145    /// #[tokio::main]
146    /// async fn main() -> Result<()> {
147    ///     let client = TradingViewClient::builder().build()?;
148    ///     let movers = client.crypto().top_gainers(10).await?;
149    ///
150    ///     println!("movers: {}", movers.len());
151    ///     Ok(())
152    /// }
153    /// ```
154    pub async fn top_gainers(&self, limit: usize) -> Result<Vec<QuoteSnapshot>> {
155        self.loader()
156            .fetch_market_quotes(
157                "crypto",
158                limit,
159                crate::scanner::fields::price::CHANGE_PERCENT.sort(SortOrder::Desc),
160            )
161            .await
162    }
163
164    pub async fn top_losers(&self, limit: usize) -> Result<Vec<QuoteSnapshot>> {
165        self.loader()
166            .fetch_market_quotes(
167                "crypto",
168                limit,
169                crate::scanner::fields::price::CHANGE_PERCENT.sort(SortOrder::Asc),
170            )
171            .await
172    }
173
174    pub async fn most_active(&self, limit: usize) -> Result<Vec<QuoteSnapshot>> {
175        self.loader()
176            .fetch_market_active_quotes(
177                "crypto",
178                limit,
179                crate::scanner::fields::price::VOLUME.sort(SortOrder::Desc),
180            )
181            .await
182    }
183
184    fn loader(&self) -> SnapshotLoader<'_> {
185        SnapshotLoader::new(
186            self.client,
187            ScanQuery::new()
188                .market("crypto")
189                .price_conversion(PriceConversion::MarketCurrency),
190        )
191    }
192}
193
194#[derive(Debug, Clone, PartialEq)]
195pub struct CryptoOverview {
196    pub quote: QuoteSnapshot,
197    pub technicals: TechnicalSummary,
198}
199
200impl TradingViewClient {
201    /// Returns the high-level crypto facade.
202    pub fn crypto(&self) -> CryptoClient<'_> {
203        CryptoClient::new(self)
204    }
205}
206
207fn overview_columns() -> Vec<Column> {
208    crate::market_data::merge_columns([quote_columns(), technical_columns()])
209}