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