futucli/cmd/proto_json/
combo.rs1use std::collections::hash_map::DefaultHasher;
5use std::hash::{Hash, Hasher};
6use std::sync::atomic::{AtomicU32, Ordering};
7
8use anyhow::{Result, anyhow, bail};
9use prost::Message;
10
11use crate::common::connect_gateway;
12use crate::output::OutputFormat;
13
14use super::contract::{parse_combo_max_c2s_json, parse_place_combo_c2s_json};
15use super::transport::{ensure_success, print_proto_response, send_proto};
16
17static PACKET_SERIAL: AtomicU32 = AtomicU32::new(1);
18
19pub async fn run_combo_max_trd_qtys(
20 gateway: &str,
21 c2s_json: &str,
22 output: OutputFormat,
23) -> Result<()> {
24 let c2s = parse_combo_max_c2s_json(c2s_json)?;
25 let request = futu_proto::trd_get_combo_max_trd_qtys::Request { c2s };
26 let response: futu_proto::trd_get_combo_max_trd_qtys::Response = send_proto(
27 gateway,
28 "futucli-combo-max-trd-qtys",
29 futu_core::proto_id::TRD_GET_COMBO_MAX_TRD_QTYS,
30 request,
31 )
32 .await?;
33 ensure_success(
34 "combo-max-trd-qtys",
35 response.ret_type,
36 response.ret_msg.as_deref(),
37 response.err_code,
38 )?;
39 print_proto_response(output, &response)
40}
41
42pub async fn run_place_combo_order(
43 gateway: &str,
44 c2s_json: &str,
45 confirm: bool,
46 idempotency_key: Option<String>,
47 output: OutputFormat,
48) -> Result<()> {
49 let mut c2s = parse_place_combo_c2s_json(c2s_json)?;
50
51 ensure_combo_order_confirmed(c2s.header.trd_env, confirm)?;
52
53 let (client, _rx) = connect_gateway(gateway, "futucli-combo-order").await?;
54 c2s.packet_id = match idempotency_key.as_deref() {
55 Some(key) => packet_id_for_idempotency_key(key),
56 None => {
57 let conn_id = client
58 .conn_id()
59 .ok_or_else(|| anyhow!("combo-order missing InitConnect conn_id"))?;
60 next_packet_id(conn_id)
61 }
62 };
63
64 let request = futu_proto::trd_place_combo_order::Request { c2s };
65 let frame = client
66 .request(
67 futu_core::proto_id::TRD_PLACE_COMBO_ORDER,
68 request.encode_to_vec(),
69 )
70 .await?;
71 let response = futu_proto::trd_place_combo_order::Response::decode(frame.body.as_ref())
72 .map_err(|err| anyhow!("decode combo-order response: {err}"))?;
73 ensure_success(
74 "combo-order",
75 response.ret_type,
76 response.ret_msg.as_deref(),
77 response.err_code,
78 )?;
79 print_proto_response(output, &response)
80}
81
82fn next_packet_id(conn_id: u64) -> futu_proto::common::PacketId {
83 let serial_no = PACKET_SERIAL.fetch_add(1, Ordering::Relaxed);
84 futu_proto::common::PacketId { conn_id, serial_no }
85}
86
87pub(super) fn ensure_combo_order_confirmed(trd_env: i32, confirm: bool) -> Result<()> {
88 if trd_env == 1 && !confirm {
89 bail!("combo-order real env requires --confirm");
90 }
91 Ok(())
92}
93
94pub(super) fn packet_id_for_idempotency_key(key: &str) -> futu_proto::common::PacketId {
98 let mut hasher = DefaultHasher::new();
99 key.hash(&mut hasher);
100 futu_proto::common::PacketId {
101 conn_id: hasher.finish(),
102 serial_no: 0,
103 }
104}