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