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