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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum Market {
18 NYSE,
20 NASDAQ,
22 SHSE,
24 SZSE,
26 HKEX,
28 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#[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 #[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
91pub enum TradeSession {
92 PreMarket = 0,
94 Regular = 1,
96 AfterHours = 2,
98 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 #[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 #[inline]
164 pub(crate) const fn default_min_move(&self) -> i32 {
165 1
166 }
167
168 #[inline]
173 pub(crate) const fn default_price_scale(&self) -> i32 {
174 100
175 }
176
177 #[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 #[inline]
200 pub(crate) const fn default_type(&self) -> SymbolType {
201 SymbolType::Stock
202 }
203
204 #[inline]
209 pub(crate) const fn default_volume_type(&self) -> VolumeType {
210 VolumeType::Base
211 }
212
213 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 #[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}