openpine_vm/
market.rs

1use std::{borrow::Cow, str::FromStr};
2
3use serde::{Deserialize, Serialize};
4use time::macros::time;
5use time_tz::timezones::db::{
6    america::NEW_YORK,
7    asia::{HONG_KONG, SHANGHAI, SINGAPORE},
8};
9
10use crate::{
11    Currency, SymbolType, VolumeType,
12    time::{TimeBasedSession, TimeBasedSessionDays, TimeZone},
13};
14
15/// Represents various stock markets.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum Market {
18    /// New York Stock Exchange
19    NYSE,
20    /// NASDAQ Stock Exchange
21    NASDAQ,
22    /// Shanghai Stock Exchange
23    SHSE,
24    /// Shenzhen Stock Exchange
25    SZSE,
26    /// Hong Kong Stock Exchange
27    HKEX,
28    /// Singapore Exchange
29    SGX,
30}
31
32impl Serialize for Market {
33    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
34    where
35        S: serde::Serializer,
36    {
37        self.as_str().serialize(serializer)
38    }
39}
40
41impl<'de> Deserialize<'de> for Market {
42    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
43    where
44        D: serde::Deserializer<'de>,
45    {
46        let s = <&str>::deserialize(deserializer)?;
47        s.parse().map_err(serde::de::Error::custom)
48    }
49}
50
51/// An error occurs when an unknown market string is encountered.
52#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
53#[error("unknown market")]
54#[non_exhaustive]
55pub struct UnknownMarketError;
56
57impl FromStr for Market {
58    type Err = UnknownMarketError;
59
60    fn from_str(s: &str) -> Result<Self, Self::Err> {
61        match s {
62            "NYSE" => Ok(Market::NYSE),
63            "NASDAQ" => Ok(Market::NASDAQ),
64            "SHSE" => Ok(Market::SHSE),
65            "SZSE" => Ok(Market::SZSE),
66            "HKEX" => Ok(Market::HKEX),
67            "SGX" => Ok(Market::SGX),
68            _ => Err(UnknownMarketError),
69        }
70    }
71}
72
73impl Market {
74    /// Returns the string representation of the market.
75    #[inline]
76    pub fn as_str(&self) -> &'static str {
77        match self {
78            Market::NYSE => "NYSE",
79            Market::NASDAQ => "NASDAQ",
80            Market::SHSE => "SHSE",
81            Market::SZSE => "SZSE",
82            Market::HKEX => "HKEX",
83            Market::SGX => "SGX",
84        }
85    }
86}
87
88/// Represents different trading sessions within a market day.
89/// Trading session identifier.
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
91pub enum TradeSession {
92    /// Pre-market session.
93    PreMarket = 0,
94    /// Regular trading session.
95    Regular = 1,
96    /// After-hours session.
97    AfterHours = 2,
98    /// Overnight session.
99    Overnight = 3,
100}
101
102const US_PREOVERNIGHT_PERIODS: (time::Time, time::Time) = (time!(00:00:00), time!(04:00:00));
103const US_PREMARKET_PERIOD: (time::Time, time::Time) = (time!(04:00:00), time!(09:30:00));
104const US_REGULAR_PERIOD: (time::Time, time::Time) = (time!(09:30:00), time!(16:15:00));
105const US_AFTERHOURS_PERIOD: (time::Time, time::Time) = (time!(16:00:00), time!(20:00:00));
106const US_POSTOVERNIGHT_PERIOD: (time::Time, time::Time) = (time!(20:00:00), time!(00:00:00));
107
108static US_REGULAR: TimeBasedSession = TimeBasedSession::new(
109    Cow::Borrowed(&[US_REGULAR_PERIOD]),
110    Some(TimeBasedSessionDays::WORKDAYS),
111);
112static US_EXTENDED: TimeBasedSession = TimeBasedSession::new(
113    Cow::Borrowed(&[
114        US_PREOVERNIGHT_PERIODS,
115        US_PREMARKET_PERIOD,
116        US_REGULAR_PERIOD,
117        US_AFTERHOURS_PERIOD,
118        US_POSTOVERNIGHT_PERIOD,
119    ]),
120    Some(TimeBasedSessionDays::WORKDAYS),
121);
122
123static CN_REGULAR: TimeBasedSession = TimeBasedSession::new(
124    Cow::Borrowed(&[
125        (time!(09:30:00), time!(11:30:00)),
126        (time!(13:00:00), time!(15:00:00)),
127    ]),
128    Some(TimeBasedSessionDays::WORKDAYS),
129);
130
131static HK_REGULAR: TimeBasedSession = TimeBasedSession::new(
132    Cow::Borrowed(&[
133        (time!(09:30:00), time!(12:00:00)),
134        (time!(13:00:00), time!(16:00:00)),
135    ]),
136    Some(TimeBasedSessionDays::WORKDAYS),
137);
138
139static SG_REGULAR: TimeBasedSession = TimeBasedSession::new(
140    Cow::Borrowed(&[
141        (time!(09:00:00), time!(12:00:00)),
142        (time!(13:00:00), time!(17:15:00)),
143    ]),
144    Some(TimeBasedSessionDays::WORKDAYS),
145);
146
147impl Market {
148    /// Returns the default currency for the market.
149    #[inline]
150    pub(crate) const fn default_currency(&self) -> Currency {
151        match self {
152            Market::NYSE | Market::NASDAQ => Currency::USD,
153            Market::SHSE | Market::SZSE => Currency::CNY,
154            Market::HKEX => Currency::HKD,
155            Market::SGX => Currency::SGD,
156        }
157    }
158
159    /// Returns the default minimum move for the market.
160    ///
161    /// This is the numerator in `mintick = min_move / price_scale`.
162    /// All supported markets currently default to `1`.
163    #[inline]
164    pub(crate) const fn default_min_move(&self) -> i32 {
165        1
166    }
167
168    /// Returns the default price scale for the market.
169    ///
170    /// This is the denominator in `mintick = min_move / price_scale`.
171    /// All supported markets currently default to `100` (mintick = 0.01).
172    #[inline]
173    pub(crate) const fn default_price_scale(&self) -> i32 {
174        100
175    }
176
177    /// Returns the default country code for the market in ISO 3166-1 alpha-2
178    /// format.
179    ///
180    /// Every supported market maps to a specific country or region:
181    /// - NYSE / NASDAQ → `"US"`
182    /// - SHSE / SZSE → `"CN"`
183    /// - HKEX → `"HK"`
184    /// - SGX → `"SG"`
185    #[inline]
186    pub(crate) const fn default_country(&self) -> &'static str {
187        match self {
188            Market::NYSE | Market::NASDAQ => "US",
189            Market::SHSE | Market::SZSE => "CN",
190            Market::HKEX => "HK",
191            Market::SGX => "SG",
192        }
193    }
194
195    /// Returns the default symbol type for the market.
196    ///
197    /// All currently supported markets are equity exchanges, so the default
198    /// type is [`SymbolType::Stock`].
199    #[inline]
200    pub(crate) const fn default_type(&self) -> SymbolType {
201        SymbolType::Stock
202    }
203
204    /// Returns the default volume type for the market.
205    ///
206    /// All currently supported equity markets report volume in base units
207    /// (shares), so the default is [`VolumeType::Base`].
208    #[inline]
209    pub(crate) const fn default_volume_type(&self) -> VolumeType {
210        VolumeType::Base
211    }
212
213    /// Returns the timezone associated with the market.
214    pub const fn timezone(&self) -> TimeZone {
215        match self {
216            Market::NYSE | Market::NASDAQ => TimeZone::Tz(NEW_YORK),
217            Market::SHSE | Market::SZSE => TimeZone::Tz(SHANGHAI),
218            Market::HKEX => TimeZone::Tz(HONG_KONG),
219            Market::SGX => TimeZone::Tz(SINGAPORE),
220        }
221    }
222
223    /// Returns the time-based session for this market.
224    /// When `include_extended_hours` is true, US stock markets return one
225    /// session that includes pre-market, regular, and after-hours as
226    /// multiple periods; other markets and US options have no extended
227    /// hours, so this flag has no effect.
228    #[inline]
229    pub(crate) const fn time_based_session(
230        &self,
231        symbol_type: SymbolType,
232        include_extended_hours: bool,
233    ) -> &'static TimeBasedSession {
234        match (self, symbol_type, include_extended_hours) {
235            (Market::NYSE | Market::NASDAQ, SymbolType::Option, _) => &US_REGULAR,
236            (Market::NYSE | Market::NASDAQ, _, true) => &US_EXTENDED,
237            (Market::NYSE | Market::NASDAQ, _, false) => &US_REGULAR,
238            (Market::SHSE | Market::SZSE, _, _) => &CN_REGULAR,
239            (Market::HKEX, _, _) => &HK_REGULAR,
240            (Market::SGX, _, _) => &SG_REGULAR,
241        }
242    }
243}