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(FutuError::ServerError {
42 ret_type: resp.ret_type,
43 msg: resp.ret_msg.unwrap_or_default(),
44 });
45 }
46
47 let s2c = resp
48 .s2c
49 .ok_or(FutuError::Codec("missing s2c in GetTradeDate".into()))?;
50
51 Ok(s2c
52 .trade_date_list
53 .iter()
54 .map(|d| TradeDate {
55 time: d.time.clone(),
56 timestamp: d.timestamp.unwrap_or(0.0),
57 })
58 .collect())
59}
60
61pub async fn get_suspend(
65 client: &FutuClient,
66 securities: &[Security],
67 begin_time: &str,
68 end_time: &str,
69) -> Result<futu_proto::qot_get_suspend::S2c> {
70 let req = futu_proto::qot_get_suspend::Request {
71 c2s: futu_proto::qot_get_suspend::C2s {
72 security_list: securities.iter().map(|s| s.to_proto()).collect(),
73 begin_time: begin_time.to_string(),
74 end_time: end_time.to_string(),
75 header: None,
76 },
77 };
78
79 let body = prost::Message::encode_to_vec(&req);
80 let resp_frame = client.request(proto_id::QOT_GET_SUSPEND, body).await?;
81 let resp: futu_proto::qot_get_suspend::Response =
82 prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
83
84 if resp.ret_type != 0 {
85 return Err(FutuError::ServerError {
86 ret_type: resp.ret_type,
87 msg: resp.ret_msg.unwrap_or_default(),
88 });
89 }
90
91 resp.s2c
92 .ok_or(FutuError::Codec("missing s2c in GetSuspend".into()))
93}
94
95pub async fn get_rehab(
99 client: &FutuClient,
100 security: &Security,
101) -> Result<futu_proto::qot_request_rehab::S2c> {
102 let req = futu_proto::qot_request_rehab::Request {
103 c2s: futu_proto::qot_request_rehab::C2s {
104 security: security.to_proto(),
105 header: None,
106 },
107 };
108
109 let body = prost::Message::encode_to_vec(&req);
110 let resp_frame = client.request(proto_id::QOT_GET_REHAB, body).await?;
111 let resp: futu_proto::qot_request_rehab::Response =
112 prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
113
114 if resp.ret_type != 0 {
115 return Err(FutuError::ServerError {
116 ret_type: resp.ret_type,
117 msg: resp.ret_msg.unwrap_or_default(),
118 });
119 }
120
121 resp.s2c
122 .ok_or(FutuError::Codec("missing s2c in GetRehab".into()))
123}
124
125#[derive(Debug, Clone)]
129pub struct PlateInfo {
130 pub security: Security,
131 pub name: String,
132 pub plate_type: Option<i32>,
133}
134
135impl PlateInfo {
136 pub fn from_proto(p: &futu_proto::qot_common::PlateInfo) -> Self {
137 Self {
138 security: Security::from_proto(&p.plate),
139 name: p.name.clone(),
140 plate_type: p.plate_type,
141 }
142 }
143}
144
145pub async fn get_plate_set(
147 client: &FutuClient,
148 market: QotMarket,
149 plate_set_type: i32,
150) -> Result<Vec<PlateInfo>> {
151 let req = futu_proto::qot_get_plate_set::Request {
152 c2s: futu_proto::qot_get_plate_set::C2s {
153 market: market as i32,
154 plate_set_type,
155 header: None,
156 },
157 };
158
159 let body = prost::Message::encode_to_vec(&req);
160 let resp_frame = client.request(proto_id::QOT_GET_PLATE_SET, body).await?;
161 let resp: futu_proto::qot_get_plate_set::Response =
162 prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
163
164 if resp.ret_type != 0 {
165 return Err(FutuError::ServerError {
166 ret_type: resp.ret_type,
167 msg: resp.ret_msg.unwrap_or_default(),
168 });
169 }
170
171 let s2c = resp
172 .s2c
173 .ok_or(FutuError::Codec("missing s2c in GetPlateSet".into()))?;
174
175 Ok(s2c
176 .plate_info_list
177 .iter()
178 .map(PlateInfo::from_proto)
179 .collect())
180}
181
182pub async fn get_plate_security(
184 client: &FutuClient,
185 plate: &Security,
186) -> Result<Vec<crate::static_info::SecurityStaticInfo>> {
187 let req = futu_proto::qot_get_plate_security::Request {
188 c2s: futu_proto::qot_get_plate_security::C2s {
189 plate: plate.to_proto(),
190 sort_field: None,
191 ascend: None,
192 header: None,
193 },
194 };
195
196 let body = prost::Message::encode_to_vec(&req);
197 let resp_frame = client
198 .request(proto_id::QOT_GET_PLATE_SECURITY, body)
199 .await?;
200 let resp: futu_proto::qot_get_plate_security::Response =
201 prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
202
203 if resp.ret_type != 0 {
204 return Err(FutuError::ServerError {
205 ret_type: resp.ret_type,
206 msg: resp.ret_msg.unwrap_or_default(),
207 });
208 }
209
210 let s2c = resp
211 .s2c
212 .ok_or(FutuError::Codec("missing s2c in GetPlateSecurity".into()))?;
213
214 Ok(s2c
215 .static_info_list
216 .iter()
217 .map(crate::static_info::SecurityStaticInfo::from_proto)
218 .collect())
219}
220
221pub async fn get_owner_plate(
223 client: &FutuClient,
224 securities: &[Security],
225) -> Result<futu_proto::qot_get_owner_plate::S2c> {
226 let req = futu_proto::qot_get_owner_plate::Request {
227 c2s: futu_proto::qot_get_owner_plate::C2s {
228 security_list: securities.iter().map(|s| s.to_proto()).collect(),
229 header: None,
230 },
231 };
232
233 let body = prost::Message::encode_to_vec(&req);
234 let resp_frame = client.request(proto_id::QOT_GET_OWNER_PLATE, body).await?;
235 let resp: futu_proto::qot_get_owner_plate::Response =
236 prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
237
238 if resp.ret_type != 0 {
239 return Err(FutuError::ServerError {
240 ret_type: resp.ret_type,
241 msg: resp.ret_msg.unwrap_or_default(),
242 });
243 }
244
245 resp.s2c
246 .ok_or(FutuError::Codec("missing s2c in GetOwnerPlate".into()))
247}
248
249pub async fn get_reference(
251 client: &FutuClient,
252 security: &Security,
253 reference_type: i32,
254) -> Result<Vec<crate::static_info::SecurityStaticInfo>> {
255 let req = futu_proto::qot_get_reference::Request {
256 c2s: futu_proto::qot_get_reference::C2s {
257 security: security.to_proto(),
258 reference_type,
259 header: None,
260 },
261 };
262
263 let body = prost::Message::encode_to_vec(&req);
264 let resp_frame = client.request(proto_id::QOT_GET_REFERENCE, body).await?;
265 let resp: futu_proto::qot_get_reference::Response =
266 prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
267
268 if resp.ret_type != 0 {
269 return Err(FutuError::ServerError {
270 ret_type: resp.ret_type,
271 msg: resp.ret_msg.unwrap_or_default(),
272 });
273 }
274
275 let s2c = resp
276 .s2c
277 .ok_or(FutuError::Codec("missing s2c in GetReference".into()))?;
278
279 Ok(s2c
280 .static_info_list
281 .iter()
282 .map(crate::static_info::SecurityStaticInfo::from_proto)
283 .collect())
284}
285
286pub async fn get_option_chain(
290 client: &FutuClient,
291 owner: &Security,
292 begin_time: &str,
293 end_time: &str,
294 option_type: Option<i32>,
295 option_cond: Option<i32>,
296 data_filter: Option<futu_proto::qot_get_option_chain::DataFilter>,
297) -> Result<futu_proto::qot_get_option_chain::S2c> {
298 let req = futu_proto::qot_get_option_chain::Request {
299 c2s: futu_proto::qot_get_option_chain::C2s {
300 owner: owner.to_proto(),
301 index_option_type: None,
302 r#type: option_type,
303 condition: option_cond,
304 begin_time: begin_time.to_string(),
305 end_time: end_time.to_string(),
306 data_filter, header: None,
308 },
309 };
310
311 let body = prost::Message::encode_to_vec(&req);
312 let resp_frame = client.request(proto_id::QOT_GET_OPTION_CHAIN, body).await?;
313 let resp: futu_proto::qot_get_option_chain::Response =
314 prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
315
316 if resp.ret_type != 0 {
317 return Err(FutuError::ServerError {
318 ret_type: resp.ret_type,
319 msg: resp.ret_msg.unwrap_or_default(),
320 });
321 }
322
323 resp.s2c
324 .ok_or(FutuError::Codec("missing s2c in GetOptionChain".into()))
325}
326
327pub async fn get_holding_change_list(
331 client: &FutuClient,
332 security: &Security,
333 holder_category: i32,
334 begin_time: Option<&str>,
335 end_time: Option<&str>,
336) -> Result<futu_proto::qot_get_holding_change_list::S2c> {
337 let req = futu_proto::qot_get_holding_change_list::Request {
338 c2s: futu_proto::qot_get_holding_change_list::C2s {
339 security: security.to_proto(),
340 holder_category,
341 begin_time: begin_time.map(|s| s.to_string()),
342 end_time: end_time.map(|s| s.to_string()),
343 header: None,
344 },
345 };
346
347 let body = prost::Message::encode_to_vec(&req);
348 let resp_frame = client
349 .request(proto_id::QOT_GET_HOLDING_CHANGE_LIST, body)
350 .await?;
351 let resp: futu_proto::qot_get_holding_change_list::Response =
352 prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
353
354 if resp.ret_type != 0 {
355 return Err(FutuError::ServerError {
356 ret_type: resp.ret_type,
357 msg: resp.ret_msg.unwrap_or_default(),
358 });
359 }
360
361 resp.s2c.ok_or(FutuError::Codec(
362 "missing s2c in GetHoldingChangeList".into(),
363 ))
364}
365
366pub async fn get_sub_info(
373 client: &FutuClient,
374 is_req_all_conn: bool,
375) -> Result<futu_proto::qot_get_sub_info::S2c> {
376 let req = futu_proto::qot_get_sub_info::Request {
377 c2s: futu_proto::qot_get_sub_info::C2s {
378 is_req_all_conn: Some(is_req_all_conn),
379 header: None,
380 },
381 };
382
383 let body = prost::Message::encode_to_vec(&req);
384 let resp_frame = client.request(proto_id::QOT_GET_SUB_INFO, body).await?;
385 let resp: futu_proto::qot_get_sub_info::Response =
386 prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
387
388 if resp.ret_type != 0 {
389 return Err(FutuError::ServerError {
390 ret_type: resp.ret_type,
391 msg: resp.ret_msg.unwrap_or_default(),
392 });
393 }
394
395 resp.s2c
396 .ok_or(FutuError::Codec("missing s2c in GetSubInfo".into()))
397}
398
399pub async fn reg_qot_push(
401 client: &FutuClient,
402 securities: &[Security],
403 sub_types: &[crate::types::SubType],
404 is_reg: bool,
405 is_first_push: Option<bool>,
406) -> Result<()> {
407 let req = futu_proto::qot_reg_qot_push::Request {
408 c2s: futu_proto::qot_reg_qot_push::C2s {
409 security_list: securities.iter().map(|s| s.to_proto()).collect(),
410 sub_type_list: sub_types.iter().map(|t| *t as i32).collect(),
411 rehab_type_list: vec![],
412 is_reg_or_un_reg: is_reg,
413 is_first_push,
414 header: None,
415 },
416 };
417
418 let body = prost::Message::encode_to_vec(&req);
419 let resp_frame = client.request(proto_id::QOT_REG_QOT_PUSH, body).await?;
420 let resp: futu_proto::qot_reg_qot_push::Response =
421 prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
422
423 if resp.ret_type != 0 {
424 return Err(FutuError::ServerError {
425 ret_type: resp.ret_type,
426 msg: resp.ret_msg.unwrap_or_default(),
427 });
428 }
429
430 Ok(())
431}