Skip to main content

futu_core/
trade_market.rs

1//! Trade-side market id namespaces shared across domain, SDK, and gateway crates.
2//!
3//! `Trd_Common.TrdMarket`, backend raw `Account.market`, and cached account
4//! markets intentionally use overlapping integers. Keep wrappers here so lower
5//! crates can share one fund-market classification without depending on
6//! higher-level trade/domain crates.
7
8pub mod legacy_backend_fund_market_id {
9    pub const HK_FUND: i32 = 13;
10    pub const US_FUND_OLD: i32 = 22;
11    pub const US_FUND: i32 = 23;
12    pub const SG_FUND: i32 = 24;
13}
14
15pub mod trd_market_id {
16    pub const HK: i32 = 1;
17    pub const US: i32 = 2;
18    pub const CN: i32 = 3;
19    pub const HKCC: i32 = 4;
20    pub const FUTURES: i32 = 5;
21    pub const SG: i32 = 6;
22    pub const CRYPTO: i32 = 7;
23    pub const AU: i32 = 8;
24    pub const FUTURES_SIMULATE_HK: i32 = 10;
25    pub const FUTURES_SIMULATE_US: i32 = 11;
26    pub const FUTURES_SIMULATE_SG: i32 = 12;
27    pub const FUTURES_SIMULATE_JP: i32 = 13;
28    pub const JP: i32 = 15;
29    /// Event-contract / prediction market.
30    /// Ref: C++ `Trd_Common.proto:42` and `_APIServer_Trd_Comm.cpp:2575-2577`.
31    pub const PREDICTION: i32 = 17;
32    /// v1.8 local extension aligned to Desktop `FINEnableMarket::KRX=18`.
33    pub const KRX: i32 = 18;
34    pub const MY: i32 = 111;
35    pub const CA: i32 = 112;
36    pub const HK_FUND: i32 = 113;
37    pub const US_FUND: i32 = 123;
38    pub const SG_FUND: i32 = 124;
39    pub const MY_FUND: i32 = 125;
40    pub const JP_FUND: i32 = 126;
41}
42
43/// User-facing aliases accepted for `Trd_Common.TrdMarket` read paths.
44pub const TRD_MARKET_STRING_VALUES: &[&str] = &[
45    "HK",
46    "US",
47    "CN",
48    "HKCC",
49    "FUTURES",
50    "SG",
51    "CRYPTO",
52    "AU",
53    "FUTURES_SIMULATE_HK",
54    "FUTURES_SIMULATE_US",
55    "FUTURES_SIMULATE_SG",
56    "FUTURES_SIMULATE_JP",
57    "JP",
58    "PREDICTION",
59    "KRX",
60    "MY",
61    "CA",
62    "HKFUND",
63    "USFUND",
64    "SGFUND",
65    "MYFUND",
66    "JPFUND",
67];
68
69pub const TRD_MARKET_PARSE_CHOICES: &str = "HK|US|CN|HKCC|FUTURES|SG|CRYPTO|AU|\
70FUTURES_SIMULATE_HK|FUTURES_SIMULATE_US|FUTURES_SIMULATE_SG|FUTURES_SIMULATE_JP|\
71JP|PREDICTION|KRX|MY|CA|HKFUND|USFUND|SGFUND|MYFUND|JPFUND or supported TrdMarket int";
72
73/// User-facing aliases accepted for active write/calculation paths.
74pub const TRD_MARKET_NON_FUND_STRING_VALUES: &[&str] = &[
75    "HK",
76    "US",
77    "CN",
78    "HKCC",
79    "FUTURES",
80    "SG",
81    "CRYPTO",
82    "AU",
83    "FUTURES_SIMULATE_HK",
84    "FUTURES_SIMULATE_US",
85    "FUTURES_SIMULATE_SG",
86    "FUTURES_SIMULATE_JP",
87    "JP",
88    "PREDICTION",
89    "MY",
90    "CA",
91];
92
93pub const TRD_MARKET_NON_FUND_PARSE_CHOICES: &str = "HK|US|CN|HKCC|FUTURES|SG|\
94CRYPTO|AU|FUTURES_SIMULATE_HK|FUTURES_SIMULATE_US|FUTURES_SIMULATE_SG|\
95FUTURES_SIMULATE_JP|JP|PREDICTION|MY|CA or supported ordinary-write TrdMarket int";
96
97/// Supported `Trd_Common.TrdMarket` integer values accepted by read surfaces,
98/// including view-only fund markets and guarded local extensions.
99pub const TRD_MARKET_INT_VALUES: &[i32] = &[
100    trd_market_id::HK,
101    trd_market_id::US,
102    trd_market_id::CN,
103    trd_market_id::HKCC,
104    trd_market_id::FUTURES,
105    trd_market_id::SG,
106    trd_market_id::CRYPTO,
107    trd_market_id::AU,
108    trd_market_id::FUTURES_SIMULATE_HK,
109    trd_market_id::FUTURES_SIMULATE_US,
110    trd_market_id::FUTURES_SIMULATE_SG,
111    trd_market_id::FUTURES_SIMULATE_JP,
112    trd_market_id::JP,
113    trd_market_id::PREDICTION,
114    trd_market_id::KRX,
115    trd_market_id::MY,
116    trd_market_id::CA,
117    trd_market_id::HK_FUND,
118    trd_market_id::US_FUND,
119    trd_market_id::SG_FUND,
120    trd_market_id::MY_FUND,
121    trd_market_id::JP_FUND,
122];
123
124/// Supported `Trd_Common.TrdMarket` integer values accepted by ordinary active
125/// write/calculation surfaces. Fund markets and the guarded KRX batch-close
126/// reservation stay outside this shared allowlist.
127pub const TRD_MARKET_NON_FUND_INT_VALUES: &[i32] = &[
128    trd_market_id::HK,
129    trd_market_id::US,
130    trd_market_id::CN,
131    trd_market_id::HKCC,
132    trd_market_id::FUTURES,
133    trd_market_id::SG,
134    trd_market_id::CRYPTO,
135    trd_market_id::AU,
136    trd_market_id::FUTURES_SIMULATE_HK,
137    trd_market_id::FUTURES_SIMULATE_US,
138    trd_market_id::FUTURES_SIMULATE_SG,
139    trd_market_id::FUTURES_SIMULATE_JP,
140    trd_market_id::JP,
141    trd_market_id::PREDICTION,
142    trd_market_id::MY,
143    trd_market_id::CA,
144];
145
146/// Parse a user-facing trade market string into the official OpenAPI
147/// `Trd_Common.TrdMarket` integer value.
148#[must_use]
149pub fn parse_trd_market_id(raw: &str) -> Option<i32> {
150    let upper = raw.trim().to_ascii_uppercase();
151    let compact = upper.replace('_', "");
152    let market = match upper.as_str() {
153        "HK" => 1,
154        "US" => 2,
155        "CN" => 3,
156        "HKCC" => 4,
157        "FUTURES" => 5,
158        "SG" => 6,
159        "CRYPTO" => 7,
160        "AU" => 8,
161        "JP" => 15,
162        "PREDICTION" => 17,
163        "KRX" => 18,
164        "MY" => 111,
165        "CA" => 112,
166        _ => match compact.as_str() {
167            "FUTURESSIMULATEHK" => 10,
168            "FUTURESSIMULATEUS" => 11,
169            "FUTURESSIMULATESG" => 12,
170            "FUTURESSIMULATEJP" => 13,
171            "HKFUND" => 113,
172            "USFUND" => 123,
173            "SGFUND" => 124,
174            "MYFUND" => 125,
175            "JPFUND" => 126,
176            _ => {
177                return upper
178                    .parse::<i32>()
179                    .ok()
180                    .filter(|value| is_trd_market_id(*value));
181            }
182        },
183    };
184    Some(market)
185}
186
187#[must_use]
188pub fn is_trd_market_id(value: i32) -> bool {
189    TRD_MARKET_INT_VALUES.contains(&value)
190}
191
192#[must_use]
193pub fn canonical_fund_trd_market_label(market: i32) -> Option<&'static str> {
194    match market {
195        trd_market_id::HK_FUND => Some("HKFund"),
196        trd_market_id::US_FUND => Some("USFund"),
197        trd_market_id::SG_FUND => Some("SGFund"),
198        trd_market_id::MY_FUND => Some("MYFund"),
199        trd_market_id::JP_FUND => Some("JPFund"),
200        _ => None,
201    }
202}
203
204/// Canonical `Trd_Common.TrdMarket` label used by user-facing filters and
205/// surface adapters.
206#[must_use]
207pub fn trd_market_label(market: i32) -> Option<&'static str> {
208    match market {
209        1 => Some("HK"),
210        2 => Some("US"),
211        3 => Some("CN"),
212        4 => Some("HKCC"),
213        5 => Some("FUTURES"),
214        6 => Some("SG"),
215        7 => Some("CRYPTO"),
216        8 => Some("AU"),
217        10 => Some("FUTURES_SIMULATE_HK"),
218        11 => Some("FUTURES_SIMULATE_US"),
219        12 => Some("FUTURES_SIMULATE_SG"),
220        13 => Some("FUTURES_SIMULATE_JP"),
221        15 => Some("JP"),
222        17 => Some("PREDICTION"),
223        // Desktop FINEnableMarket::KRX=18. Official TrdMarket has not named
224        // it yet, but v1.8 BatchClose accepts the numeric source value.
225        18 => Some("KRX"),
226        111 => Some("MY"),
227        112 => Some("CA"),
228        trd_market_id::HK_FUND => Some("HKFUND"),
229        trd_market_id::US_FUND => Some("USFUND"),
230        trd_market_id::SG_FUND => Some("SGFUND"),
231        trd_market_id::MY_FUND => Some("MYFUND"),
232        trd_market_id::JP_FUND => Some("JPFUND"),
233        _ => None,
234    }
235}
236
237/// Label for fund markets that are view-only on active write/calculation paths.
238///
239/// This intentionally covers both backend raw cached account markets and
240/// canonical OpenAPI fund markets. Use [`trd_market_label`] for generic display.
241#[must_use]
242pub fn view_only_fund_market_label(trd_market: i32) -> Option<&'static str> {
243    CachedAccountMarket::new(trd_market).view_only_fund_label()
244}
245
246/// Parse a trade market for active write/calculation paths.
247#[must_use]
248pub fn parse_non_fund_trd_market_id(raw: &str) -> Option<i32> {
249    parse_trd_market_id(raw).filter(|market| TRD_MARKET_NON_FUND_INT_VALUES.contains(market))
250}
251
252/// Backend raw `Account.market` value cached in `CachedTrdAcc.trd_market`.
253#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
254pub struct RawAccountMarket(i32);
255
256impl RawAccountMarket {
257    #[must_use]
258    pub const fn new(value: i32) -> Self {
259        Self(value)
260    }
261
262    #[must_use]
263    pub const fn raw_i32(self) -> i32 {
264        self.0
265    }
266
267    #[must_use]
268    pub const fn as_i32(self) -> i32 {
269        self.raw_i32()
270    }
271
272    #[must_use]
273    pub fn view_only_fund_label(self) -> Option<&'static str> {
274        use legacy_backend_fund_market_id::*;
275
276        match self.0 {
277            HK_FUND => Some("HKFund(raw)"),
278            US_FUND_OLD => Some("USFund(raw,old)"),
279            US_FUND => Some("USFund(raw)"),
280            SG_FUND => Some("SGFund(raw)"),
281            _ => None,
282        }
283    }
284}
285
286/// Market namespace read from `CachedTrdAcc.trd_market`.
287///
288/// The production cache stores backend raw `Account.market`, but this boundary
289/// deliberately stays fail-closed for legacy/test fixtures that may contain
290/// canonical OpenAPI fund market values from earlier projections.
291#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
292pub struct CachedAccountMarket(i32);
293
294impl CachedAccountMarket {
295    #[must_use]
296    pub const fn new(value: i32) -> Self {
297        Self(value)
298    }
299
300    #[must_use]
301    pub const fn raw_i32(self) -> i32 {
302        self.0
303    }
304
305    #[must_use]
306    pub const fn as_i32(self) -> i32 {
307        self.raw_i32()
308    }
309
310    #[must_use]
311    pub fn view_only_fund_label(self) -> Option<&'static str> {
312        use trd_market_id::*;
313
314        RawAccountMarket::new(self.0)
315            .view_only_fund_label()
316            .or(match self.0 {
317                HK_FUND => Some("HKFund"),
318                US_FUND => Some("USFund"),
319                SG_FUND => Some("SGFund"),
320                MY_FUND => Some("MYFund"),
321                JP_FUND => Some("JPFund"),
322                _ => None,
323            })
324    }
325}