1use futu_core::error::{FutuError, Result};
2use futu_core::proto_id;
3use futu_net::client::FutuClient;
4
5use crate::subscription_plan::{SubscribeOptionsPlan, SubscribePlanError};
6use crate::types::{Security, SubType};
7
8#[derive(Debug, Clone, Copy, Default)]
10pub struct SubscribeOptions {
11 pub is_reg_push: bool,
12 pub is_first_push: bool,
13 pub extended_time: Option<bool>,
14 pub session: Option<i32>,
15 pub orderbook_detail: Option<bool>,
16}
17
18pub async fn subscribe(
23 client: &FutuClient,
24 securities: &[Security],
25 sub_types: &[SubType],
26 is_reg_push: bool,
27 is_first_push: bool,
28) -> Result<()> {
29 subscribe_with_options(
30 client,
31 securities,
32 sub_types,
33 SubscribeOptions {
34 is_reg_push,
35 is_first_push,
36 ..SubscribeOptions::default()
37 },
38 )
39 .await
40}
41
42pub async fn subscribe_with_options(
43 client: &FutuClient,
44 securities: &[Security],
45 sub_types: &[SubType],
46 options: SubscribeOptions,
47) -> Result<()> {
48 validate_subscribe_options(options)?;
49 let req = futu_proto::qot_sub::Request {
50 c2s: futu_proto::qot_sub::C2s {
51 security_list: securities.iter().map(|s| s.to_proto()).collect(),
52 sub_type_list: sub_types.iter().map(|t| *t as i32).collect(),
53 is_sub_or_un_sub: true,
54 is_reg_or_un_reg_push: Some(options.is_reg_push),
55 reg_push_rehab_type_list: vec![],
56 is_first_push: Some(options.is_first_push),
57 is_unsub_all: None,
58 is_sub_order_book_detail: options.orderbook_detail,
59 extended_time: options.extended_time,
60 session: options.session,
61 header: None,
62 },
63 };
64
65 let body = prost::Message::encode_to_vec(&req);
66 let resp_frame = client.request(proto_id::QOT_SUB, body).await?;
67
68 let resp: futu_proto::qot_sub::Response =
69 prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
70
71 check_ret_type(resp.ret_type, resp.ret_msg)
72}
73
74fn validate_subscribe_options(options: SubscribeOptions) -> Result<()> {
75 match SubscribeOptionsPlan::from_raw(
76 options.session,
77 options.extended_time,
78 options.orderbook_detail,
79 ) {
80 Ok(_) => Ok(()),
81 Err(SubscribePlanError::OvernightSessionUnsupported) => Err(FutuError::Codec(
82 "Qot_Sub: session=OVERNIGHT (4) is not supported; use session=2 (ETH) or 3 (ALL)"
83 .to_string(),
84 )),
85 }
86}
87
88pub async fn unsubscribe(
90 client: &FutuClient,
91 securities: &[Security],
92 sub_types: &[SubType],
93) -> Result<()> {
94 let req = futu_proto::qot_sub::Request {
95 c2s: futu_proto::qot_sub::C2s {
96 security_list: securities.iter().map(|s| s.to_proto()).collect(),
97 sub_type_list: sub_types.iter().map(|t| *t as i32).collect(),
98 is_sub_or_un_sub: false,
99 is_reg_or_un_reg_push: Some(true),
100 reg_push_rehab_type_list: vec![],
101 is_first_push: None,
102 is_unsub_all: None,
103 is_sub_order_book_detail: None,
104 extended_time: None,
105 session: None,
106 header: None,
107 },
108 };
109
110 let body = prost::Message::encode_to_vec(&req);
111 let resp_frame = client.request(proto_id::QOT_SUB, body).await?;
112
113 let resp: futu_proto::qot_sub::Response =
114 prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
115
116 check_ret_type(resp.ret_type, resp.ret_msg)
117}
118
119fn check_ret_type(ret_type: i32, ret_msg: Option<String>) -> Result<()> {
120 if ret_type != 0 {
121 return Err(crate::server_err(ret_type, ret_msg));
122 }
123 Ok(())
124}