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_trd_market_for_trade_read(
40    cached_trd_market: Option<i32>,
41    fallback_trd_market: i32,
42) -> Option<i32> {
43    cached_trd_market.or_else(|| (fallback_trd_market != 0).then_some(fallback_trd_market))
44}
45
46pub fn response_order_trd_market_for_trade_read(
47    trd_env: i32,
48    cached_trd_market: Option<i32>,
49    fallback_trd_market: i32,
50) -> Option<i32> {
51    // C++ `_APIServer_Trd_Comm.cpp::OrderData_NNToAPI`:
52    //   enOrderMarket = real ? nnOrder.enMarket : accItem.enTrdMkt
53    // Sim order responses must use account market, not backend/order row
54    // market.
55    if trd_env == 0 {
56        return (fallback_trd_market != 0).then_some(fallback_trd_market);
57    }
58    response_trd_market_for_trade_read(cached_trd_market, fallback_trd_market)
59}
60
61pub fn response_order_sec_market_for_trade_read(
62    trd_env: i32,
63    cached_sec_market: Option<i32>,
64    cached_trd_market: Option<i32>,
65    fallback_trd_market: i32,
66    code: &str,
67) -> Option<i32> {
68    if trd_env != 0 {
69        return response_sec_market_for_trade_read(
70            cached_sec_market,
71            cached_trd_market,
72            fallback_trd_market,
73            code,
74        );
75    }
76    // Sim order secMarket is derived from account market too. Strip any code
77    // prefix first so a stale SDK prefix cannot reclassify a sim HK order as
78    // US/N/A on the public response.
79    let bare_code = strip_market_prefix(code);
80    let derived = derive_sec_market(0, fallback_trd_market, &bare_code);
81    (derived != 0).then_some(derived)
82}
83
84pub fn response_order_currency_for_trade_read(
85    trd_env: i32,
86    cached_currency: Option<i32>,
87    _cached_trd_market: Option<i32>,
88    fallback_trd_market: i32,
89) -> Option<i32> {
90    if trd_env == 0 {
91        return crate::currency::trade_read_currency_for_market(
92            (fallback_trd_market != 0).then_some(fallback_trd_market),
93        );
94    }
95    cached_currency
96}
97
98pub fn response_order_fill_sec_market_for_trade_read(
99    trd_market: Option<i32>,
100    code: &str,
101) -> Option<i32> {
102    // C++ `_APIServer_Trd_Comm.cpp::OrderFillData_NNToAPI` derives
103    // `secMarket` from the backend deal market and code. The Rust backend
104    // proto does not expose `enExDestination` as a numeric field here, so use
105    // the shared trade-read fallback: code prefix/pattern first, then deal
106    // trd_market.
107    response_sec_market_for_trade_read(None, trd_market, trd_market.unwrap_or_default(), code)
108}
109
110/// Preserve C++ `Trd_GetPositionList` P/L ratio semantics.
111///
112/// C++ `APIServer_Trd_GetPositionList.cpp:83-97` writes
113/// `fDilutedPLRatio` / `fAveragePLRatio` straight into the FTAPI response.
114/// The lower real/sim parsers already normalize backend-specific wire forms
115/// before the APIServer layer. Do not infer units from price/cost here: real
116/// accounts have shown cases where that code-pattern fallback expands the
117/// public response by 100x versus C++.
118pub fn response_position_pl_ratio_for_trade_read(
119    raw_ratio: Option<f64>,
120    _price: f64,
121    _cost_price: f64,
122    _position_side: i32,
123) -> Option<f64> {
124    raw_ratio
125}
126
127#[cfg(test)]
128mod tests;