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