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#[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 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 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 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 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}