Skip to main content

futu_backend/trade_query/orders/
projection.rs

1//! trade_query/orders/projection — backend order proto -> cached order rows.
2
3use futu_cache::trd_cache::CachedOrder;
4use futu_domain_trade_history::projection::backend_orders::{
5    BackendOrderSupFacts, BackendTradeReadOrderFacts, project_backend_trade_read_order_like_cpp,
6};
7use futu_domain_trade_history::projection::combo::{
8    HistoryOrderComboFacts, HistoryOrderComboLegFacts, ProjectedHistoryOrderComboLeg,
9};
10use futu_domain_trade_history::projection::orders::{
11    BackendOrderUnpackFacts, project_backend_order_unpacked_trd_market_like_cpp,
12};
13use futu_domain_trade_order::{OrderWireExtensionFacts, project_order_wire_extensions_like_cpp};
14
15use crate::proto_internal::{odr_sys_cmn, sim_odr_sys_cmn};
16
17pub fn order_proto_to_cached_like_cpp(trd_env: i32, o: &odr_sys_cmn::Order) -> Option<CachedOrder> {
18    order_proto_to_cached_with_jp_acc_type_like_cpp(trd_env, o, None)
19}
20
21pub fn order_proto_to_cached_with_jp_acc_type_like_cpp(
22    trd_env: i32,
23    o: &odr_sys_cmn::Order,
24    jp_acc_type: Option<i32>,
25) -> Option<CachedOrder> {
26    let trd_market = project_backend_order_unpacked_trd_market_like_cpp(BackendOrderUnpackFacts {
27        trd_env,
28        has_order_id: o.order_id.is_some(),
29        has_side: o.side.is_some(),
30        market: o.market,
31        has_symbol: o.symbol.is_some(),
32        has_create_time: o.create_time.is_some(),
33        has_expire_time: o.expire_time.is_some(),
34        has_status: o.status.is_some(),
35    })?;
36    let order_trade_time_type = o.sup.as_ref().and_then(|s| s.order_trade_time_type);
37    let combo = o
38        .multi_leg_info
39        .as_ref()
40        .map(|multi_leg| HistoryOrderComboFacts {
41            backend_strategy_type: multi_leg.strategy_type,
42            legs: multi_leg
43                .leg_infos
44                .iter()
45                .map(|leg| HistoryOrderComboLegFacts {
46                    symbol: leg.leg_symbol.as_deref(),
47                    exchange: leg.leg_exchange.as_deref(),
48                    side: leg.leg_side,
49                    ratio_qty: leg.leg_ratio_qty.as_deref(),
50                    position_id: leg.position_id.as_deref(),
51                })
52                .collect(),
53        });
54    let sup = o.sup.as_ref();
55    let wire_extensions = project_order_wire_extensions_like_cpp(OrderWireExtensionFacts {
56        cash_order_qty: o.cash_order_qty.as_deref(),
57        has_condition: o.condition.is_some(),
58        portfolio_stock_id: o.portfolio_stock_id,
59        has_margin_trade_info: o.margin_trade_info.is_some(),
60        event_contract_quote_id: o
61            .order_extra
62            .as_ref()
63            .and_then(|extra| extra.quote_id.as_deref()),
64    });
65    let projected = project_backend_trade_read_order_like_cpp(BackendTradeReadOrderFacts {
66        trd_env,
67        sim_option_account: trd_env == 0 && matches!(o.market, Some(7 | 9)),
68        trd_market_ftapi: trd_market,
69        ex_destination: o.exdestination,
70        exchange: o.exchange.as_deref(),
71        order_type_trd_market_ftapi: trd_market,
72        currency: o.currency.map(|c| c as i32),
73        jp_acc_type,
74        app_lang: 0,
75        order_id_ex: o.order_id.as_deref(),
76        side: o.side,
77        order_type: o.r#type,
78        security_type: o.security_type,
79        status: o.status,
80        code: o.symbol.as_deref(),
81        stock_name: o.stock_name.as_deref(),
82        qty: o.qty.as_deref(),
83        price: o.price.as_deref(),
84        create_time_micros: o.create_time,
85        update_time_micros: o.update_time,
86        market_id: o.exchange_code,
87        cum_qty: o.cum_qty.as_deref(),
88        avg_fill_price: o.avg_fill_price.as_deref(),
89        last_err_msg: o.last_err_msg.as_deref(),
90        text: o.text.as_deref(),
91        aux_price: o.aux_price.as_deref(),
92        expire_time_micros: o.expire_time,
93        order_amount: o.order_amount.as_deref(),
94        sup: BackendOrderSupFacts {
95            order_trade_time_type,
96            time_in_force: sup.and_then(|value| value.time_in_force),
97            odr_enable_ba_trade: sup.and_then(|value| value.odr_enable_ba_trade),
98            hk_odr_enable_ba_trade: sup.and_then(|value| value.hk_odr_enable_ba_trade),
99            trail_type: sup.and_then(|value| value.trail_type),
100            trail_percent: sup.and_then(|value| value.trail_percent.as_deref()),
101            trail_price: sup.and_then(|value| value.trail_price.as_deref()),
102        },
103        combo,
104    });
105
106    Some(CachedOrder {
107        order_id: projected.order_id,
108        order_id_ex: projected.order_id_ex,
109        code: projected.code,
110        // Cached rows keep backend stock_name; public response projection
111        // localizes combo display names from strategy_type + request app_lang.
112        name: o.stock_name.as_ref().cloned().unwrap_or(projected.name),
113        trd_side: projected.trd_side,
114        order_type: projected.order_type,
115        order_status: projected.order_status,
116        qty: projected.qty,
117        price: projected.price.unwrap_or(0.0),
118        fill_qty: projected.fill_qty,
119        fill_avg_price: projected.fill_avg_price,
120        create_time: projected.create_time,
121        update_time: projected.update_time,
122        last_err_msg: projected.last_err_msg,
123        sec_market: projected.sec_market,
124        create_timestamp: projected.create_timestamp,
125        update_timestamp: projected.update_timestamp,
126        remark: projected.remark,
127        time_in_force: projected.time_in_force,
128        fill_outside_rth: projected.fill_outside_rth,
129        aux_price: projected.aux_price,
130        trail_type: projected.trail_type,
131        trail_value: projected.trail_value,
132        trail_spread: projected.trail_spread,
133        currency: projected.currency,
134        trd_market: projected.trd_market,
135        strategy_type: projected.strategy_type,
136        combo_legs: projected
137            .combo_legs
138            .into_iter()
139            .map(combo_leg_data_domain_to_api_like_cpp)
140            .collect(),
141        quote_id: wire_extensions
142            .event_contract_quote_id
143            .map(ToOwned::to_owned),
144        backend_order_id: o.order_id.clone().unwrap_or_default(),
145        order_version: o.version.unwrap_or(0) as i32,
146        exchange_code: o.exchange_code.unwrap_or(0) as i32,
147        exchange: o.exchange.clone().unwrap_or_default(),
148        security_type: o.security_type.unwrap_or(0) as i32,
149        session: projected.session,
150        order_trade_time_type,
151        sub_account_id: o.sub_account_id,
152        jp_acc_type: projected.jp_acc_type,
153        is_stub: false,
154        is_local_order: false,
155        stub_inserted_at_ms: 0,
156        is_pending_broker_confirm: false,
157    })
158}
159
160fn combo_leg_data_domain_to_api_like_cpp(
161    leg: ProjectedHistoryOrderComboLeg,
162) -> futu_proto::qot_common::ComboLeg {
163    futu_proto::qot_common::ComboLeg {
164        security: futu_proto::qot_common::Security {
165            market: leg.qot_market,
166            code: leg.code,
167        },
168        side: leg.side,
169        qty_ratio: leg.qty_ratio,
170        position_id: leg.position_id,
171        // C++ `_APIServer_Trd_Comm.cpp::ComboLegData_NNToAPI` projects only
172        // security/side/qtyRatio/positionID for order-list rows. `predSide`
173        // is an event-contract write input and is not reconstructed on this
174        // read path. Revisit if that C++ projector starts setting field 5.
175        pred_side: None,
176    }
177}
178
179pub(crate) fn sim_order_proto_to_cached_like_cpp(
180    trd_env: i32,
181    o: &sim_odr_sys_cmn::Order,
182) -> Option<CachedOrder> {
183    let trd_market = project_backend_order_unpacked_trd_market_like_cpp(BackendOrderUnpackFacts {
184        trd_env,
185        has_order_id: o.order_id.is_some(),
186        has_side: o.side.is_some(),
187        market: o.market,
188        has_symbol: o.symbol.is_some(),
189        has_create_time: o.create_time.is_some(),
190        has_expire_time: o.expire_time.is_some(),
191        has_status: o.status.is_some(),
192    })?;
193
194    let order_trade_time_type = o.sup.as_ref().and_then(|s| s.order_trade_time_type);
195    let sup = o.sup.as_ref();
196    let projected = project_backend_trade_read_order_like_cpp(BackendTradeReadOrderFacts {
197        trd_env,
198        sim_option_account: trd_env == 0 && matches!(o.market, Some(7 | 9)),
199        trd_market_ftapi: trd_market,
200        ex_destination: o.exdestination,
201        exchange: o.exchange.as_deref(),
202        order_type_trd_market_ftapi: trd_market,
203        currency: o.currency.map(|c| c as i32),
204        jp_acc_type: o.sub_account.map(|v| v as i32),
205        app_lang: 0,
206        order_id_ex: o.order_id.as_deref(),
207        side: o.side,
208        order_type: o.r#type,
209        security_type: o.security_type,
210        status: o.status,
211        code: o.symbol.as_deref(),
212        stock_name: o.stock_name.as_deref(),
213        qty: o.qty.as_deref(),
214        price: o.price.as_deref(),
215        create_time_micros: o.create_time,
216        update_time_micros: o.update_time,
217        market_id: o.exchange_code,
218        cum_qty: o.cum_qty.as_deref(),
219        avg_fill_price: o.avg_fill_price.as_deref(),
220        last_err_msg: o.last_err_msg.as_deref(),
221        text: o.text.as_deref(),
222        aux_price: o.aux_price.as_deref(),
223        expire_time_micros: o.expire_time,
224        order_amount: o.order_amount.as_deref(),
225        sup: BackendOrderSupFacts {
226            order_trade_time_type,
227            time_in_force: sup.and_then(|value| value.time_in_force),
228            odr_enable_ba_trade: sup.and_then(|value| value.odr_enable_ba_trade),
229            hk_odr_enable_ba_trade: sup.and_then(|value| value.hk_odr_enable_ba_trade),
230            trail_type: sup.and_then(|value| value.trail_type),
231            trail_percent: sup.and_then(|value| value.trail_percent.as_deref()),
232            trail_price: sup.and_then(|value| value.trail_price.as_deref()),
233        },
234        combo: None,
235    });
236
237    Some(CachedOrder {
238        order_id: projected.order_id,
239        order_id_ex: projected.order_id_ex.clone(),
240        code: projected.code,
241        name: projected.name,
242        trd_side: projected.trd_side,
243        order_type: projected.order_type,
244        order_status: projected.order_status,
245        qty: projected.qty,
246        price: projected.price.unwrap_or(0.0),
247        fill_qty: projected.fill_qty,
248        fill_avg_price: projected.fill_avg_price,
249        create_time: projected.create_time,
250        update_time: projected.update_time,
251        last_err_msg: projected.last_err_msg,
252        sec_market: projected.sec_market,
253        create_timestamp: projected.create_timestamp,
254        update_timestamp: projected.update_timestamp,
255        remark: projected.remark,
256        time_in_force: projected.time_in_force,
257        fill_outside_rth: projected.fill_outside_rth,
258        aux_price: projected.aux_price,
259        trail_type: projected.trail_type,
260        trail_value: projected.trail_value,
261        trail_spread: projected.trail_spread,
262        currency: projected.currency,
263        trd_market: projected.trd_market,
264        strategy_type: None,
265        combo_legs: Vec::new(),
266        quote_id: None,
267        backend_order_id: projected.order_id_ex,
268        order_version: o.version.unwrap_or(0) as i32,
269        exchange_code: o.exchange_code.unwrap_or(0) as i32,
270        exchange: o.exchange.clone().unwrap_or_default(),
271        security_type: o.security_type.unwrap_or(0) as i32,
272        session: projected.session,
273        order_trade_time_type,
274        sub_account_id: None,
275        jp_acc_type: projected.jp_acc_type,
276        is_stub: false,
277        is_local_order: false,
278        stub_inserted_at_ms: 0,
279        is_pending_broker_confirm: false,
280    })
281}