1use std::sync::Arc;
5
6use anyhow::{Result, anyhow, bail};
7use futu_net::client::FutuClient;
8use futu_qot::quote_rights::{QuoteRightsProfile, SYS_QUERY_GET_QUOTE_RIGHTS_PROFILE};
9use futu_surface_spec::endpoints::get_delay_statistics::{
10 DEFAULT_QOT_PUSH_STAGE, default_segment_list_vec, default_type_list_vec,
11};
12use prost::Message;
13use serde::Serialize;
14
15#[derive(Serialize)]
16struct GlobalStateOut {
17 market_hk: i32,
18 market_us: i32,
19 market_sh: i32,
20 market_sz: i32,
21 market_hk_future: i32,
22 market_us_future: Option<i32>,
23 market_sg_future: Option<i32>,
24 market_jp_future: Option<i32>,
25 market_sg: Option<i32>,
26 market_my: Option<i32>,
27 market_jp: Option<i32>,
28 qot_logined: bool,
29 trd_logined: bool,
30 server_ver: i32,
31 server_build_no: i32,
32 server_time: i64,
33 conn_id: Option<u64>,
34 time: i64, local_time: Option<f64>,
37 program_status: Option<serde_json::Value>, qot_svr_ip_addr: Option<String>,
39 trd_svr_ip_addr: Option<String>,
40 market_bond: Option<i32>,
41 market_global_index: Option<i32>,
42 market_sg_security: Option<i32>,
43 market_stock_connect: Option<i32>,
44 market_digital_ccy: Option<i32>,
45 market_treasury_yield: Option<i32>,
46 market_fund: Option<i32>,
47}
48
49pub async fn get_global_state(client: &Arc<FutuClient>) -> Result<String> {
51 let req = futu_proto::get_global_state::Request {
52 c2s: futu_proto::get_global_state::C2s { user_id: 0 },
53 };
54 let body = req.encode_to_vec();
55 let frame = client
56 .request(futu_core::proto_id::GET_GLOBAL_STATE, body)
57 .await?;
58 let resp = futu_proto::get_global_state::Response::decode(frame.body.as_ref())
59 .map_err(|e| anyhow!("decode global_state: {e}"))?;
60 if resp.ret_type != 0 {
61 bail!(
62 "global_state ret_type={} msg={:?}",
63 resp.ret_type,
64 resp.ret_msg
65 );
66 }
67 let s = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
68 let out = GlobalStateOut {
69 market_hk: s.market_hk,
70 market_us: s.market_us,
71 market_sh: s.market_sh,
72 market_sz: s.market_sz,
73 market_hk_future: s.market_hk_future,
74 market_us_future: s.market_us_future,
75 market_sg_future: s.market_sg_future,
76 market_jp_future: s.market_jp_future,
77 market_sg: s.market_sg,
78 market_my: s.market_my,
79 market_jp: s.market_jp,
80 qot_logined: s.qot_logined,
81 trd_logined: s.trd_logined,
82 server_ver: s.server_ver,
83 server_build_no: s.server_build_no,
84 server_time: s.time,
85 conn_id: s.conn_id,
86 time: s.time,
88 local_time: s.local_time,
89 program_status: s.program_status.and_then(|p| serde_json::to_value(p).ok()),
90 qot_svr_ip_addr: s.qot_svr_ip_addr,
91 trd_svr_ip_addr: s.trd_svr_ip_addr,
92 market_bond: s.market_bond,
93 market_global_index: s.market_global_index,
94 market_sg_security: s.market_sg_security,
95 market_stock_connect: s.market_stock_connect,
96 market_digital_ccy: s.market_digital_ccy,
97 market_treasury_yield: s.market_treasury_yield,
98 market_fund: s.market_fund,
99 };
100 Ok(serde_json::to_string_pretty(&out)?)
101}
102
103#[derive(Serialize)]
104struct UserInfoOut {
105 nick_name: Option<String>,
106 user_id: Option<i64>,
107 user_attribution: Option<i32>,
108 hk_qot_right: Option<i32>,
109 us_qot_right: Option<i32>,
110 cn_qot_right: Option<i32>,
111 cc_qot_right: Option<i32>,
112 sg_stock_qot_right: Option<i32>,
113 my_stock_qot_right: Option<i32>,
114 jp_stock_qot_right: Option<i32>,
115 sub_quota: Option<i32>,
116 history_kl_quota: Option<i32>,
117}
118
119pub async fn get_user_info(client: &Arc<FutuClient>) -> Result<String> {
121 let req = futu_proto::get_user_info::Request {
122 c2s: futu_proto::get_user_info::C2s { flag: None },
123 };
124 let body = req.encode_to_vec();
125 let frame = client
126 .request(futu_core::proto_id::GET_USER_INFO, body)
127 .await?;
128 let resp = futu_proto::get_user_info::Response::decode(frame.body.as_ref())
129 .map_err(|e| anyhow!("decode user_info: {e}"))?;
130 if resp.ret_type != 0 {
131 bail!(
132 "user_info ret_type={} msg={:?}",
133 resp.ret_type,
134 resp.ret_msg
135 );
136 }
137 let s = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
138 let out = UserInfoOut {
139 nick_name: s.nick_name,
140 user_id: s.user_id,
141 user_attribution: s.user_attribution,
142 hk_qot_right: s.hk_qot_right,
143 us_qot_right: s.us_qot_right,
144 cn_qot_right: s.cn_qot_right,
145 cc_qot_right: s.cc_qot_right,
146 sg_stock_qot_right: s.sg_stock_qot_right,
147 my_stock_qot_right: s.my_stock_qot_right,
148 jp_stock_qot_right: s.jp_stock_qot_right,
149 sub_quota: s.sub_quota,
150 history_kl_quota: s.history_kl_quota,
151 };
152 Ok(serde_json::to_string_pretty(&out)?)
153}
154
155async fn refresh_quote_rights(client: &Arc<FutuClient>) -> Result<()> {
156 let req = futu_proto::test_cmd::Request {
157 c2s: futu_proto::test_cmd::C2s {
158 cmd: "request_highest_quote_right".to_string(),
159 param_str: None,
160 param_bytes: None,
161 },
162 };
163 let frame = client
164 .request(futu_core::proto_id::TEST_CMD, req.encode_to_vec())
165 .await?;
166 let resp = futu_proto::test_cmd::Response::decode(frame.body.as_ref())
167 .map_err(|e| anyhow!("decode request_highest_quote_right: {e}"))?;
168 if resp.ret_type != 0 {
169 bail!(
170 "request_highest_quote_right ret_type={} msg={:?}",
171 resp.ret_type,
172 resp.ret_msg
173 );
174 }
175 Ok(())
176}
177
178pub async fn get_quote_rights(client: &Arc<FutuClient>, refresh: bool) -> Result<String> {
180 if refresh {
181 refresh_quote_rights(client).await?;
182 }
183 let req = futu_proto::test_cmd::Request {
184 c2s: futu_proto::test_cmd::C2s {
185 cmd: SYS_QUERY_GET_QUOTE_RIGHTS_PROFILE.to_string(),
186 param_str: None,
187 param_bytes: None,
188 },
189 };
190 let frame = client
191 .request(futu_core::proto_id::TEST_CMD, req.encode_to_vec())
192 .await?;
193 let resp = futu_proto::test_cmd::Response::decode(frame.body.as_ref())
194 .map_err(|e| anyhow!("decode {SYS_QUERY_GET_QUOTE_RIGHTS_PROFILE}: {e}"))?;
195 if resp.ret_type != 0 {
196 bail!(
197 "{SYS_QUERY_GET_QUOTE_RIGHTS_PROFILE} ret_type={} msg={:?}",
198 resp.ret_type,
199 resp.ret_msg
200 );
201 }
202 let json = resp
203 .s2c
204 .and_then(|s| s.result_str)
205 .ok_or_else(|| anyhow!("{SYS_QUERY_GET_QUOTE_RIGHTS_PROFILE}: missing result_str"))?;
206 let profile: QuoteRightsProfile = serde_json::from_str(&json)
207 .map_err(|e| anyhow!("parse {SYS_QUERY_GET_QUOTE_RIGHTS_PROFILE} profile: {e}"))?;
208 Ok(serde_json::to_string_pretty(&profile)?)
209}
210
211pub async fn get_delay_statistics(client: &Arc<FutuClient>) -> Result<String> {
216 let req = futu_proto::get_delay_statistics::Request {
217 c2s: futu_proto::get_delay_statistics::C2s {
218 type_list: default_type_list_vec(),
219 qot_push_stage: Some(DEFAULT_QOT_PUSH_STAGE),
220 segment_list: default_segment_list_vec(),
221 },
222 };
223 let body = req.encode_to_vec();
224 let frame = client
225 .request(futu_core::proto_id::GET_DELAY_STATISTICS, body)
226 .await?;
227 let resp = futu_proto::get_delay_statistics::Response::decode(frame.body.as_ref())
228 .map_err(|e| anyhow!("decode delay_statistics: {e}"))?;
229 if resp.ret_type != 0 {
230 bail!(
231 "delay_statistics ret_type={} msg={:?}",
232 resp.ret_type,
233 resp.ret_msg
234 );
235 }
236 let s = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
237 Ok(serde_json::to_string_pretty(&serde_json::json!({
238 "qot_push_statistics_list_len": s.qot_push_statistics_list.len(),
239 "req_reply_statistics_list_len": s.req_reply_statistics_list.len(),
240 "place_order_statistics_list_len": s.place_order_statistics_list.len(),
241 "_note": "summary only; for raw per-segment samples use REST /api/delay-statistics",
242 }))?)
243}
244
245