1use std::sync::Arc;
5
6use anyhow::{Result, anyhow, bail};
7use futu_core::qot_quote_rights::{QuoteRightsProfile, SYS_QUERY_GET_QUOTE_RIGHTS_PROFILE};
8use futu_net::client::FutuClient;
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
155pub async fn verification(
156 client: &Arc<FutuClient>,
157 c2s: futu_proto::verification::C2s,
158) -> Result<String> {
159 let request = futu_proto::verification::Request { c2s };
160 let frame = client
161 .request(futu_core::proto_id::VERIFICATION, request.encode_to_vec())
162 .await?;
163 let response = futu_proto::verification::Response::decode(frame.body.as_ref())
164 .map_err(|error| anyhow!("decode Verification: {error}"))?;
165 if response.ret_type != 0 {
166 bail!(
167 "Verification ret_type={} msg={:?}",
168 response.ret_type,
169 response.ret_msg
170 );
171 }
172 Ok(serde_json::json!({
173 "ret_type": response.ret_type,
174 "ret_msg": response.ret_msg,
175 })
176 .to_string())
177}
178
179async fn refresh_quote_rights(client: &Arc<FutuClient>) -> Result<()> {
180 let req = futu_proto::test_cmd::Request {
181 c2s: futu_proto::test_cmd::C2s {
182 cmd: "request_highest_quote_right".to_string(),
183 param_str: None,
184 param_bytes: None,
185 },
186 };
187 let frame = client
188 .request(futu_core::proto_id::TEST_CMD, req.encode_to_vec())
189 .await?;
190 let resp = futu_proto::test_cmd::Response::decode(frame.body.as_ref())
191 .map_err(|e| anyhow!("decode request_highest_quote_right: {e}"))?;
192 if resp.ret_type != 0 {
193 bail!(
194 "request_highest_quote_right ret_type={} msg={:?}",
195 resp.ret_type,
196 resp.ret_msg
197 );
198 }
199 Ok(())
200}
201
202pub async fn get_quote_rights(client: &Arc<FutuClient>, refresh: bool) -> Result<String> {
204 if refresh {
205 refresh_quote_rights(client).await?;
206 }
207 let req = futu_proto::test_cmd::Request {
208 c2s: futu_proto::test_cmd::C2s {
209 cmd: SYS_QUERY_GET_QUOTE_RIGHTS_PROFILE.to_string(),
210 param_str: None,
211 param_bytes: None,
212 },
213 };
214 let frame = client
215 .request(futu_core::proto_id::TEST_CMD, req.encode_to_vec())
216 .await?;
217 let resp = futu_proto::test_cmd::Response::decode(frame.body.as_ref())
218 .map_err(|e| anyhow!("decode {SYS_QUERY_GET_QUOTE_RIGHTS_PROFILE}: {e}"))?;
219 if resp.ret_type != 0 {
220 bail!(
221 "{SYS_QUERY_GET_QUOTE_RIGHTS_PROFILE} ret_type={} msg={:?}",
222 resp.ret_type,
223 resp.ret_msg
224 );
225 }
226 let json = resp
227 .s2c
228 .and_then(|s| s.result_str)
229 .ok_or_else(|| anyhow!("{SYS_QUERY_GET_QUOTE_RIGHTS_PROFILE}: missing result_str"))?;
230 let profile: QuoteRightsProfile = serde_json::from_str(&json)
231 .map_err(|e| anyhow!("parse {SYS_QUERY_GET_QUOTE_RIGHTS_PROFILE} profile: {e}"))?;
232 Ok(serde_json::to_string_pretty(&profile)?)
233}
234
235pub async fn get_delay_statistics(client: &Arc<FutuClient>) -> Result<String> {
240 let req = futu_proto::get_delay_statistics::Request {
241 c2s: futu_proto::get_delay_statistics::C2s {
242 type_list: default_type_list_vec(),
243 qot_push_stage: Some(DEFAULT_QOT_PUSH_STAGE),
244 segment_list: default_segment_list_vec(),
245 },
246 };
247 let body = req.encode_to_vec();
248 let frame = client
249 .request(futu_core::proto_id::GET_DELAY_STATISTICS, body)
250 .await?;
251 let resp = futu_proto::get_delay_statistics::Response::decode(frame.body.as_ref())
252 .map_err(|e| anyhow!("decode delay_statistics: {e}"))?;
253 if resp.ret_type != 0 {
254 bail!(
255 "delay_statistics ret_type={} msg={:?}",
256 resp.ret_type,
257 resp.ret_msg
258 );
259 }
260 let s = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
261 Ok(serde_json::to_string_pretty(&serde_json::json!({
262 "qot_push_statistics_list_len": s.qot_push_statistics_list.len(),
263 "req_reply_statistics_list_len": s.req_reply_statistics_list.len(),
264 "place_order_statistics_list_len": s.place_order_statistics_list.len(),
265 "_note": "summary only; for raw per-segment samples use REST /api/delay-statistics",
266 }))?)
267}
268
269