1use std::sync::Arc;
5
6use anyhow::{Result, anyhow, bail};
7use futu_net::client::FutuClient;
8use prost::Message;
9
10use crate::state::parse_symbol;
11
12pub async fn subscribe(
14 client: &Arc<FutuClient>,
15 symbols: &[String],
16 sub_types: &[i32],
17 is_first_push: bool,
18 is_reg_push: bool,
19) -> Result<String> {
20 let sec_list: Vec<_> = symbols
21 .iter()
22 .map(|s| parse_symbol(s))
23 .collect::<Result<Vec<_>>>()?;
24 let proto_secs: Vec<_> = sec_list
25 .iter()
26 .map(|s| futu_proto::qot_common::Security {
27 market: s.market as i32,
28 code: s.code.clone(),
29 })
30 .collect();
31 let req = futu_proto::qot_sub::Request {
32 c2s: futu_proto::qot_sub::C2s {
33 security_list: proto_secs,
34 sub_type_list: sub_types.to_vec(),
35 is_sub_or_un_sub: true, is_reg_or_un_reg_push: Some(is_reg_push),
37 reg_push_rehab_type_list: vec![],
38 is_first_push: Some(is_first_push),
39 is_unsub_all: None,
40 is_sub_order_book_detail: None,
41 extended_time: None,
42 session: None,
43 header: None,
44 },
45 };
46 let body = req.encode_to_vec();
47 let frame = client.request(futu_core::proto_id::QOT_SUB, body).await?;
48 let resp = futu_proto::qot_sub::Response::decode(frame.body.as_ref())
49 .map_err(|e| anyhow!("decode subscribe: {e}"))?;
50 if resp.ret_type != 0 {
51 bail!(
52 "subscribe ret_type={} msg={:?}",
53 resp.ret_type,
54 resp.ret_msg
55 );
56 }
57 Ok(serde_json::to_string_pretty(&serde_json::json!({
58 "ok": true,
59 "subscribed_symbols": symbols,
60 "sub_types": sub_types,
61 "is_first_push": is_first_push,
62 "is_reg_push": is_reg_push,
63 }))?)
64}
65
66pub async fn unsubscribe(
69 client: &Arc<FutuClient>,
70 symbols: &[String],
71 sub_types: &[i32],
72 unsub_all: bool,
73) -> Result<String> {
74 let sec_list: Vec<_> = if unsub_all {
75 Vec::new()
76 } else {
77 symbols
78 .iter()
79 .map(|s| parse_symbol(s))
80 .collect::<Result<Vec<_>>>()?
81 };
82 let proto_secs: Vec<_> = sec_list
83 .iter()
84 .map(|s| futu_proto::qot_common::Security {
85 market: s.market as i32,
86 code: s.code.clone(),
87 })
88 .collect();
89 let req = futu_proto::qot_sub::Request {
90 c2s: futu_proto::qot_sub::C2s {
91 security_list: proto_secs,
92 sub_type_list: sub_types.to_vec(),
93 is_sub_or_un_sub: false, is_reg_or_un_reg_push: Some(false),
95 reg_push_rehab_type_list: vec![],
96 is_first_push: None,
97 is_unsub_all: Some(unsub_all),
98 is_sub_order_book_detail: None,
99 extended_time: None,
100 session: None,
101 header: None,
102 },
103 };
104 let body = req.encode_to_vec();
105 let frame = client.request(futu_core::proto_id::QOT_SUB, body).await?;
106 let resp = futu_proto::qot_sub::Response::decode(frame.body.as_ref())
107 .map_err(|e| anyhow!("decode unsubscribe: {e}"))?;
108 if resp.ret_type != 0 {
109 bail!(
110 "unsubscribe ret_type={} msg={:?}",
111 resp.ret_type,
112 resp.ret_msg
113 );
114 }
115 Ok(serde_json::to_string_pretty(&serde_json::json!({
116 "ok": true,
117 "unsub_all": unsub_all,
118 "count": symbols.len(),
119 }))?)
120}
121
122pub async fn get_token_state(client: &Arc<FutuClient>, app_id: Option<&str>) -> Result<String> {
128 use futu_backend::proto_internal::futu_token_state;
129 let req = futu_token_state::DaemonGetTokenStateReq {
130 c2s: futu_token_state::daemon_get_token_state_req::C2s {
131 app_id: app_id.map(|s| s.to_string()),
132 },
133 };
134 let body = req.encode_to_vec();
135 let frame = client
136 .request(futu_core::proto_id::GET_TOKEN_STATE, body)
137 .await?;
138 let resp = futu_token_state::DaemonGetTokenStateRsp::decode(frame.body.as_ref())
139 .map_err(|e| anyhow!("decode token_state: {e}"))?;
140 if resp.ret_type != 0 {
141 bail!(
142 "token_state ret_type={} msg={:?}",
143 resp.ret_type,
144 resp.ret_msg
145 );
146 }
147 let s = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
148 Ok(serde_json::to_string_pretty(&serde_json::json!({
149 "nn_token_enable": s.nn_token_enable,
150 "nn_token_bind": s.nn_token_bind,
151 "mm_token_enable": s.mm_token_enable,
152 "mm_token_bind": s.mm_token_bind,
153 }))?)
154}
155
156pub async fn get_risk_free_rate(client: &Arc<FutuClient>) -> Result<String> {
161 use futu_backend::proto_internal::risk_free_rate;
162 let req = risk_free_rate::DaemonGetRiskFreeRateReq {
163 c2s: risk_free_rate::daemon_get_risk_free_rate_req::C2s { rate_time: None },
164 };
165 let body = req.encode_to_vec();
166 let frame = client
167 .request(futu_core::proto_id::QOT_GET_RISK_FREE_RATE, body)
168 .await?;
169 let resp = risk_free_rate::DaemonGetRiskFreeRateRsp::decode(frame.body.as_ref())
170 .map_err(|e| anyhow!("decode risk_free_rate: {e}"))?;
171 if resp.ret_type != 0 {
172 bail!(
173 "risk_free_rate ret_type={} msg={:?}",
174 resp.ret_type,
175 resp.ret_msg
176 );
177 }
178 let s = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
179 Ok(serde_json::to_string_pretty(&serde_json::json!({
180 "hk_rate_pct": s.hk_rate_pct,
181 "us_rate_pct": s.us_rate_pct,
182 "jp_rate_pct": s.jp_rate_pct,
183 "update_time": s.update_time,
184 "hk_rate_raw": s.hk_rate_raw,
185 "us_rate_raw": s.us_rate_raw,
186 "jp_rate_raw": s.jp_rate_raw,
187 }))?)
188}
189
190pub async fn get_spread_table(client: &Arc<FutuClient>) -> Result<String> {
192 use futu_backend::proto_internal::spread_table_6503;
193 let req = spread_table_6503::DaemonGetSpreadTableReq {
194 c2s: spread_table_6503::daemon_get_spread_table_req::C2s { reserved: None },
195 };
196 let body = req.encode_to_vec();
197 let frame = client
198 .request(futu_core::proto_id::QOT_GET_SPREAD_TABLE, body)
199 .await?;
200 let resp = spread_table_6503::DaemonGetSpreadTableRsp::decode(frame.body.as_ref())
201 .map_err(|e| anyhow!("decode spread_table: {e}"))?;
202 if resp.ret_type != 0 {
203 bail!(
204 "spread_table ret_type={} msg={:?}",
205 resp.ret_type,
206 resp.ret_msg
207 );
208 }
209 let s = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
210 Ok(serde_json::to_string_pretty(&s)?)
211}
212
213pub async fn get_ticker_statistic(
215 client: &Arc<FutuClient>,
216 symbol: &str,
217 ticker_type: Option<i32>,
218 stat_type: Option<u32>,
219) -> Result<String> {
220 use futu_backend::proto_internal::ticker_statistic_daemon;
221 let sec = parse_symbol(symbol)?;
222 let req = ticker_statistic_daemon::DaemonGetTickerStatisticReq {
223 c2s: ticker_statistic_daemon::daemon_get_ticker_statistic_req::C2s {
224 security: ticker_statistic_daemon::Security {
225 market: sec.market as i32,
226 code: sec.code,
227 },
228 ticker_type,
229 ticker_time: None,
230 stat_type,
231 },
234 };
235 let body = req.encode_to_vec();
236 let frame = client
237 .request(futu_core::proto_id::QOT_GET_TICKER_STATISTIC, body)
238 .await?;
239 let resp = ticker_statistic_daemon::DaemonGetTickerStatisticRsp::decode(frame.body.as_ref())
240 .map_err(|e| anyhow!("decode ticker_statistic: {e}"))?;
241 if resp.ret_type != 0 {
242 bail!(
243 "ticker_statistic ret_type={} msg={:?}",
244 resp.ret_type,
245 resp.ret_msg
246 );
247 }
248 let s = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
249 Ok(serde_json::to_string_pretty(&s)?)
250}
251
252pub struct TickerStatisticDetailInput<'a> {
259 pub symbol: &'a str,
260 pub ticker_type: Option<i32>,
261 pub ticker_time: Option<u64>,
262 pub select_num: Option<u32>,
263 pub data_from: Option<u32>,
264 pub data_max_count: Option<u32>,
265 pub stat_type: Option<u32>,
266}
267
268pub async fn get_ticker_statistic_detail(
269 client: &Arc<FutuClient>,
270 input: TickerStatisticDetailInput<'_>,
271) -> Result<String> {
272 use futu_backend::proto_internal::ticker_statistic_daemon;
273 let sec = parse_symbol(input.symbol)?;
274 let req = ticker_statistic_daemon::DaemonGetTickerStatisticDetailReq {
275 c2s: ticker_statistic_daemon::daemon_get_ticker_statistic_detail_req::C2s {
276 security: ticker_statistic_daemon::Security {
277 market: sec.market as i32,
278 code: sec.code,
279 },
280 ticker_type: input.ticker_type,
281 ticker_time: input.ticker_time,
282 select_num: input.select_num,
283 data_from: input.data_from,
284 data_max_count: input.data_max_count,
285 stat_type: input.stat_type,
286 },
287 };
288 let body = req.encode_to_vec();
289 let frame = client
290 .request(futu_core::proto_id::QOT_GET_TICKER_STATISTIC_DETAIL, body)
291 .await?;
292 let resp =
293 ticker_statistic_daemon::DaemonGetTickerStatisticDetailRsp::decode(frame.body.as_ref())
294 .map_err(|e| anyhow!("decode ticker_statistic_detail: {e}"))?;
295 if resp.ret_type != 0 {
296 bail!(
297 "ticker_statistic_detail ret_type={} msg={:?}",
298 resp.ret_type,
299 resp.ret_msg
300 );
301 }
302 let s = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
303 Ok(serde_json::to_string_pretty(&s)?)
304}