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