Skip to main content

tvdata_rs/crypto/
mod.rs

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