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        backend_order_type_raw: o.r#type,
116        lot_type: o.lot_type,
117        order_status: projected.order_status,
118        qty: projected.qty,
119        price: projected.price.unwrap_or(0.0),
120        fill_qty: projected.fill_qty,
121        fill_avg_price: projected.fill_avg_price,
122        create_time: projected.create_time,
123        update_time: projected.update_time,
124        last_err_msg: projected.last_err_msg,
125        sec_market: projected.sec_market,
126        create_timestamp: projected.create_timestamp,
127        update_timestamp: projected.update_timestamp,
128        remark: projected.remark,
129        backend_text: o.text.clone(),
130        time_in_force: projected.time_in_force,
131        fill_outside_rth: projected.fill_outside_rth,
132        aux_price: projected.aux_price,
133        trail_type: projected.trail_type,
134        trail_value: projected.trail_value,
135        trail_spread: projected.trail_spread,
136        currency: projected.currency,
137        trd_market: projected.trd_market,
138        strategy_type: projected.strategy_type,
139        combo_legs: projected
140            .combo_legs
141            .into_iter()
142            .map(combo_leg_data_domain_to_api_like_cpp)
143            .collect(),
144        parent_order_id_ex: o.parent_order_id.clone(),
145        legacy_group_id: o.group_id.clone(),
146        relation_type: o.relation_type,
147        order_group_id: o.order_group_id.clone(),
148        order_group_type_raw: o.group_type,
149        quote_id: wire_extensions
150            .event_contract_quote_id
151            .map(ToOwned::to_owned),
152        backend_order_id: o.order_id.clone().unwrap_or_default(),
153        order_version: o.version.unwrap_or(0) as i32,
154        exchange_code: o.exchange_code.unwrap_or(0) as i32,
155        exchange: o.exchange.clone().unwrap_or_default(),
156        security_type: o.security_type.unwrap_or(0) as i32,
157        session: projected.session,
158        order_trade_time_type,
159        sub_account_id: o.sub_account_id,
160        jp_acc_type: projected.jp_acc_type,
161        is_stub: false,
162        is_local_order: false,
163        stub_inserted_at_ms: 0,
164        is_pending_broker_confirm: false,
165    })
166}
167
168fn combo_leg_data_domain_to_api_like_cpp(
169    leg: ProjectedHistoryOrderComboLeg,
170) -> futu_proto::qot_common::ComboLeg {
171    futu_proto::qot_common::ComboLeg {
172        security: futu_proto::qot_common::Security {
173            market: leg.qot_market,
174            code: leg.code,
175        },
176        side: leg.side,
177        qty_ratio: leg.qty_ratio,
178        position_id: leg.position_id,
179        // C++ `_APIServer_Trd_Comm.cpp::ComboLegData_NNToAPI` projects only
180        // security/side/qtyRatio/positionID for order-list rows. `predSide`
181        // is an event-contract write input and is not reconstructed on this
182        // read path. Revisit if that C++ projector starts setting field 5.
183        pred_side: None,
184    }
185}
186
187pub(crate) fn sim_order_proto_to_cached_like_cpp(
188    trd_env: i32,
189    o: &sim_odr_sys_cmn::Order,
190) -> Option<CachedOrder> {
191    let trd_market = project_backend_order_unpacked_trd_market_like_cpp(BackendOrderUnpackFacts {
192        trd_env,
193        has_order_id: o.order_id.is_some(),
194        has_side: o.side.is_some(),
195        market: o.market,
196        has_symbol: o.symbol.is_some(),
197        has_create_time: o.create_time.is_some(),
198        has_expire_time: o.expire_time.is_some(),
199        has_status: o.status.is_some(),
200    })?;
201
202    let order_trade_time_type = o.sup.as_ref().and_then(|s| s.order_trade_time_type);
203    let sup = o.sup.as_ref();
204    let projected = project_backend_trade_read_order_like_cpp(BackendTradeReadOrderFacts {
205        trd_env,
206        sim_option_account: trd_env == 0 && matches!(o.market, Some(7 | 9)),
207        trd_market_ftapi: trd_market,
208        ex_destination: o.exdestination,
209        exchange: o.exchange.as_deref(),
210        order_type_trd_market_ftapi: trd_market,
211        currency: o.currency.map(|c| c as i32),
212        jp_acc_type: o.sub_account.map(|v| v as i32),
213        app_lang: 0,
214        order_id_ex: o.order_id.as_deref(),
215        side: o.side,
216        order_type: o.r#type,
217        security_type: o.security_type,
218        status: o.status,
219        code: o.symbol.as_deref(),
220        stock_name: o.stock_name.as_deref(),
221        qty: o.qty.as_deref(),
222        price: o.price.as_deref(),
223        create_time_micros: o.create_time,
224        update_time_micros: o.update_time,
225        market_id: o.exchange_code,
226        cum_qty: o.cum_qty.as_deref(),
227        avg_fill_price: o.avg_fill_price.as_deref(),
228        last_err_msg: o.last_err_msg.as_deref(),
229        text: o.text.as_deref(),
230        aux_price: o.aux_price.as_deref(),
231        expire_time_micros: o.expire_time,
232        order_amount: o.order_amount.as_deref(),
233        sup: BackendOrderSupFacts {
234            order_trade_time_type,
235            time_in_force: sup.and_then(|value| value.time_in_force),
236            odr_enable_ba_trade: sup.and_then(|value| value.odr_enable_ba_trade),
237            hk_odr_enable_ba_trade: sup.and_then(|value| value.hk_odr_enable_ba_trade),
238            trail_type: sup.and_then(|value| value.trail_type),
239            trail_percent: sup.and_then(|value| value.trail_percent.as_deref()),
240            trail_price: sup.and_then(|value| value.trail_price.as_deref()),
241        },
242        combo: None,
243    });
244
245    Some(CachedOrder {
246        order_id: projected.order_id,
247        order_id_ex: projected.order_id_ex.clone(),
248        code: projected.code,
249        name: projected.name,
250        trd_side: projected.trd_side,
251        order_type: projected.order_type,
252        backend_order_type_raw: o.r#type,
253        lot_type: o.lot_type,
254        order_status: projected.order_status,
255        qty: projected.qty,
256        price: projected.price.unwrap_or(0.0),
257        fill_qty: projected.fill_qty,
258        fill_avg_price: projected.fill_avg_price,
259        create_time: projected.create_time,
260        update_time: projected.update_time,
261        last_err_msg: projected.last_err_msg,
262        sec_market: projected.sec_market,
263        create_timestamp: projected.create_timestamp,
264        update_timestamp: projected.update_timestamp,
265        remark: projected.remark,
266        backend_text: o.text.clone(),
267        time_in_force: projected.time_in_force,
268        fill_outside_rth: projected.fill_outside_rth,
269        aux_price: projected.aux_price,
270        trail_type: projected.trail_type,
271        trail_value: projected.trail_value,
272        trail_spread: projected.trail_spread,
273        currency: projected.currency,
274        trd_market: projected.trd_market,
275        strategy_type: None,
276        combo_legs: Vec::new(),
277        parent_order_id_ex: o.parent_order_id.clone(),
278        legacy_group_id: o.group_id.clone(),
279        relation_type: o.relation_type,
280        order_group_id: o.order_group_id.clone(),
281        order_group_type_raw: o.group_type,
282        quote_id: None,
283        backend_order_id: projected.order_id_ex,
284        order_version: o.version.unwrap_or(0) as i32,
285        exchange_code: o.exchange_code.unwrap_or(0) as i32,
286        exchange: o.exchange.clone().unwrap_or_default(),
287        security_type: o.security_type.unwrap_or(0) as i32,
288        session: projected.session,
289        order_trade_time_type,
290        sub_account_id: None,
291        jp_acc_type: projected.jp_acc_type,
292        is_stub: false,
293        is_local_order: false,
294        stub_inserted_at_ms: 0,
295        is_pending_broker_confirm: false,
296    })
297}