Skip to main content

futu_core/
trade_currency.rs

1//! Trade-side currency labels and shared user-facing funds diagnostics.
2//!
3//! This module is deliberately lower than `futu-trd` and trade-domain crates so
4//! SDK, gateway, and domain policies can share one warning contract without
5//! introducing reverse domain dependencies.
6
7use crate::trade_market::trd_market_id;
8
9// These are internal `NN_TrdMarket` values, not public
10// `Trd_Common::TrdMarket`. In particular, internal 7 is Sim_US_Option while
11// public 7 is Crypto (internal Crypto is 200).
12// Ref: `FutuOpenD/Src/NNBase/NNBase_Define_Enum.h:172-180,189`.
13const NN_TRD_MARKET_SIM_US_OPTION: i32 = 7;
14const NN_TRD_MARKET_SIM_HK_OPTION: i32 = 9;
15const NN_TRD_MARKET_SIM_US_MARGIN: i32 = 100;
16
17/// `Trd_Common.proto::Currency` enum values used by public trade surfaces.
18///
19/// Ref: `Trd_Common.proto::Currency`.
20pub mod currency_id {
21    pub const HKD: i32 = 1;
22    pub const USD: i32 = 2;
23    pub const CNH: i32 = 3;
24    pub const JPY: i32 = 4;
25    pub const SGD: i32 = 5;
26    pub const AUD: i32 = 6;
27    pub const CAD: i32 = 7;
28    pub const MYR: i32 = 8;
29    /// Ref: C++ `Trd_Common.proto:204` and `_NNProto_Trd_Comm.cpp:859-861`.
30    pub const NZD: i32 = 9;
31}
32
33/// Public `Trd_Common.Currency` label used in diagnostics.
34#[must_use]
35pub fn currency_label(currency: i32) -> &'static str {
36    match currency {
37        currency_id::HKD => "HKD",
38        currency_id::USD => "USD",
39        currency_id::CNH => "CNH",
40        currency_id::JPY => "JPY",
41        currency_id::SGD => "SGD",
42        currency_id::AUD => "AUD",
43        currency_id::CAD => "CAD",
44        currency_id::MYR => "MYR",
45        currency_id::NZD => "NZD",
46        _ => "UNKNOWN",
47    }
48}
49
50/// Parse a user-facing currency code into `Trd_Common.Currency`.
51///
52/// This lives in core because REST/CLI/MCP and typed trade facades all need the
53/// same loud-reject contract for textual currency input.
54pub fn parse_currency_label(value: &str) -> Result<i32, String> {
55    match value.trim().to_ascii_uppercase().as_str() {
56        "HKD" => Ok(currency_id::HKD),
57        "USD" => Ok(currency_id::USD),
58        "CNH" | "CNY" | "RMB" => Ok(currency_id::CNH),
59        "JPY" => Ok(currency_id::JPY),
60        "SGD" => Ok(currency_id::SGD),
61        "AUD" => Ok(currency_id::AUD),
62        "CAD" => Ok(currency_id::CAD),
63        "MYR" => Ok(currency_id::MYR),
64        "NZD" => Ok(currency_id::NZD),
65        _ => Err(format!(
66            "invalid currency {value:?}: expected HKD|USD|CNH|JPY|SGD|AUD|CAD|MYR|NZD"
67        )),
68    }
69}
70
71/// User-visible warning when a single-currency backend ignores explicit funds
72/// currency and returns the account native currency instead.
73///
74/// Only warn when the user explicitly requested a currency and the response
75/// carries a different non-unknown currency tag.
76#[must_use]
77pub fn funds_currency_mismatch_warning(
78    requested_currency: Option<i32>,
79    returned_currency: Option<i32>,
80) -> Option<String> {
81    let requested = requested_currency?;
82    let returned = returned_currency?;
83    if returned == 0 || returned == requested {
84        return None;
85    }
86
87    let requested_label = currency_label(requested);
88    let returned_label = currency_label(returned);
89    Some(format!(
90        "currency ignored by backend: requested `{requested_label}` (id={requested}), \
91         returned `{returned_label}` (id={returned}). 此账户按账户基准币种返回资金数据."
92    ))
93}
94
95/// Project a present internal `NN_TrdMarket` into the currency used by
96/// simulated trade-read position and order responses.
97///
98/// A missing market remains missing because C++ never calls the mapping
99/// function without an `NN_TrdMarket` value. A present but unknown enum value
100/// follows C++'s unconditional HKD fallback.
101///
102/// Real and Crypto response paths must publish their backend currency facts
103/// directly and must not call this helper with public `TrdMarket` values.
104///
105/// This is not the GetFunds-local market mapping (which returns Unknown for
106/// unsupported markets) and not the raw backend account-market mapping (where
107/// value 13 has a different namespace).
108///
109/// Ref: `FutuOpenD/Src/NNDataCenter/Trade/INNData_Trd_CommonCurrency.cpp:138-171`.
110#[must_use]
111pub fn trade_read_currency_for_sim_nn_market(trd_market: Option<i32>) -> Option<i32> {
112    Some(match trd_market? {
113        trd_market_id::HK | NN_TRD_MARKET_SIM_HK_OPTION | trd_market_id::FUTURES_SIMULATE_HK => {
114            currency_id::HKD
115        }
116        trd_market_id::US
117        | NN_TRD_MARKET_SIM_US_OPTION
118        | trd_market_id::FUTURES_SIMULATE_US
119        | NN_TRD_MARKET_SIM_US_MARGIN
120        | trd_market_id::PREDICTION => currency_id::USD,
121        trd_market_id::CN | trd_market_id::HKCC => currency_id::CNH,
122        trd_market_id::SG | trd_market_id::FUTURES_SIMULATE_SG => currency_id::SGD,
123        trd_market_id::JP | trd_market_id::FUTURES_SIMULATE_JP => currency_id::JPY,
124        trd_market_id::AU => currency_id::AUD,
125        trd_market_id::MY => currency_id::MYR,
126        trd_market_id::CA => currency_id::CAD,
127        _ => currency_id::HKD,
128    })
129}