Skip to main content

tvdata_rs/forex/
mod.rs

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/// High-level FX 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.forex().quote("FX:EURUSD").await?;
24///
25///     println!("{:?}", quote.close);
26///     Ok(())
27/// }
28/// ```
29#[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    /// Fetches a typed FX quote snapshot for a single symbol.
44    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    /// Fetches typed FX 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 = 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    /// Fetches the most active FX instruments by TradingView volume.
143    ///
144    /// # Examples
145    ///
146    /// ```no_run
147    /// use tvdata_rs::{Result, TradingViewClient};
148    ///
149    /// #[tokio::main]
150    /// async fn main() -> Result<()> {
151    ///     let client = TradingViewClient::builder().build()?;
152    ///     let movers = client.forex().most_active(10).await?;
153    ///
154    ///     println!("movers: {}", movers.len());
155    ///     Ok(())
156    /// }
157    /// ```
158    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    /// Returns the high-level FX facade.
186    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}