Skip to main content

futu_trd/
projection.rs

1//! Shared trade read response projection helpers.
2//!
3//! The cache/backend layer stores values close to the wire shape. Public
4//! `Trd_*` responses should expose FTAPI semantics consistently across REST,
5//! gRPC, raw WS, MCP, and CLI, so market/currency/ratio projection lives here
6//! instead of inside one surface handler.
7
8use crate::market::{derive_sec_market, strip_market_prefix};
9
10/// C++ `SessionNNToAPIFromOrderTradeTime`.
11///
12/// Ref: `FutuOpenD/Src/APIServer/APIServer_Inner_API.cpp:2869-2892`
13/// and `proto/Common.proto:81-87`.
14#[must_use]
15pub fn order_trade_time_type_to_session(order_trade_time_type: Option<u32>) -> Option<i32> {
16    match order_trade_time_type? {
17        0 => Some(0), // NN_TrdOrderTradeTimeType_UNSET -> Session_NONE
18        1 => Some(1), // NN_TrdOrderTradeTimeType_GENNERAL -> Session_RTH
19        2 => Some(2), // NN_TrdOrderTradeTimeType_ENABLE_BA_TRADE -> Session_ETH
20        3 => Some(4), // NN_TrdOrderTradeTimeType_ONLY_OVERNIGHT -> Session_OVERNIGHT
21        6 => Some(3), // NN_TrdOrderTradeTimeType_ALL_DAY -> Session_ALL
22        _ => Some(0), // C++ default keeps Session_NONE
23    }
24}
25
26pub fn response_sec_market_for_trade_read(
27    cached_sec_market: Option<i32>,
28    trd_market: Option<i32>,
29    fallback_trd_market: i32,
30    code: &str,
31) -> Option<i32> {
32    if let Some(sec_market) = cached_sec_market.filter(|v| *v != 0) {
33        return Some(sec_market);
34    }
35    let derived = derive_sec_market(0, trd_market.unwrap_or(fallback_trd_market), code);
36    (derived != 0).then_some(derived)
37}
38
39pub fn response_currency_for_trade_read(
40    cached_currency: Option<i32>,
41    trd_market: Option<i32>,
42) -> Option<i32> {
43    if let Some(currency) = cached_currency.filter(|v| *v != 0) {
44        return Some(currency);
45    }
46    crate::currency::trade_read_currency_for_market(trd_market)
47}
48
49pub fn response_trd_market_for_trade_read(
50    cached_trd_market: Option<i32>,
51    fallback_trd_market: i32,
52) -> Option<i32> {
53    cached_trd_market.or_else(|| (fallback_trd_market != 0).then_some(fallback_trd_market))
54}
55
56pub fn response_order_trd_market_for_trade_read(
57    trd_env: i32,
58    cached_trd_market: Option<i32>,
59    fallback_trd_market: i32,
60) -> Option<i32> {
61    // C++ `_APIServer_Trd_Comm.cpp::OrderData_NNToAPI`:
62    //   enOrderMarket = real ? nnOrder.enMarket : accItem.enTrdMkt
63    // Sim order responses must use account market, not backend/order row
64    // market.
65    if trd_env == 0 {
66        return (fallback_trd_market != 0).then_some(fallback_trd_market);
67    }
68    response_trd_market_for_trade_read(cached_trd_market, fallback_trd_market)
69}
70
71pub fn response_order_sec_market_for_trade_read(
72    trd_env: i32,
73    cached_sec_market: Option<i32>,
74    cached_trd_market: Option<i32>,
75    fallback_trd_market: i32,
76    code: &str,
77) -> Option<i32> {
78    if trd_env != 0 {
79        return response_sec_market_for_trade_read(
80            cached_sec_market,
81            cached_trd_market,
82            fallback_trd_market,
83            code,
84        );
85    }
86    // Sim order secMarket is derived from account market too. Strip any code
87    // prefix first so a stale SDK prefix cannot reclassify a sim HK order as
88    // US/N/A on the public response.
89    let bare_code = strip_market_prefix(code);
90    let derived = derive_sec_market(0, fallback_trd_market, &bare_code);
91    (derived != 0).then_some(derived)
92}
93
94pub fn response_order_currency_for_trade_read(
95    trd_env: i32,
96    cached_currency: Option<i32>,
97    cached_trd_market: Option<i32>,
98    fallback_trd_market: i32,
99) -> Option<i32> {
100    if trd_env == 0 {
101        return crate::currency::trade_read_currency_for_market(
102            (fallback_trd_market != 0).then_some(fallback_trd_market),
103        );
104    }
105    response_currency_for_trade_read(
106        cached_currency,
107        response_trd_market_for_trade_read(cached_trd_market, fallback_trd_market),
108    )
109}
110
111pub fn response_order_fill_sec_market_for_trade_read(
112    trd_market: Option<i32>,
113    code: &str,
114) -> Option<i32> {
115    // C++ `_APIServer_Trd_Comm.cpp::OrderFillData_NNToAPI` derives
116    // `secMarket` from the backend deal market and code. The Rust backend
117    // proto does not expose `enExDestination` as a numeric field here, so use
118    // the shared trade-read fallback: code prefix/pattern first, then deal
119    // trd_market.
120    response_sec_market_for_trade_read(None, trd_market, trd_market.unwrap_or_default(), code)
121}
122
123/// Preserve C++ `Trd_GetPositionList` P/L ratio semantics.
124///
125/// C++ `APIServer_Trd_GetPositionList.cpp:83-97` writes
126/// `fDilutedPLRatio` / `fAveragePLRatio` straight into the FTAPI response.
127/// The lower real/sim parsers already normalize backend-specific wire forms
128/// before the APIServer layer. Do not infer units from price/cost here: real
129/// accounts have shown cases where that code-pattern fallback expands the
130/// public response by 100x versus C++.
131pub fn response_position_pl_ratio_for_trade_read(
132    raw_ratio: Option<f64>,
133    _price: f64,
134    _cost_price: f64,
135    _position_side: i32,
136) -> Option<f64> {
137    raw_ratio
138}
139
140#[cfg(test)]
141mod tests;