1use futu_core::error::{FutuError, Result};
4use futu_core::proto_id;
5use futu_net::client::FutuClient;
6
7use crate::types::{QotMarket, Security};
8
9#[derive(Debug, Clone)]
13pub struct TradeDate {
14 pub time: String,
15 pub timestamp: f64,
16}
17
18pub async fn get_trade_date(
20 client: &FutuClient,
21 market: QotMarket,
22 begin_time: &str,
23 end_time: &str,
24) -> Result<Vec<TradeDate>> {
25 let req = futu_proto::qot_request_trade_date::Request {
26 c2s: futu_proto::qot_request_trade_date::C2s {
27 market: market as i32,
28 begin_time: begin_time.to_string(),
29 end_time: end_time.to_string(),
30 security: None,
31 header: None,
32 },
33 };
34
35 let body = prost::Message::encode_to_vec(&req);
36 let resp_frame = client.request(proto_id::QOT_GET_TRADE_DATE, body).await?;
37 let resp: futu_proto::qot_request_trade_date::Response =
38 prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
39
40 if resp.ret_type != 0 {
41 return Err(crate::server_err(resp.ret_type, resp.ret_msg));
42 }
43
44 let s2c = resp
45 .s2c
46 .ok_or(FutuError::Codec("missing s2c in GetTradeDate".into()))?;
47
48 Ok(s2c
49 .trade_date_list
50 .iter()
51 .map(|d| TradeDate {
52 time: d.time.clone(),
53 timestamp: d.timestamp.unwrap_or(0.0),
54 })
55 .collect())
56}
57
58pub async fn get_suspend(
62 client: &FutuClient,
63 securities: &[Security],
64 begin_time: &str,
65 end_time: &str,
66) -> Result<futu_proto::qot_get_suspend::S2c> {
67 let req = futu_proto::qot_get_suspend::Request {
68 c2s: futu_proto::qot_get_suspend::C2s {
69 security_list: securities.iter().map(|s| s.to_proto()).collect(),
70 begin_time: begin_time.to_string(),
71 end_time: end_time.to_string(),
72 header: None,
73 },
74 };
75
76 let body = prost::Message::encode_to_vec(&req);
77 let resp_frame = client.request(proto_id::QOT_GET_SUSPEND, body).await?;
78 let resp: futu_proto::qot_get_suspend::Response =
79 prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
80
81 if resp.ret_type != 0 {
82 return Err(crate::server_err(resp.ret_type, resp.ret_msg));
83 }
84
85 resp.s2c
86 .ok_or(FutuError::Codec("missing s2c in GetSuspend".into()))
87}
88
89pub async fn get_rehab(
93 client: &FutuClient,
94 security: &Security,
95) -> Result<futu_proto::qot_request_rehab::S2c> {
96 let req = futu_proto::qot_request_rehab::Request {
97 c2s: futu_proto::qot_request_rehab::C2s {
98 security: security.to_proto(),
99 header: None,
100 },
101 };
102
103 let body = prost::Message::encode_to_vec(&req);
104 let resp_frame = client.request(proto_id::QOT_GET_REHAB, body).await?;
105 let resp: futu_proto::qot_request_rehab::Response =
106 prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
107
108 if resp.ret_type != 0 {
109 return Err(crate::server_err(resp.ret_type, resp.ret_msg));
110 }
111
112 resp.s2c
113 .ok_or(FutuError::Codec("missing s2c in GetRehab".into()))
114}
115
116#[derive(Debug, Clone)]
120pub struct PlateInfo {
121 pub security: Security,
122 pub name: String,
123 pub plate_type: Option<i32>,
124}
125
126impl PlateInfo {
127 pub fn from_proto(p: &futu_proto::qot_common::PlateInfo) -> Self {
128 Self {
129 security: Security::from_proto(&p.plate),
130 name: p.name.clone(),
131 plate_type: p.plate_type,
132 }
133 }
134}
135
136pub async fn get_plate_set(
138 client: &FutuClient,
139 market: QotMarket,
140 plate_set_type: i32,
141) -> Result<Vec<PlateInfo>> {
142 let req = futu_proto::qot_get_plate_set::Request {
143 c2s: futu_proto::qot_get_plate_set::C2s {
144 market: market as i32,
145 plate_set_type,
146 header: None,
147 },
148 };
149
150 let body = prost::Message::encode_to_vec(&req);
151 let resp_frame = client.request(proto_id::QOT_GET_PLATE_SET, body).await?;
152 let resp: futu_proto::qot_get_plate_set::Response =
153 prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
154
155 if resp.ret_type != 0 {
156 return Err(crate::server_err(resp.ret_type, resp.ret_msg));
157 }
158
159 let s2c = resp
160 .s2c
161 .ok_or(FutuError::Codec("missing s2c in GetPlateSet".into()))?;
162
163 Ok(s2c
164 .plate_info_list
165 .iter()
166 .map(PlateInfo::from_proto)
167 .collect())
168}
169
170pub async fn get_plate_security(
172 client: &FutuClient,
173 plate: &Security,
174) -> Result<Vec<crate::static_info::SecurityStaticInfo>> {
175 let req = futu_proto::qot_get_plate_security::Request {
176 c2s: futu_proto::qot_get_plate_security::C2s {
177 plate: plate.to_proto(),
178 sort_field: None,
179 ascend: None,
180 header: None,
181 },
182 };
183
184 let body = prost::Message::encode_to_vec(&req);
185 let resp_frame = client
186 .request(proto_id::QOT_GET_PLATE_SECURITY, body)
187 .await?;
188 let resp: futu_proto::qot_get_plate_security::Response =
189 prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
190
191 if resp.ret_type != 0 {
192 return Err(crate::server_err(resp.ret_type, resp.ret_msg));
193 }
194
195 let s2c = resp
196 .s2c
197 .ok_or(FutuError::Codec("missing s2c in GetPlateSecurity".into()))?;
198
199 Ok(s2c
200 .static_info_list
201 .iter()
202 .map(crate::static_info::SecurityStaticInfo::from_proto)
203 .collect())
204}
205
206pub async fn get_owner_plate(
208 client: &FutuClient,
209 securities: &[Security],
210) -> Result<futu_proto::qot_get_owner_plate::S2c> {
211 let req = futu_proto::qot_get_owner_plate::Request {
212 c2s: futu_proto::qot_get_owner_plate::C2s {
213 security_list: securities.iter().map(|s| s.to_proto()).collect(),
214 header: None,
215 },
216 };
217
218 let body = prost::Message::encode_to_vec(&req);
219 let resp_frame = client.request(proto_id::QOT_GET_OWNER_PLATE, body).await?;
220 let resp: futu_proto::qot_get_owner_plate::Response =
221 prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
222
223 if resp.ret_type != 0 {
224 return Err(crate::server_err(resp.ret_type, resp.ret_msg));
225 }
226
227 resp.s2c
228 .ok_or(FutuError::Codec("missing s2c in GetOwnerPlate".into()))
229}
230
231pub async fn get_reference(
233 client: &FutuClient,
234 security: &Security,
235 reference_type: i32,
236) -> Result<Vec<crate::static_info::SecurityStaticInfo>> {
237 let req = futu_proto::qot_get_reference::Request {
238 c2s: futu_proto::qot_get_reference::C2s {
239 security: security.to_proto(),
240 reference_type,
241 header: None,
242 },
243 };
244
245 let body = prost::Message::encode_to_vec(&req);
246 let resp_frame = client.request(proto_id::QOT_GET_REFERENCE, body).await?;
247 let resp: futu_proto::qot_get_reference::Response =
248 prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
249
250 if resp.ret_type != 0 {
251 return Err(crate::server_err(resp.ret_type, resp.ret_msg));
252 }
253
254 let s2c = resp
255 .s2c
256 .ok_or(FutuError::Codec("missing s2c in GetReference".into()))?;
257
258 Ok(s2c
259 .static_info_list
260 .iter()
261 .map(crate::static_info::SecurityStaticInfo::from_proto)
262 .collect())
263}
264
265pub async fn get_option_chain(
269 client: &FutuClient,
270 owner: &Security,
271 begin_time: &str,
272 end_time: &str,
273 option_type: Option<i32>,
274 option_cond: Option<i32>,
275 data_filter: Option<futu_proto::qot_get_option_chain::DataFilter>,
276) -> Result<futu_proto::qot_get_option_chain::S2c> {
277 let req = futu_proto::qot_get_option_chain::Request {
278 c2s: futu_proto::qot_get_option_chain::C2s {
279 owner: owner.to_proto(),
280 index_option_type: None,
281 r#type: option_type,
282 condition: option_cond,
283 begin_time: begin_time.to_string(),
284 end_time: end_time.to_string(),
285 data_filter, header: None,
287 },
288 };
289
290 let body = prost::Message::encode_to_vec(&req);
291 let resp_frame = client.request(proto_id::QOT_GET_OPTION_CHAIN, body).await?;
292 let resp: futu_proto::qot_get_option_chain::Response =
293 prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
294
295 if resp.ret_type != 0 {
296 return Err(crate::server_err(resp.ret_type, resp.ret_msg));
297 }
298
299 resp.s2c
300 .ok_or(FutuError::Codec("missing s2c in GetOptionChain".into()))
301}
302
303pub async fn get_holding_change_list(
307 client: &FutuClient,
308 security: &Security,
309 holder_category: i32,
310 begin_time: Option<&str>,
311 end_time: Option<&str>,
312) -> Result<futu_proto::qot_get_holding_change_list::S2c> {
313 let req = futu_proto::qot_get_holding_change_list::Request {
314 c2s: futu_proto::qot_get_holding_change_list::C2s {
315 security: security.to_proto(),
316 holder_category,
317 begin_time: begin_time.map(|s| s.to_string()),
318 end_time: end_time.map(|s| s.to_string()),
319 header: None,
320 },
321 };
322
323 let body = prost::Message::encode_to_vec(&req);
324 let resp_frame = client
325 .request(proto_id::QOT_GET_HOLDING_CHANGE_LIST, body)
326 .await?;
327 let resp: futu_proto::qot_get_holding_change_list::Response =
328 prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
329
330 if resp.ret_type != 0 {
331 return Err(crate::server_err(resp.ret_type, resp.ret_msg));
332 }
333
334 resp.s2c.ok_or(FutuError::Codec(
335 "missing s2c in GetHoldingChangeList".into(),
336 ))
337}
338
339pub async fn get_sub_info(
346 client: &FutuClient,
347 is_req_all_conn: bool,
348) -> Result<futu_proto::qot_get_sub_info::S2c> {
349 let req = futu_proto::qot_get_sub_info::Request {
350 c2s: futu_proto::qot_get_sub_info::C2s {
351 is_req_all_conn: Some(is_req_all_conn),
352 header: None,
353 },
354 };
355
356 let body = prost::Message::encode_to_vec(&req);
357 let resp_frame = client.request(proto_id::QOT_GET_SUB_INFO, body).await?;
358 let resp: futu_proto::qot_get_sub_info::Response =
359 prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
360
361 if resp.ret_type != 0 {
362 return Err(crate::server_err(resp.ret_type, resp.ret_msg));
363 }
364
365 resp.s2c
366 .ok_or(FutuError::Codec("missing s2c in GetSubInfo".into()))
367}
368
369pub async fn reg_qot_push(
371 client: &FutuClient,
372 securities: &[Security],
373 sub_types: &[crate::types::SubType],
374 is_reg: bool,
375 is_first_push: Option<bool>,
376) -> Result<()> {
377 let req = futu_proto::qot_reg_qot_push::Request {
378 c2s: futu_proto::qot_reg_qot_push::C2s {
379 security_list: securities.iter().map(|s| s.to_proto()).collect(),
380 sub_type_list: sub_types.iter().map(|t| *t as i32).collect(),
381 rehab_type_list: vec![],
382 is_reg_or_un_reg: is_reg,
383 is_first_push,
384 header: None,
385 },
386 };
387
388 let body = prost::Message::encode_to_vec(&req);
389 let resp_frame = client.request(proto_id::QOT_REG_QOT_PUSH, body).await?;
390 let resp: futu_proto::qot_reg_qot_push::Response =
391 prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
392
393 if resp.ret_type != 0 {
394 return Err(crate::server_err(resp.ret_type, resp.ret_msg));
395 }
396
397 Ok(())
398}