Skip to main content

tradingview/models/
mod.rs

1//! Shared data types used across the crate.
2//!
3//! This module defines the core domain model for interacting with TradingView:
4//! market data types, symbol representations, time intervals, and search
5//! responses. Most types are `Serialize` + `Deserialize` for wire compatibility
6//! with TradingView's protocols.
7//!
8//! # Key Types
9//!
10//! | Type | Purpose |
11//! |------|---------|
12//! | [`Symbol`] | A tradable instrument (e.g. `BINANCE:BTCUSDT`) |
13//! | [`Interval`] | Time granularity for OHLCV bars |
14//! | [`SymbolType`] / [`MarketType`] | Categorization of instruments |
15//! | [`ChartOptions`] | Configuration for chart data subscriptions |
16//! | [`UserCookies`] | Authenticated session state |
17//! | [`OHLCV`] | A single OHLCV bar with timestamp |
18//!
19//! [`Symbol`]: Symbol
20//! [`Interval`]: Interval
21//! [`SymbolType`]: SymbolType
22//! [`MarketType`]: MarketType
23//! [`ChartOptions`]: crate::chart::ChartOptions
24//! [`UserCookies`]: UserCookies
25//! [`OHLCV`]: crate::chart::OHLCV
26
27pub use self::MarketType::*;
28pub use self::news::*;
29pub use crate::chart::*;
30pub use crate::quote::models::*;
31
32use chrono::Duration;
33use iso_currency::Currency;
34use serde::{Deserialize, Deserializer, Serialize};
35use std::{collections::HashMap, fmt::Display};
36pub mod news;
37pub mod pine_indicator;
38
39/// Trait for types that carry a symbol–exchange pair.
40///
41/// Provides a standard way to construct the TradingView-style identifier
42/// `"EXCHANGE:SYMBOL"` (e.g. `"BINANCE:BTCUSDT"`).
43pub trait MarketSymbol {
44    /// Create a new instance from symbol and exchange strings.
45    fn new<S: Into<String>>(symbol: S, exchange: S) -> Self;
46    /// The raw symbol/ticker (e.g. `"BTCUSDT"`).
47    fn symbol(&self) -> &str;
48    /// The exchange name (e.g. `"BINANCE"`).
49    fn exchange(&self) -> &str;
50    /// The combined identifier `"EXCHANGE:SYMBOL"`.
51    fn id(&self) -> String {
52        format!("{}:{}", self.exchange(), self.symbol())
53    }
54}
55
56impl MarketSymbol for Symbol {
57    fn symbol(&self) -> &str {
58        &self.symbol
59    }
60
61    fn exchange(&self) -> &str {
62        &self.exchange
63    }
64
65    fn id(&self) -> String {
66        Symbol::id(self)
67    }
68
69    fn new<S: Into<String>>(symbol: S, exchange: S) -> Self {
70        Self {
71            symbol: symbol.into(),
72            exchange: exchange.into(),
73            ..Default::default()
74        }
75    }
76}
77
78/// A chart drawing retrieved from TradingView (lines, shapes, annotations, etc.).
79#[derive(Debug, Clone, Default, Serialize, Deserialize)]
80pub struct ChartDrawing {
81    pub success: bool,
82    pub payload: ChartDrawingSource,
83}
84
85/// Container for chart drawing source data, keyed by drawing ID.
86#[derive(Debug, Clone, Default, Serialize, Deserialize)]
87pub struct ChartDrawingSource {
88    pub sources: HashMap<String, ChartDrawingSourceData>,
89}
90
91/// Per-drawing metadata: symbol, currency, update time, and state.
92#[derive(Debug, Clone, Default, Serialize, Deserialize)]
93#[serde(rename_all = "camelCase")]
94pub struct ChartDrawingSourceData {
95    id: String,
96    symbol: String,
97    currency_id: String,
98    server_update_time: i64,
99    state: ChartDrawingSourceState,
100}
101
102/// The geometric state of a drawing: a collection of time/price anchor points.
103#[derive(Debug, Clone, Default, Serialize, Deserialize)]
104#[serde(rename_all = "camelCase")]
105pub struct ChartDrawingSourceState {
106    points: Vec<ChartDrawingSourceStatePoint>,
107}
108
109/// A single anchor point in a chart drawing (time, offset, price).
110#[derive(Debug, Clone, Default, Serialize, Deserialize)]
111pub struct ChartDrawingSourceStatePoint {
112    time_t: i64,
113    offset: i64,
114    price: f64,
115}
116
117/// Authenticated TradingView user session state.
118///
119/// Contains the credentials needed for premium features: auth token, session
120/// hash, device token, and private channel ID. Obtain via
121/// [`UserCookies::login`].
122///
123/// Serialize to JSON for session persistence across restarts.
124#[derive(Clone, Serialize, Deserialize, Debug, Default)]
125pub struct UserCookies {
126    pub id: u32,
127    pub username: String,
128    pub private_channel: String,
129    pub auth_token: String,
130    #[serde(default)]
131    pub session: String,
132    #[serde(default)]
133    pub session_signature: String,
134    pub session_hash: String,
135    #[serde(default)]
136    pub device_token: String,
137}
138
139/// Response from the TradingView symbol search endpoint.
140///
141/// `remaining` counts how many results are left beyond the returned page.
142/// `symbols` is the current page of matches.
143#[derive(Debug, Clone, Default, Deserialize)]
144pub struct SymbolSearchResponse {
145    #[serde(rename(deserialize = "symbols_remaining"))]
146    pub remaining: u64,
147    pub symbols: Vec<Symbol>,
148}
149
150/// A tradable instrument on TradingView.
151///
152/// The canonical identifier is `"EXCHANGE:SYMBOL"` (e.g. `"NASDAQ:AAPL"`,
153/// `"BINANCE:BTCUSDT"`). Use [`Symbol::id()`] to obtain this string.
154///
155/// Construct via the builder:
156///
157/// ```rust
158/// use tradingview::Symbol;
159/// let sym = Symbol::builder()
160///     .symbol("BTCUSDT")
161///     .exchange("BINANCE")
162///     .build();
163/// ```
164#[derive(Clone, PartialEq, Deserialize, Serialize, Debug, Default, Hash)]
165pub struct Symbol {
166    pub symbol: String,
167    #[serde(default)]
168    pub description: String,
169    #[serde(default, rename(deserialize = "type"))]
170    pub market_type: String,
171    #[serde(default)]
172    pub exchange: String,
173    /// Data-feed prefix when it differs from the display exchange (e.g.
174    /// search shows `exchange: "NYSE Arca"` with `prefix: "AMEX"`; chart
175    /// requests must use `AMEX:SYMBOL`). Empty when identical.
176    #[serde(default)]
177    pub prefix: String,
178    #[serde(default)]
179    pub currency_code: String,
180    #[serde(default, rename(deserialize = "provider_id"))]
181    pub data_provider: String,
182    #[serde(default, rename(deserialize = "country"))]
183    pub country_code: String,
184    #[serde(default, rename(deserialize = "typespecs"))]
185    pub type_specs: Vec<String>,
186    #[serde(default, rename(deserialize = "source2"))]
187    pub exchange_source: ExchangeSource,
188}
189
190#[bon::bon]
191impl Symbol {
192    #[builder]
193    pub fn new<S: Into<String>>(
194        symbol: S,
195        exchange: S,
196        currency: Option<Currency>,
197        prefix: Option<S>,
198    ) -> Self {
199        Self {
200            symbol: symbol.into(),
201            exchange: exchange.into(),
202            currency_code: currency.map(|c| c.to_string()).unwrap_or_default(),
203            prefix: prefix.map(|p| p.into()).unwrap_or_default(),
204            ..Default::default()
205        }
206    }
207
208    pub fn id(&self) -> String {
209        let prefix = if !self.prefix.is_empty() {
210            &self.prefix
211        } else {
212            &self.exchange
213        };
214        format!("{}:{}", prefix, self.symbol)
215    }
216}
217
218/// Metadata about an exchange / data source.
219///
220/// Returned as part of [`Symbol`] search results to identify the originating
221/// market data provider.
222#[derive(Clone, PartialEq, Deserialize, Serialize, Debug, Default, Hash)]
223pub struct ExchangeSource {
224    pub id: String,
225    pub name: String,
226    pub description: String,
227}
228
229/// Which part of the trading session to request.
230#[derive(Debug, Default, Clone, Deserialize, Serialize, Copy, PartialEq, Eq, Hash)]
231pub enum SessionType {
232    /// Standard market hours.
233    #[default]
234    Regular,
235    /// Extended-hours trading.
236    Extended,
237    /// Pre-market session.
238    PreMarket,
239    /// Post-market / after-hours session.
240    PostMarket,
241}
242
243impl Display for SessionType {
244    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245        match self {
246            SessionType::Regular => write!(f, "regular"),
247            SessionType::Extended => write!(f, "extended"),
248            SessionType::PreMarket => write!(f, "premarket"),
249            SessionType::PostMarket => write!(f, "postmarket"),
250        }
251    }
252}
253
254/// Data adjustment applied to historical OHLCV bars.
255///
256/// When `Splits`, prices are adjusted for stock splits. When `Dividends`,
257/// prices are adjusted for dividend payments.
258#[derive(Debug, Default, Clone, Deserialize, Serialize, Copy)]
259pub enum MarketAdjustment {
260    /// Adjust for stock splits.
261    #[default]
262    Splits,
263    /// Adjust for dividend payments.
264    Dividends,
265}
266
267impl Display for MarketAdjustment {
268    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269        match self {
270            MarketAdjustment::Splits => write!(f, "splits"),
271            MarketAdjustment::Dividends => write!(f, "dividends"),
272        }
273    }
274}
275
276/// Current market session status as reported by TradingView.
277#[derive(Debug, Default, Clone, Deserialize, Serialize, Copy)]
278pub enum MarketStatus {
279    /// Market is closed for a holiday.
280    Holiday,
281    /// Regular market hours — market is open.
282    #[default]
283    Open,
284    /// Market is closed (out of session).
285    Close,
286    /// Post-market / after-hours trading.
287    Post,
288    /// Pre-market trading.
289    Pre,
290}
291
292impl Display for MarketStatus {
293    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
294        match self {
295            MarketStatus::Holiday => write!(f, "holiday"),
296            MarketStatus::Open => write!(f, "market"),
297            MarketStatus::Close => write!(f, "out_of_session"),
298            MarketStatus::Post => write!(f, "post_market"),
299            MarketStatus::Pre => write!(f, "pre_market"),
300        }
301    }
302}
303
304/// IANA timezone identifier for exchange trading schedules.
305///
306/// Maps to TradingView's timezone format. Default is [`EtcUTC`](Timezone::EtcUTC).
307#[derive(Debug, Default, Clone, Deserialize, Serialize, Copy, PartialEq, Eq, Hash)]
308pub enum Timezone {
309    /// Africa/Cairo
310    AfricaCairo,
311    AfricaCasablanca,
312    AfricaJohannesburg,
313    AfricaLagos,
314    AfricaNairobi,
315    AfricaTunis,
316    AmericaAnchorage,
317    AmericaArgentinaBuenosAires,
318    AmericaBogota,
319    AmericaCaracas,
320    AmericaChicago,
321    AmericaElSalvador,
322    AmericaJuneau,
323    AmericaLima,
324    AmericaLosAngeles,
325    AmericaMexicoCity,
326    AmericaNewYork,
327    AmericaPhoenix,
328    AmericaSantiago,
329    AmericaSaoPaulo,
330    AmericaToronto,
331    AmericaVancouver,
332    AsiaAlmaty,
333    AsiaAshkhabad,
334    AsiaBahrain,
335    AsiaBangkok,
336    AsiaChongqing,
337    AsiaColombo,
338    AsiaDhaka,
339    AsiaDubai,
340    AsiaHoChiMinh,
341    AsiaHongKong,
342    AsiaJakarta,
343    AsiaJerusalem,
344    AsiaKarachi,
345    AsiaKathmandu,
346    AsiaKolkata,
347    AsiaKuwait,
348    AsiaManila,
349    AsiaMuscat,
350    AsiaNicosia,
351    AsiaQatar,
352    AsiaRiyadh,
353    AsiaSeoul,
354    AsiaShanghai,
355    AsiaSingapore,
356    AsiaTaipei,
357    AsiaTehran,
358    AsiaTokyo,
359    AsiaYangon,
360    AtlanticReykjavik,
361    AustraliaAdelaide,
362    AustraliaBrisbane,
363    AustraliaPerth,
364    AustraliaSydney,
365    EuropeAmsterdam,
366    EuropeAthens,
367    EuropeBelgrade,
368    EuropeBerlin,
369    EuropeBratislava,
370    EuropeBrussels,
371    EuropeBucharest,
372    EuropeBudapest,
373    EuropeCopenhagen,
374    EuropeDublin,
375    EuropeHelsinki,
376    EuropeIstanbul,
377    EuropeLisbon,
378    EuropeLondon,
379    EuropeLuxembourg,
380    EuropeMadrid,
381    EuropeMalta,
382    EuropeMoscow,
383    EuropeOslo,
384    EuropeParis,
385    EuropeRiga,
386    EuropeRome,
387    EuropeStockholm,
388    EuropeTallinn,
389    EuropeVilnius,
390    EuropeWarsaw,
391    EuropeZurich,
392    PacificAuckland,
393    PacificChatham,
394    PacificFakaofo,
395    PacificHonolulu,
396    PacificNorfolk,
397    USMountain,
398    #[default]
399    EtcUTC,
400}
401
402impl Display for Timezone {
403    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
404        match self {
405            Timezone::AfricaCairo => write!(f, "Africa/Cairo"),
406            Timezone::AfricaCasablanca => write!(f, "Africa/Casablanca"),
407            Timezone::AfricaJohannesburg => write!(f, "Africa/Johannesburg"),
408            Timezone::AfricaLagos => write!(f, "Africa/Lagos"),
409            Timezone::AfricaNairobi => write!(f, "Africa/Nairobi"),
410            Timezone::AfricaTunis => write!(f, "Africa/Tunis"),
411            Timezone::AmericaAnchorage => write!(f, "America/Anchorage"),
412            Timezone::AmericaArgentinaBuenosAires => write!(f, "America/Argentina/Buenos_Aires"),
413            Timezone::AmericaBogota => write!(f, "America/Bogota"),
414            Timezone::AmericaCaracas => write!(f, "America/Caracas"),
415            Timezone::AmericaChicago => write!(f, "America/Chicago"),
416            Timezone::AmericaElSalvador => write!(f, "America/El_Salvador"),
417            Timezone::AmericaJuneau => write!(f, "America/Juneau"),
418            Timezone::AmericaLima => write!(f, "America/Lima"),
419            Timezone::AmericaLosAngeles => write!(f, "America/Los_Angeles"),
420            Timezone::AmericaMexicoCity => write!(f, "America/Mexico_City"),
421            Timezone::AmericaNewYork => write!(f, "America/New_York"),
422            Timezone::AmericaPhoenix => write!(f, "America/Phoenix"),
423            Timezone::AmericaSantiago => write!(f, "America/Santiago"),
424            Timezone::AmericaSaoPaulo => write!(f, "America/Sao_Paulo"),
425            Timezone::AmericaToronto => write!(f, "America/Toronto"),
426            Timezone::AmericaVancouver => write!(f, "America/Vancouver"),
427            Timezone::AsiaAlmaty => write!(f, "Asia/Almaty"),
428            Timezone::AsiaAshkhabad => write!(f, "Asia/Ashkhabad"),
429            Timezone::AsiaBahrain => write!(f, "Asia/Bahrain"),
430            Timezone::AsiaBangkok => write!(f, "Asia/Bangkok"),
431            Timezone::AsiaChongqing => write!(f, "Asia/Chongqing"),
432            Timezone::AsiaColombo => write!(f, "Asia/Colombo"),
433            Timezone::AsiaDhaka => write!(f, "Asia/Dhaka"),
434            Timezone::AsiaDubai => write!(f, "Asia/Dubai"),
435            Timezone::AsiaHoChiMinh => write!(f, "Asia/Ho_Chi_Minh"),
436            Timezone::AsiaHongKong => write!(f, "Asia/Hong_Kong"),
437            Timezone::AsiaJakarta => write!(f, "Asia/Jakarta"),
438            Timezone::AsiaJerusalem => write!(f, "Asia/Jerusalem"),
439            Timezone::AsiaKarachi => write!(f, "Asia/Karachi"),
440            Timezone::AsiaKathmandu => write!(f, "Asia/Kathmandu"),
441            Timezone::AsiaKolkata => write!(f, "Asia/Kolkata"),
442            Timezone::AsiaKuwait => write!(f, "Asia/Kuwait"),
443            Timezone::AsiaManila => write!(f, "Asia/Manila"),
444            Timezone::AsiaMuscat => write!(f, "Asia/Muscat"),
445            Timezone::AsiaNicosia => write!(f, "Asia/Nicosia"),
446            Timezone::AsiaQatar => write!(f, "Asia/Qatar"),
447            Timezone::AsiaRiyadh => write!(f, "Asia/Riyadh"),
448            Timezone::AsiaSeoul => write!(f, "Asia/Seoul"),
449            Timezone::AsiaShanghai => write!(f, "Asia/Shanghai"),
450            Timezone::AsiaSingapore => write!(f, "Asia/Singapore"),
451            Timezone::AsiaTaipei => write!(f, "Asia/Taipei"),
452            Timezone::AsiaTehran => write!(f, "Asia/Tehran"),
453            Timezone::AsiaTokyo => write!(f, "Asia/Tokyo"),
454            Timezone::AsiaYangon => write!(f, "Asia/Yangon"),
455            Timezone::AtlanticReykjavik => write!(f, "Atlantic/Reykjavik"),
456            Timezone::AustraliaAdelaide => write!(f, "Australia/Adelaide"),
457            Timezone::AustraliaBrisbane => write!(f, "Australia/Brisbane"),
458            Timezone::AustraliaPerth => write!(f, "Australia/Perth"),
459            Timezone::AustraliaSydney => write!(f, "Australia/Sydney"),
460            Timezone::EuropeAmsterdam => write!(f, "Europe/Amsterdam"),
461            Timezone::EuropeAthens => write!(f, "Europe/Athens"),
462            Timezone::EuropeBelgrade => write!(f, "Europe/Belgrade"),
463            Timezone::EuropeBerlin => write!(f, "Europe/Berlin"),
464            Timezone::EuropeBratislava => write!(f, "Europe/Bratislava"),
465            Timezone::EuropeBrussels => write!(f, "Europe/Brussels"),
466            Timezone::EuropeBucharest => write!(f, "Europe/Bucharest"),
467            Timezone::EuropeBudapest => write!(f, "Europe/Budapest"),
468            Timezone::EuropeCopenhagen => write!(f, "Europe/Copenhagen"),
469            Timezone::EuropeDublin => write!(f, "Europe/Dublin"),
470            Timezone::EuropeHelsinki => write!(f, "Europe/Helsinki"),
471            Timezone::EuropeIstanbul => write!(f, "Europe/Istanbul"),
472            Timezone::EuropeLisbon => write!(f, "Europe/Lisbon"),
473            Timezone::EuropeLondon => write!(f, "Europe/London"),
474            Timezone::EuropeLuxembourg => write!(f, "Europe/Luxembourg"),
475            Timezone::EuropeMadrid => write!(f, "Europe/Madrid"),
476            Timezone::EuropeMalta => write!(f, "Europe/Malta"),
477            Timezone::EuropeMoscow => write!(f, "Europe/Moscow"),
478            Timezone::EuropeOslo => write!(f, "Europe/Oslo"),
479            Timezone::EuropeParis => write!(f, "Europe/Paris"),
480            Timezone::EuropeRiga => write!(f, "Europe/Riga"),
481            Timezone::EuropeRome => write!(f, "Europe/Rome"),
482            Timezone::EuropeStockholm => write!(f, "Europe/Stockholm"),
483            Timezone::EuropeTallinn => write!(f, "Europe/Tallinn"),
484            Timezone::EuropeVilnius => write!(f, "Europe/Vilnius"),
485            Timezone::EuropeWarsaw => write!(f, "Europe/Warsaw"),
486            Timezone::EuropeZurich => write!(f, "Europe/Zurich"),
487            Timezone::PacificAuckland => write!(f, "Pacific/Auckland"),
488            Timezone::PacificChatham => write!(f, "Pacific/Chatham"),
489            Timezone::PacificFakaofo => write!(f, "Pacific/Fakaofo"),
490            Timezone::PacificHonolulu => write!(f, "Pacific/Honolulu"),
491            Timezone::PacificNorfolk => write!(f, "Pacific/Norfolk"),
492            Timezone::USMountain => write!(f, "US/Mountain"),
493            Timezone::EtcUTC => write!(f, "Etc/UTC"),
494        }
495    }
496}
497
498/// Time interval (granularity) for OHLCV/candle bars.
499///
500/// This is one of the most-used types in the crate. Every historical and
501/// real-time data request specifies an interval. The default is `OneDay`.
502///
503/// # Conversion
504///
505/// - `From<&str>` parses common string representations (`"1h"`, `"1D"`, `"1W"`, etc.).
506/// - `From<u8>` maps TradingView's numeric interval codes.
507/// - `From<Interval> for chrono::Duration` provides an approximate duration.
508/// - [`Display`] outputs the TradingView wire format.
509///
510/// # Navigation
511///
512/// [`Interval::longer()`] steps up to the next coarser interval.
513///
514/// [`Interval::longer()`]: Interval::longer()
515#[derive(Debug, Default, Clone, Deserialize, Serialize, Copy, PartialEq, Eq, Hash)]
516pub enum Interval {
517    /// 1 second.
518    OneSecond = 0,
519    /// 5 seconds.
520    FiveSeconds = 1,
521    /// 10 seconds.
522    TenSeconds = 2,
523    /// 15 seconds.
524    FifteenSeconds = 3,
525    /// 30 seconds.
526    ThirtySeconds = 4,
527    /// 1 minute.
528    OneMinute = 5,
529    /// 3 minutes.
530    ThreeMinutes = 6,
531    /// 5 minutes.
532    FiveMinutes = 7,
533    /// 15 minutes.
534    FifteenMinutes = 8,
535    /// 30 minutes.
536    ThirtyMinutes = 9,
537    /// 45 minutes.
538    FortyFiveMinutes = 10,
539    /// 1 hour.
540    OneHour = 11,
541    /// 2 hours.
542    TwoHours = 12,
543    /// 4 hours.
544    FourHours = 13,
545    /// 1 day (default).
546    #[default]
547    OneDay = 14,
548    /// 1 week.
549    OneWeek = 15,
550    /// 1 month.
551    OneMonth = 16,
552    /// 1 quarter (~3 months).
553    OneQuarter = 17,
554    /// 6 months.
555    SixMonths = 18,
556    /// 1 year.
557    Yearly = 19,
558}
559
560impl Interval {
561    pub fn longer(self) -> Interval {
562        match self {
563            Interval::OneSecond => Interval::FiveSeconds,
564            Interval::FiveSeconds => Interval::TenSeconds,
565            Interval::TenSeconds => Interval::FifteenSeconds,
566            Interval::FifteenSeconds => Interval::ThirtySeconds,
567            Interval::ThirtySeconds => Interval::OneMinute,
568            Interval::OneMinute => Interval::ThreeMinutes,
569            Interval::ThreeMinutes => Interval::FiveMinutes,
570            Interval::FiveMinutes => Interval::FifteenMinutes,
571            Interval::FifteenMinutes => Interval::ThirtyMinutes,
572            Interval::ThirtyMinutes => Interval::FortyFiveMinutes,
573            Interval::FortyFiveMinutes => Interval::OneHour,
574            Interval::OneHour => Interval::TwoHours,
575            Interval::TwoHours => Interval::FourHours,
576            Interval::FourHours => Interval::OneDay,
577            Interval::OneDay => Interval::OneWeek,
578            Interval::OneWeek => Interval::OneMonth,
579            Interval::OneMonth => Interval::OneQuarter,
580            Interval::OneQuarter => Interval::SixMonths,
581            _ => self, // Yearly remains the same
582        }
583    }
584}
585
586impl From<u8> for Interval {
587    fn from(value: u8) -> Self {
588        match value {
589            0 => Interval::OneSecond,
590            1 => Interval::FiveSeconds,
591            2 => Interval::TenSeconds,
592            3 => Interval::FifteenSeconds,
593            4 => Interval::ThirtySeconds,
594            5 => Interval::OneMinute,
595            6 => Interval::ThreeMinutes,
596            7 => Interval::FiveMinutes,
597            8 => Interval::FifteenMinutes,
598            9 => Interval::ThirtyMinutes,
599            10 => Interval::FortyFiveMinutes,
600            11 => Interval::OneHour,
601            12 => Interval::TwoHours,
602            13 => Interval::FourHours,
603            14 => Interval::OneDay,
604            15 => Interval::OneWeek,
605            16 => Interval::OneMonth,
606            17 => Interval::OneQuarter,
607            18 => Interval::SixMonths,
608            _ => Interval::Yearly, // Default to Yearly for any other value
609        }
610    }
611}
612
613impl From<Interval> for Duration {
614    fn from(interval: Interval) -> Self {
615        match interval {
616            Interval::OneSecond => Duration::seconds(1),
617            Interval::FiveSeconds => Duration::seconds(5),
618            Interval::TenSeconds => Duration::seconds(10),
619            Interval::FifteenSeconds => Duration::seconds(15),
620            Interval::ThirtySeconds => Duration::seconds(30),
621            Interval::OneMinute => Duration::minutes(1),
622            Interval::ThreeMinutes => Duration::minutes(3),
623            Interval::FiveMinutes => Duration::minutes(5),
624            Interval::FifteenMinutes => Duration::minutes(15),
625            Interval::ThirtyMinutes => Duration::minutes(30),
626            Interval::FortyFiveMinutes => Duration::minutes(45),
627            Interval::OneHour => Duration::hours(1),
628            Interval::TwoHours => Duration::hours(2),
629            Interval::FourHours => Duration::hours(4),
630            Interval::OneDay => Duration::days(1),
631            Interval::OneWeek => Duration::weeks(1),
632            Interval::OneMonth => Duration::days(30), // Approximation
633            Interval::OneQuarter => Duration::days(90), // Approximation
634            Interval::SixMonths => Duration::days(180), // Approximation
635            Interval::Yearly => Duration::days(365),  // Approximation
636        }
637    }
638}
639
640impl From<&str> for Interval {
641    fn from(value: &str) -> Self {
642        match value {
643            "1s" => Interval::OneSecond,
644            "5s" => Interval::FiveSeconds,
645            "10s" => Interval::TenSeconds,
646            "15s" => Interval::FifteenSeconds,
647            "30s" => Interval::ThirtySeconds,
648            "1m" => Interval::OneMinute,
649            "3m" => Interval::ThreeMinutes,
650            "5m" => Interval::FiveMinutes,
651            "15m" => Interval::FifteenMinutes,
652            "30m" => Interval::ThirtyMinutes,
653            "45m" => Interval::FortyFiveMinutes,
654            "1h" => Interval::OneHour,
655            "2h" => Interval::TwoHours,
656            "4h" => Interval::FourHours,
657            "1d" => Interval::OneDay,
658            "7d" => Interval::OneWeek,
659            "30d" => Interval::OneMonth,
660            "120d" => Interval::OneQuarter,
661            "180d" => Interval::SixMonths,
662            "1y" => Interval::Yearly,
663            _ => Interval::OneDay,
664        }
665    }
666}
667
668impl Display for Interval {
669    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
670        let time_interval = match self {
671            Interval::OneSecond => "1S",
672            Interval::FiveSeconds => "5S",
673            Interval::TenSeconds => "10S",
674            Interval::FifteenSeconds => "15S",
675            Interval::ThirtySeconds => "30S",
676            Interval::OneMinute => "1",
677            Interval::ThreeMinutes => "3",
678            Interval::FiveMinutes => "5",
679            Interval::FifteenMinutes => "15",
680            Interval::ThirtyMinutes => "30",
681            Interval::FortyFiveMinutes => "45",
682            Interval::OneHour => "1H",
683            Interval::TwoHours => "2H",
684            Interval::FourHours => "4H",
685            Interval::OneDay => "1D",
686            Interval::OneWeek => "1W",
687            Interval::OneMonth => "1M",
688            Interval::OneQuarter => "3M",
689            Interval::SixMonths => "6M",
690            Interval::Yearly => "12M",
691        };
692        write!(f, "{time_interval}")
693    }
694}
695
696/// Supported UI language for TradingView responses (news, descriptions, etc.).
697///
698/// Default is [`English`](LanguageCode::English).
699#[derive(Debug, Default, Clone, Deserialize, Serialize, Copy)]
700pub enum LanguageCode {
701    /// Arabic.
702    Arabic,
703    /// Chinese (Simplified).
704    Chinese,
705    /// Czech.
706    Czech,
707    /// Danish.
708    Danish,
709    /// Catalan.
710    Catalan,
711    /// Dutch.
712    Dutch,
713    /// English (default).
714    #[default]
715    English,
716    /// Estonian.
717    Estonian,
718    /// French.
719    French,
720    /// German.
721    German,
722    /// Greek.
723    Greek,
724    /// Hebrew.
725    Hebrew,
726    /// Hungarian.
727    Hungarian,
728    /// Indonesian.
729    Indonesian,
730    /// Italian.
731    Italian,
732    /// Japanese.
733    Japanese,
734    /// Korean.
735    Korean,
736    /// Persian (Farsi).
737    Persian,
738    /// Polish.
739    Polish,
740    /// Portuguese.
741    Portuguese,
742    /// Romanian.
743    Romanian,
744    /// Russian.
745    Russian,
746    /// Slovak.
747    Slovak,
748    /// Spanish.
749    Spanish,
750    /// Swedish.
751    Swedish,
752    /// Thai.
753    Thai,
754    /// Turkish.
755    Turkish,
756    /// Vietnamese.
757    Vietnamese,
758    /// Norwegian.
759    Norwegian,
760    /// Malay.
761    Malay,
762    /// Chinese (Traditional).
763    TraditionalChinese,
764}
765
766impl Display for LanguageCode {
767    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
768        match *self {
769            LanguageCode::Arabic => write!(f, "ar"),
770            LanguageCode::Chinese => write!(f, "zh"),
771            LanguageCode::Czech => write!(f, "cs"),
772            LanguageCode::Danish => write!(f, "da_DK"),
773            LanguageCode::Catalan => write!(f, "ca_ES"),
774            LanguageCode::Dutch => write!(f, "nl_NL"),
775            LanguageCode::English => write!(f, "en"),
776            LanguageCode::Estonian => write!(f, "et_EE"),
777            LanguageCode::French => write!(f, "fr"),
778            LanguageCode::German => write!(f, "de"),
779            LanguageCode::Greek => write!(f, "el"),
780            LanguageCode::Hebrew => write!(f, "he_IL"),
781            LanguageCode::Hungarian => write!(f, "hu_HU"),
782            LanguageCode::Indonesian => write!(f, "id_ID"),
783            LanguageCode::Italian => write!(f, "it"),
784            LanguageCode::Japanese => write!(f, "ja"),
785            LanguageCode::Korean => write!(f, "ko"),
786            LanguageCode::Persian => write!(f, "fa"),
787            LanguageCode::Polish => write!(f, "pl"),
788            LanguageCode::Portuguese => write!(f, "pt"),
789            LanguageCode::Romanian => write!(f, "ro"),
790            LanguageCode::Russian => write!(f, "ru"),
791            LanguageCode::Slovak => write!(f, "sk_SK"),
792            LanguageCode::Spanish => write!(f, "es"),
793            LanguageCode::Swedish => write!(f, "sv"),
794            LanguageCode::Thai => write!(f, "th"),
795            LanguageCode::Turkish => write!(f, "tr"),
796            LanguageCode::Vietnamese => write!(f, "vi"),
797            LanguageCode::Norwegian => write!(f, "no"),
798            LanguageCode::Malay => write!(f, "ms_MY"),
799            LanguageCode::TraditionalChinese => write!(f, "zh_TW"),
800        }
801    }
802}
803
804/// Financial reporting period for fundamental data.
805///
806/// Serialized as an untagged string (`"FY"`, `"FQ"`, `"FH"`, `"TTM"`, or any
807/// other custom period string).
808#[derive(Debug, Clone, PartialEq, Serialize)]
809#[serde(untagged)]
810pub enum FinancialPeriod {
811    /// Fiscal year.
812    FiscalYear,
813    /// Fiscal quarter.
814    FiscalQuarter,
815    /// Fiscal half-year.
816    FiscalHalfYear,
817    /// Trailing twelve months.
818    TrailingTwelveMonths,
819    /// Catch-all for unrecognized period strings.
820    UnknownPeriod(String),
821}
822
823impl<'de> Deserialize<'de> for FinancialPeriod {
824    fn deserialize<D>(deserializer: D) -> Result<FinancialPeriod, D::Error>
825    where
826        D: Deserializer<'de>,
827    {
828        let s: String = Deserialize::deserialize(deserializer)?;
829        match s.as_str() {
830            "FY" => Ok(FinancialPeriod::FiscalYear),
831            "FQ" => Ok(FinancialPeriod::FiscalQuarter),
832            "FH" => Ok(FinancialPeriod::FiscalHalfYear),
833            "TTM" => Ok(FinancialPeriod::TrailingTwelveMonths),
834            _ => Ok(FinancialPeriod::UnknownPeriod(s)),
835        }
836    }
837}
838
839impl Display for FinancialPeriod {
840    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
841        match *self {
842            FinancialPeriod::FiscalYear => write!(f, "FY"),
843            FinancialPeriod::FiscalQuarter => write!(f, "FQ"),
844            FinancialPeriod::FiscalHalfYear => write!(f, "FH"),
845            FinancialPeriod::TrailingTwelveMonths => write!(f, "TTM"),
846            FinancialPeriod::UnknownPeriod(ref s) => write!(f, "{s}"),
847        }
848    }
849}
850
851/// Broad instrument type classification.
852///
853/// Used in symbol search filtering and displayed in TradingView's symbol info.
854/// Default is [`Stock`](SymbolType::Stock).
855#[derive(Debug, Default, Clone, Deserialize, Serialize, Copy, PartialEq, Eq, Hash)]
856pub enum SymbolType {
857    /// Common or preferred stock.
858    #[default]
859    Stock,
860    /// Market index.
861    Index,
862    /// Forex / currency pair.
863    Forex,
864    /// Futures contract.
865    Futures,
866    /// Bitcoin-denominated instrument.
867    Bitcoin,
868    /// Cryptocurrency.
869    Crypto,
870    /// Unclassified / undefined.
871    Undefined,
872    /// Pine Script expression.
873    Expression,
874    /// Spread instrument.
875    Spread,
876    /// Contract for difference.
877    Cfd,
878    /// Economic indicator.
879    Economic,
880    /// Equity.
881    Equity,
882    /// Depository receipt (ADR, GDR).
883    Dr,
884    /// Bond.
885    Bond,
886    /// Rights offering.
887    Right,
888    /// Warrant.
889    Warrant,
890    /// Fund (ETF, mutual fund, REIT).
891    Fund,
892    /// Structured product.
893    Structured,
894    /// Commodity.
895    Commodity,
896    /// Fundamental data.
897    Fundamental,
898    /// Spot market instrument.
899    Spot,
900}
901
902impl Display for SymbolType {
903    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
904        match *self {
905            SymbolType::Stock => write!(f, "stock"),
906            SymbolType::Index => write!(f, "index"),
907            SymbolType::Forex => write!(f, "forex"),
908            SymbolType::Futures => write!(f, "futures"),
909            SymbolType::Bitcoin => write!(f, "bitcoin"),
910            SymbolType::Crypto => write!(f, "crypto"),
911            SymbolType::Undefined => write!(f, "undefined"),
912            SymbolType::Expression => write!(f, "expression"),
913            SymbolType::Spread => write!(f, "spread"),
914            SymbolType::Cfd => write!(f, "cfd"),
915            SymbolType::Economic => write!(f, "economic"),
916            SymbolType::Equity => write!(f, "equity"),
917            SymbolType::Dr => write!(f, "dr"),
918            SymbolType::Bond => write!(f, "bond"),
919            SymbolType::Right => write!(f, "right"),
920            SymbolType::Warrant => write!(f, "warrant"),
921            SymbolType::Fund => write!(f, "fund"),
922            SymbolType::Structured => write!(f, "structured"),
923            SymbolType::Commodity => write!(f, "commodity"),
924            SymbolType::Fundamental => write!(f, "fundamental"),
925            SymbolType::Spot => write!(f, "spot"),
926        }
927    }
928}
929
930/// Market category for filtering symbol search results.
931///
932/// The `Stocks`, `Crypto`, and `Funds` variants each carry a sub-type for
933/// finer-grained filtering. Default is [`All`].
934#[derive(Debug, Default, Clone, Deserialize, Serialize, Copy, PartialEq, Eq, Hash)]
935pub enum MarketType {
936    /// All markets (no filter).
937    #[default]
938    All,
939    /// Stocks, with optional sub-type.
940    Stocks(StocksType),
941    /// Funds (ETF, mutual fund, REIT, trust), with optional sub-type.
942    Funds(FundsType),
943    /// Futures contracts.
944    Futures,
945    /// Forex / currencies.
946    Forex,
947    /// Cryptocurrencies, with optional sub-type.
948    Crypto(CryptoType),
949    /// Market indices.
950    Indices,
951    /// Bonds.
952    Bonds,
953    /// Economic indicators / data.
954    Economy,
955}
956
957/// Stock sub-type for use with [`MarketType::Stocks`].
958#[derive(Debug, Default, Clone, Deserialize, Serialize, Copy, PartialEq, Eq, Hash)]
959pub enum StocksType {
960    /// All stock types.
961    #[default]
962    All,
963    /// Common stock.
964    Common,
965    /// Preferred stock.
966    Preferred,
967    /// Depository receipt (ADR, GDR).
968    DepositoryReceipt,
969    /// Warrant.
970    Warrant,
971}
972
973/// Crypto sub-type for use with [`MarketType::Crypto`].
974#[derive(Debug, Default, Clone, Deserialize, Serialize, Copy, PartialEq, Eq, Hash)]
975pub enum CryptoType {
976    /// All crypto types.
977    #[default]
978    All,
979    /// Spot market.
980    Spot,
981    /// Futures / perpetual contracts.
982    Futures,
983    /// Swap contracts.
984    Swap,
985    /// Crypto index.
986    Index,
987    /// Fundamental crypto data.
988    Fundamental,
989}
990
991/// Fund sub-type for use with [`MarketType::Funds`].
992#[derive(Debug, Default, Clone, Deserialize, Serialize, Copy, PartialEq, Eq, Hash)]
993pub enum FundsType {
994    /// All fund types.
995    #[default]
996    All,
997    /// Exchange-Traded Fund.
998    ETF,
999    /// Mutual fund.
1000    MutualFund,
1001    /// Trust.
1002    Trust,
1003    /// Real Estate Investment Trust.
1004    REIT,
1005}
1006
1007/// Centralization level for crypto markets.
1008///
1009/// Controls whether to search centralized or decentralized exchanges.
1010/// Default is [`CEX`](CryptoCentralization::CEX).
1011#[derive(Debug, Default, Clone, Deserialize, Serialize, Copy, PartialEq, Eq, Hash)]
1012pub enum CryptoCentralization {
1013    /// Centralized exchange (e.g. Binance, Coinbase).
1014    #[default]
1015    CEX,
1016    /// Decentralized exchange (e.g. Uniswap, PancakeSwap).
1017    DEX,
1018}
1019
1020impl From<&str> for MarketType {
1021    fn from(value: &str) -> Self {
1022        match value {
1023            "all" | "undefined" => All,
1024            "stock" => Stocks(StocksType::All),
1025            "common_stock" => Stocks(StocksType::Common),
1026            "preferred_stock" => Stocks(StocksType::Preferred),
1027            "depository_receipt" => Stocks(StocksType::DepositoryReceipt),
1028            "warrant" => Stocks(StocksType::Warrant),
1029            "fund" => Funds(FundsType::All),
1030            "etf" => Funds(FundsType::ETF),
1031            "mutual_fund" => Funds(FundsType::MutualFund),
1032            "trust_fund" => Funds(FundsType::Trust),
1033            "reit" => Funds(FundsType::REIT),
1034            "futures" => Futures,
1035            "forex" => Forex,
1036            "crypto" => Crypto(CryptoType::All),
1037            "crypto_spot" => Crypto(CryptoType::Spot),
1038            "crypto_futures" => Crypto(CryptoType::Futures),
1039            "crypto_swap" => Crypto(CryptoType::Swap),
1040            "crypto_index" => Crypto(CryptoType::Index),
1041            "crypto_fundamental" => Crypto(CryptoType::Fundamental),
1042            "index" => Indices,
1043            "bond" => Bonds,
1044            "economic" => Economy,
1045            _ => All,
1046        }
1047    }
1048}
1049
1050impl Display for MarketType {
1051    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1052        match *self {
1053            All => write!(f, "undefined"),
1054            Stocks(t) => match t {
1055                StocksType::All => write!(f, "stocks"),
1056                StocksType::Common => write!(f, "common_stock"),
1057                StocksType::Preferred => write!(f, "preferred_stock"),
1058                StocksType::DepositoryReceipt => write!(f, "depository_receipt"),
1059                StocksType::Warrant => write!(f, "warrant"),
1060            },
1061            Funds(t) => match t {
1062                FundsType::All => write!(f, "funds"),
1063                FundsType::ETF => write!(f, "etf"),
1064                FundsType::MutualFund => write!(f, "mutual_fund"),
1065                FundsType::Trust => write!(f, "trust_fund"),
1066                FundsType::REIT => write!(f, "reit"),
1067            },
1068            Futures => write!(f, "futures"),
1069            Forex => write!(f, "forex"),
1070            Crypto(t) => match t {
1071                CryptoType::All => write!(f, "crypto"),
1072                CryptoType::Spot => write!(f, "crypto_spot"),
1073                CryptoType::Futures => write!(f, "crypto_futures"),
1074                CryptoType::Swap => write!(f, "crypto_swap"),
1075                CryptoType::Index => write!(f, "crypto_index"),
1076                CryptoType::Fundamental => write!(f, "crypto_fundamental"),
1077            },
1078            Indices => write!(f, "index"),
1079            Bonds => write!(f, "bond"),
1080            Economy => write!(f, "economic"),
1081        }
1082    }
1083}
1084
1085impl Display for CryptoCentralization {
1086    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1087        match *self {
1088            CryptoCentralization::CEX => write!(f, "cex"),
1089            CryptoCentralization::DEX => write!(f, "dex"),
1090        }
1091    }
1092}
1093
1094/// Futures product category for filtering futures symbol search.
1095#[derive(Debug, Default, Clone, Deserialize, Serialize, Copy)]
1096pub enum FuturesProductType {
1097    /// Single-stock futures.
1098    SingleStock,
1099    /// World index futures.
1100    WorldIndices,
1101    /// Currency futures (default).
1102    #[default]
1103    Currencies,
1104    /// Interest rate futures.
1105    InterestRates,
1106    /// Energy futures.
1107    Energy,
1108    /// Agricultural futures.
1109    Agriculture,
1110    /// Metals futures.
1111    Metals,
1112    /// Weather derivatives.
1113    Weather,
1114}
1115
1116impl Display for FuturesProductType {
1117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1118        match *self {
1119            FuturesProductType::SingleStock => write!(f, "Financial%2FEquity"),
1120            FuturesProductType::WorldIndices => write!(f, "Financial%2FIndex"),
1121            FuturesProductType::Currencies => write!(f, "Financial%2FCurrency"),
1122            FuturesProductType::InterestRates => write!(f, "=Financial%2FInterestRate"),
1123            FuturesProductType::Energy => write!(f, "Financial%2FEnergy"),
1124            FuturesProductType::Agriculture => write!(f, "Financial%2FAgriculture"),
1125            FuturesProductType::Metals => write!(f, "Financial%2FMetals"),
1126            FuturesProductType::Weather => write!(f, "Financial%2FWeather"),
1127        }
1128    }
1129}
1130
1131/// Stock market sector classification.
1132///
1133/// Used in symbol search filtering for equity instruments.
1134/// Default is [`Finance`](StockSector::Finance).
1135#[derive(Debug, Default, Clone, Deserialize, Serialize, Copy)]
1136pub enum StockSector {
1137    /// Commercial services.
1138    CommercialServices,
1139    /// Communications.
1140    Communications,
1141    /// Consumer durables.
1142    ConsumerDurables,
1143    /// Consumer non-durables.
1144    ConsumerNonDurables,
1145    /// Consumer services.
1146    ConsumerServices,
1147    /// Distribution services.
1148    DistributionServices,
1149    /// Electronic technology.
1150    ElectronicTechnology,
1151    /// Energy minerals.
1152    EnergyMinerals,
1153    /// Finance (default).
1154    #[default]
1155    Finance,
1156    /// Government.
1157    Government,
1158    /// Health services.
1159    HealthServices,
1160    /// Health technology.
1161    HealthTechnology,
1162    /// Industrial services.
1163    IndustrialServices,
1164    /// Miscellaneous.
1165    Miscellaneous,
1166    /// Non-energy minerals.
1167    NonEnergyMinerals,
1168    /// Process industries.
1169    ProcessIndustries,
1170    /// Producer manufacturing.
1171    ProducerManufacturing,
1172    /// Retail trade.
1173    RetailTrade,
1174    /// Technology services.
1175    TechnologyServices,
1176    /// Transportation.
1177    Transportation,
1178    /// Utilities.
1179    Utilities,
1180}
1181
1182impl Display for StockSector {
1183    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1184        match *self {
1185            StockSector::CommercialServices => write!(f, "Commercial+Services"),
1186            StockSector::Communications => write!(f, "Communications"),
1187            StockSector::ConsumerDurables => write!(f, "Consumer+Durables"),
1188            StockSector::ConsumerNonDurables => write!(f, "Consumer+Non-Durables"),
1189            StockSector::ConsumerServices => write!(f, "Consumer+Services"),
1190            StockSector::DistributionServices => write!(f, "Distribution+Services"),
1191            StockSector::ElectronicTechnology => write!(f, "Electronic+Technology"),
1192            StockSector::EnergyMinerals => write!(f, "Energy+Minerals"),
1193            StockSector::Finance => write!(f, "Finance"),
1194            StockSector::Government => write!(f, "Government"),
1195            StockSector::HealthServices => write!(f, "Health+Services"),
1196            StockSector::HealthTechnology => write!(f, "Health+Technology"),
1197            StockSector::IndustrialServices => write!(f, "Industrial+Services"),
1198            StockSector::Miscellaneous => write!(f, "Miscellaneous"),
1199            StockSector::NonEnergyMinerals => write!(f, "Non-Energy+Minerals"),
1200            StockSector::ProcessIndustries => write!(f, "Process+Industries"),
1201            StockSector::ProducerManufacturing => write!(f, "Producer+Manufacturing"),
1202            StockSector::RetailTrade => write!(f, "Retail+Trade"),
1203            StockSector::TechnologyServices => write!(f, "Technology+Services"),
1204            StockSector::Transportation => write!(f, "Transportation"),
1205            StockSector::Utilities => write!(f, "Utilities"),
1206        }
1207    }
1208}
1209
1210/// Source organization for economic indicator data.
1211///
1212/// Default is [`WorldBank`](EconomicSource::WorldBank).
1213#[derive(Debug, Default, Clone, Deserialize, Serialize, Copy)]
1214pub enum EconomicSource {
1215    /// World Bank.
1216    #[default]
1217    WorldBank,
1218    /// Eurostat (EU statistical office).
1219    EUROSTAT,
1220    /// Akamai (internet/connectivity data).
1221    AKAMAI,
1222    /// Transparency International.
1223    TransparencyInternational,
1224    /// OECD.
1225    OrganizationForEconomicCooperationAndDevelopment,
1226    /// World Economic Forum.
1227    WorldEconomicForum,
1228    /// WageIndicator Foundation.
1229    WageIndicatorFoundation,
1230    /// U.S. Bureau of Labor Statistics.
1231    BureauOfLaborStatistics,
1232    /// U.S. Federal Reserve.
1233    FederalReserve,
1234    /// Stockholm International Peace Research Institute.
1235    StockholmInternationalPeaceResearchInstitute,
1236    /// Institute for Economics and Peace.
1237    InstituteForEconomicsAndPeace,
1238    /// U.S. Bureau of Economic Analysis.
1239    BureauOfEconomicAnalysis,
1240    /// World Gold Council.
1241    WorldGoldCouncil,
1242    /// U.S. Census Bureau.
1243    CensusBureau,
1244    /// Central Bank of West African States.
1245    CentralBankOfWestAfricanStates,
1246    /// International Monetary Fund.
1247    InternationalMonetaryFund,
1248    /// U.S. Energy Information Administration.
1249    USEnergyInformationAdministration,
1250    /// Statistics Canada.
1251    StatisticCanada,
1252    /// UK Office for National Statistics.
1253    OfficeForNationalStatistics,
1254    /// Statistics Norway.
1255    StatisticsNorway,
1256}
1257
1258/// Economic indicator category for filtering economic data.
1259///
1260/// Default is [`GDP`](EconomicCategory::GDP).
1261#[derive(Debug, Default, Clone, Deserialize, Serialize, Copy)]
1262pub enum EconomicCategory {
1263    /// Gross Domestic Product.
1264    #[default]
1265    GDP,
1266    /// Labor market indicators.
1267    Labor,
1268    /// Price indices (CPI, PPI, etc.).
1269    Prices,
1270    /// Health-related indicators.
1271    Health,
1272    /// Money supply and monetary indicators.
1273    Money,
1274    /// Trade balance and trade indicators.
1275    Trade,
1276    /// Government spending and fiscal data.
1277    Government,
1278    /// Business confidence and activity.
1279    Business,
1280    /// Consumer confidence and spending.
1281    Consumer,
1282    /// Housing market indicators.
1283    Housing,
1284    /// Tax-related data.
1285    Taxes,
1286}
1287
1288impl Display for EconomicSource {
1289    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1290        match *self {
1291            EconomicSource::WorldBank => write!(f, "__WB"),
1292            EconomicSource::EUROSTAT => write!(f, "__EUROSTAT"),
1293            EconomicSource::AKAMAI => write!(f, "__AKAMAI"),
1294            EconomicSource::TransparencyInternational => write!(f, "__TI"),
1295            EconomicSource::OrganizationForEconomicCooperationAndDevelopment => write!(f, "__OECD"),
1296            EconomicSource::WorldEconomicForum => write!(f, "__WEF"),
1297            EconomicSource::WageIndicatorFoundation => write!(f, "__WIF"),
1298            EconomicSource::BureauOfLaborStatistics => write!(f, "USBLS"),
1299            EconomicSource::FederalReserve => write!(f, "USFR"),
1300            EconomicSource::StockholmInternationalPeaceResearchInstitute => write!(f, "__SIPRI"),
1301            EconomicSource::InstituteForEconomicsAndPeace => write!(f, "__IEP"),
1302            EconomicSource::BureauOfEconomicAnalysis => write!(f, "USBEA"),
1303            EconomicSource::WorldGoldCouncil => write!(f, "__WGC"),
1304            EconomicSource::CensusBureau => write!(f, "USCB"),
1305            EconomicSource::CentralBankOfWestAfricanStates => write!(f, "__BCEAO"),
1306            EconomicSource::InternationalMonetaryFund => write!(f, "__IMF"),
1307            EconomicSource::USEnergyInformationAdministration => write!(f, "__UEIA"),
1308            EconomicSource::StatisticCanada => write!(f, "CASC"),
1309            EconomicSource::OfficeForNationalStatistics => write!(f, "GBONS"),
1310            EconomicSource::StatisticsNorway => write!(f, "NOSN"),
1311        }
1312    }
1313}
1314
1315impl Display for EconomicCategory {
1316    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1317        match *self {
1318            EconomicCategory::GDP => write!(f, "gdp"),
1319            EconomicCategory::Labor => write!(f, "lbr"),
1320            EconomicCategory::Prices => write!(f, "prce"),
1321            EconomicCategory::Health => write!(f, "hlth"),
1322            EconomicCategory::Money => write!(f, "mny"),
1323            EconomicCategory::Trade => write!(f, "trd"),
1324            EconomicCategory::Government => write!(f, "gov"),
1325            EconomicCategory::Business => write!(f, "bsnss"),
1326            EconomicCategory::Consumer => write!(f, "cnsm"),
1327            EconomicCategory::Housing => write!(f, "hse"),
1328            EconomicCategory::Taxes => write!(f, "txs"),
1329        }
1330    }
1331}
1332
1333/// Technical analysis recommendation scores for oscillators, summary, and moving averages.
1334///
1335/// Values are normalized to the range `[-1.0, 1.0]`.
1336#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
1337pub struct TechnicalAnalysisRecommendations {
1338    /// Oscillator-based recommendation score (`Recommend.Other`).
1339    #[serde(rename = "Other")]
1340    pub other: f64,
1341    /// Overall summary recommendation score (`Recommend.All`).
1342    #[serde(rename = "All")]
1343    pub all: f64,
1344    /// Moving average recommendation score (`Recommend.MA`).
1345    #[serde(rename = "MA")]
1346    pub ma: f64,
1347}
1348
1349/// Alias for [`TechnicalAnalysisRecommendations`].
1350pub type TechnicalAnalysisRecommendation = TechnicalAnalysisRecommendations;
1351/// Alias for [`TechnicalAnalysisRecommendations`].
1352pub type PeriodRecommendation = TechnicalAnalysisRecommendations;
1353
1354/// The eight standard time periods supported by TradingView's technical analysis scanner.
1355#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1356pub enum TechnicalAnalysisPeriod {
1357    /// 1 minute (`"1"`).
1358    #[serde(rename = "1")]
1359    Minute1,
1360    /// 5 minutes (`"5"`).
1361    #[serde(rename = "5")]
1362    Minute5,
1363    /// 15 minutes (`"15"`).
1364    #[serde(rename = "15")]
1365    Minute15,
1366    /// 1 hour / 60 minutes (`"60"`).
1367    #[serde(rename = "60")]
1368    Hour1,
1369    /// 4 hours / 240 minutes (`"240"`).
1370    #[serde(rename = "240")]
1371    Hour4,
1372    /// 1 day (`"1D"`).
1373    #[serde(rename = "1D")]
1374    Day1,
1375    /// 1 week (`"1W"`).
1376    #[serde(rename = "1W")]
1377    Week1,
1378    /// 1 month (`"1M"`).
1379    #[serde(rename = "1M")]
1380    Month1,
1381}
1382
1383impl TechnicalAnalysisPeriod {
1384    /// All eight periods in the fixed scanner evaluation order.
1385    pub const ALL: [Self; 8] = [
1386        Self::Minute1,
1387        Self::Minute5,
1388        Self::Minute15,
1389        Self::Hour1,
1390        Self::Hour4,
1391        Self::Day1,
1392        Self::Week1,
1393        Self::Month1,
1394    ];
1395
1396    /// String identifier matching the TradingView scanner column suffix.
1397    pub const fn as_str(&self) -> &'static str {
1398        match self {
1399            Self::Minute1 => "1",
1400            Self::Minute5 => "5",
1401            Self::Minute15 => "15",
1402            Self::Hour1 => "60",
1403            Self::Hour4 => "240",
1404            Self::Day1 => "1D",
1405            Self::Week1 => "1W",
1406            Self::Month1 => "1M",
1407        }
1408    }
1409}
1410
1411/// Alias for [`TechnicalAnalysisPeriod`].
1412pub type Period = TechnicalAnalysisPeriod;
1413
1414impl Display for TechnicalAnalysisPeriod {
1415    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1416        f.write_str(self.as_str())
1417    }
1418}
1419
1420/// Technical analysis ratings across all eight reference periods.
1421#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
1422pub struct TechnicalAnalysis {
1423    /// 1-minute ratings.
1424    #[serde(rename = "1")]
1425    pub period_1m: TechnicalAnalysisRecommendations,
1426    /// 5-minute ratings.
1427    #[serde(rename = "5")]
1428    pub period_5m: TechnicalAnalysisRecommendations,
1429    /// 15-minute ratings.
1430    #[serde(rename = "15")]
1431    pub period_15m: TechnicalAnalysisRecommendations,
1432    /// 1-hour / 60-minute ratings.
1433    #[serde(rename = "60")]
1434    pub period_1h: TechnicalAnalysisRecommendations,
1435    /// 4-hour / 240-minute ratings.
1436    #[serde(rename = "240")]
1437    pub period_4h: TechnicalAnalysisRecommendations,
1438    /// 1-day ratings.
1439    #[serde(rename = "1D")]
1440    pub period_1d: TechnicalAnalysisRecommendations,
1441    /// 1-week ratings.
1442    #[serde(rename = "1W")]
1443    pub period_1w: TechnicalAnalysisRecommendations,
1444    /// 1-month ratings.
1445    #[serde(rename = "1M")]
1446    pub period_1m_month: TechnicalAnalysisRecommendations,
1447}
1448
1449impl TechnicalAnalysis {
1450    /// Get recommendation scores for a specific period.
1451    pub fn get(&self, period: TechnicalAnalysisPeriod) -> &TechnicalAnalysisRecommendations {
1452        match period {
1453            TechnicalAnalysisPeriod::Minute1 => &self.period_1m,
1454            TechnicalAnalysisPeriod::Minute5 => &self.period_5m,
1455            TechnicalAnalysisPeriod::Minute15 => &self.period_15m,
1456            TechnicalAnalysisPeriod::Hour1 => &self.period_1h,
1457            TechnicalAnalysisPeriod::Hour4 => &self.period_4h,
1458            TechnicalAnalysisPeriod::Day1 => &self.period_1d,
1459            TechnicalAnalysisPeriod::Week1 => &self.period_1w,
1460            TechnicalAnalysisPeriod::Month1 => &self.period_1m_month,
1461        }
1462    }
1463
1464    /// Get recommendation scores for a specific period (alias for [`TechnicalAnalysis::get`]).
1465    pub fn period(&self, period: TechnicalAnalysisPeriod) -> &TechnicalAnalysisRecommendations {
1466        self.get(period)
1467    }
1468
1469    /// Get recommendation scores by period string slice (`"1"`, `"5"`, `"15"`, `"60"`, `"240"`, `"1D"`, `"1W"`, `"1M"`).
1470    pub fn get_by_str(&self, period: &str) -> Option<&TechnicalAnalysisRecommendations> {
1471        match period {
1472            "1" => Some(&self.period_1m),
1473            "5" => Some(&self.period_5m),
1474            "15" => Some(&self.period_15m),
1475            "60" => Some(&self.period_1h),
1476            "240" => Some(&self.period_4h),
1477            "1D" => Some(&self.period_1d),
1478            "1W" => Some(&self.period_1w),
1479            "1M" => Some(&self.period_1m_month),
1480            _ => None,
1481        }
1482    }
1483}
1484
1485impl std::ops::Index<TechnicalAnalysisPeriod> for TechnicalAnalysis {
1486    type Output = TechnicalAnalysisRecommendations;
1487
1488    fn index(&self, period: TechnicalAnalysisPeriod) -> &Self::Output {
1489        self.get(period)
1490    }
1491}
1492
1493impl std::ops::Index<&str> for TechnicalAnalysis {
1494    type Output = TechnicalAnalysisRecommendations;
1495
1496    fn index(&self, period: &str) -> &Self::Output {
1497        self.get_by_str(period)
1498            .unwrap_or_else(|| panic!("invalid period: {period}"))
1499    }
1500}
1501
1502#[cfg(test)]
1503mod tests {
1504    use super::*;
1505
1506    #[test]
1507    fn test_symbol_id_prefers_prefix_over_exchange() {
1508        let sym_prefixed = Symbol {
1509            symbol: "SPY".to_string(),
1510            exchange: "NYSE Arca".to_string(),
1511            prefix: "AMEX".to_string(),
1512            ..Default::default()
1513        };
1514        assert_eq!(sym_prefixed.id(), "AMEX:SPY");
1515        assert_eq!(MarketSymbol::id(&sym_prefixed), "AMEX:SPY");
1516
1517        let sym_empty_prefix = Symbol {
1518            symbol: "BTCUSDT".to_string(),
1519            exchange: "BINANCE".to_string(),
1520            prefix: "".to_string(),
1521            ..Default::default()
1522        };
1523        assert_eq!(sym_empty_prefix.id(), "BINANCE:BTCUSDT");
1524        assert_eq!(MarketSymbol::id(&sym_empty_prefix), "BINANCE:BTCUSDT");
1525    }
1526
1527    #[test]
1528    fn test_symbol_builder_with_prefix() {
1529        let sym = Symbol::builder()
1530            .symbol("SPY")
1531            .exchange("NYSE Arca")
1532            .prefix("AMEX")
1533            .build();
1534        assert_eq!(sym.id(), "AMEX:SPY");
1535
1536        let sym_default = Symbol::builder()
1537            .symbol("BTCUSDT")
1538            .exchange("BINANCE")
1539            .build();
1540        assert_eq!(sym_default.id(), "BINANCE:BTCUSDT");
1541    }
1542
1543    #[test]
1544    fn test_period_properties() {
1545        assert_eq!(TechnicalAnalysisPeriod::ALL.len(), 8);
1546        assert_eq!(Period::Day1.as_str(), "1D");
1547        assert_eq!(format!("{}", Period::Month1), "1M");
1548    }
1549}