1use crate::client::TradingViewClient;
2use crate::error::Result;
3use crate::market_data::{
4 QuoteSnapshot, RowDecoder, SnapshotLoader, TechnicalSummary, decode_quote, decode_technical,
5 identity_columns, merge_columns, technical_columns,
6};
7use crate::scanner::fields::price;
8use crate::scanner::{Column, ScanQuery, SortOrder, Ticker};
9
10#[cfg(test)]
11mod tests;
12
13#[derive(Debug, Clone, Copy)]
30pub struct ForexClient<'a> {
31 client: &'a TradingViewClient,
32}
33
34impl<'a> ForexClient<'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 = forex_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 = forex_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 technical_summary(&self, symbol: impl Into<Ticker>) -> Result<TechnicalSummary> {
69 let columns = technical_columns();
70 let decoder = RowDecoder::new(&columns);
71 let row = self.loader().fetch_one(symbol, columns).await?;
72 Ok(decode_technical(&decoder, &row))
73 }
74
75 pub async fn technical_summaries<I, T>(&self, symbols: I) -> Result<Vec<TechnicalSummary>>
76 where
77 I: IntoIterator<Item = T>,
78 T: Into<Ticker>,
79 {
80 let columns = technical_columns();
81 let decoder = RowDecoder::new(&columns);
82 let rows = self.loader().fetch_many(symbols, columns).await?;
83
84 Ok(rows
85 .iter()
86 .map(|row| decode_technical(&decoder, row))
87 .collect::<Vec<_>>())
88 }
89
90 pub async fn overview(&self, symbol: impl Into<Ticker>) -> Result<ForexOverview> {
91 let columns = overview_columns();
92 let decoder = RowDecoder::new(&columns);
93 let row = self.loader().fetch_one(symbol, columns).await?;
94 Ok(ForexOverview {
95 quote: decode_quote(&decoder, &row),
96 technicals: decode_technical(&decoder, &row),
97 })
98 }
99
100 pub async fn overviews<I, T>(&self, symbols: I) -> Result<Vec<ForexOverview>>
101 where
102 I: IntoIterator<Item = T>,
103 T: Into<Ticker>,
104 {
105 let columns = overview_columns();
106 let decoder = RowDecoder::new(&columns);
107 let rows = self.loader().fetch_many(symbols, columns).await?;
108
109 Ok(rows
110 .iter()
111 .map(|row| ForexOverview {
112 quote: decode_quote(&decoder, row),
113 technicals: decode_technical(&decoder, row),
114 })
115 .collect::<Vec<_>>())
116 }
117
118 pub async fn top_gainers(&self, limit: usize) -> Result<Vec<QuoteSnapshot>> {
119 self.loader()
120 .fetch_market_quotes_with_columns(
121 "forex",
122 limit,
123 price::CHANGE_PERCENT.sort(SortOrder::Desc),
124 forex_quote_columns(),
125 false,
126 )
127 .await
128 }
129
130 pub async fn top_losers(&self, limit: usize) -> Result<Vec<QuoteSnapshot>> {
131 self.loader()
132 .fetch_market_quotes_with_columns(
133 "forex",
134 limit,
135 price::CHANGE_PERCENT.sort(SortOrder::Asc),
136 forex_quote_columns(),
137 false,
138 )
139 .await
140 }
141
142 pub async fn most_active(&self, limit: usize) -> Result<Vec<QuoteSnapshot>> {
159 self.loader()
160 .fetch_market_quotes_with_columns(
161 "forex",
162 limit,
163 price::VOLUME.sort(SortOrder::Desc),
164 forex_quote_columns(),
165 true,
166 )
167 .await
168 }
169
170 fn loader(&self) -> SnapshotLoader<'_> {
171 SnapshotLoader::new(
172 self.client,
173 ScanQuery::new().market("forex").symbol_types(["forex"]),
174 )
175 }
176}
177
178#[derive(Debug, Clone, PartialEq)]
179pub struct ForexOverview {
180 pub quote: QuoteSnapshot,
181 pub technicals: TechnicalSummary,
182}
183
184impl TradingViewClient {
185 pub fn forex(&self) -> ForexClient<'_> {
187 ForexClient::new(self)
188 }
189}
190
191fn overview_columns() -> Vec<Column> {
192 merge_columns([forex_quote_columns(), technical_columns()])
193}
194
195fn forex_quote_columns() -> Vec<Column> {
196 merge_columns([
197 identity_columns(),
198 vec![
199 price::OPEN,
200 price::HIGH,
201 price::LOW,
202 price::CLOSE,
203 price::CHANGE_PERCENT,
204 price::CHANGE_ABS,
205 price::VOLUME,
206 price::RELATIVE_VOLUME,
207 ],
208 ])
209}