futu_mcp/handlers/reference/
queries.rs1use std::sync::Arc;
5
6use anyhow::{Result, anyhow, bail};
7use futu_net::client::FutuClient;
8use futu_qot::page_bounds::validate_begin_num;
9use prost::Message;
10use serde::Serialize;
11
12use crate::state::parse_symbol;
13
14#[derive(Serialize)]
15struct StockFilterOut {
16 code: String,
17 name: String,
18}
19
20pub async fn get_stock_filter(
28 client: &Arc<FutuClient>,
29 market: i32,
30 begin: i32,
31 num: i32,
32) -> Result<String> {
33 let bounds =
34 validate_begin_num(begin, num, 200, "stock_filter").map_err(|e| anyhow!("{}", e))?;
35 let req = futu_proto::qot_stock_filter::Request {
36 c2s: futu_proto::qot_stock_filter::C2s {
37 begin: bounds.begin,
38 num: bounds.num,
39 market,
40 plate: None,
41 base_filter_list: vec![],
42 accumulate_filter_list: vec![],
43 financial_filter_list: vec![],
44 pattern_filter_list: vec![],
45 custom_indicator_filter_list: vec![],
46 header: None,
47 },
48 };
49 let body = req.encode_to_vec();
50 let frame = client
51 .request(futu_core::proto_id::QOT_STOCK_FILTER, body)
52 .await?;
53 let resp = futu_proto::qot_stock_filter::Response::decode(frame.body.as_ref())
54 .map_err(|e| anyhow!("decode stock_filter: {e}"))?;
55 if resp.ret_type != 0 {
56 bail!(
57 "stock_filter ret_type={} msg={:?}",
58 resp.ret_type,
59 resp.ret_msg
60 );
61 }
62 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
63 let out: Vec<StockFilterOut> = s2c
64 .data_list
65 .iter()
66 .map(|d| StockFilterOut {
67 code: d.security.code.clone(),
68 name: d.name.clone(),
69 })
70 .collect();
71 Ok(serde_json::to_string_pretty(&serde_json::json!({
72 "last_page": s2c.last_page,
73 "all_count": s2c.all_count,
74 "stocks": out,
75 }))?)
76}
77
78#[derive(Serialize)]
83struct TradingDayOut {
84 time: String,
85 trade_date_type: i32,
86}
87
88pub async fn get_trading_days(
92 client: &Arc<FutuClient>,
93 market: i32,
94 begin_time: &str,
95 end_time: &str,
96) -> Result<String> {
97 let req = futu_proto::qot_request_trade_date::Request {
98 c2s: futu_proto::qot_request_trade_date::C2s {
99 market,
100 begin_time: begin_time.to_string(),
101 end_time: end_time.to_string(),
102 security: None,
103 header: None,
104 },
105 };
106 let body = req.encode_to_vec();
107 let frame = client
108 .request(futu_core::proto_id::QOT_REQUEST_TRADE_DATE, body)
109 .await?;
110 let resp = futu_proto::qot_request_trade_date::Response::decode(frame.body.as_ref())
111 .map_err(|e| anyhow!("decode trading_days: {e}"))?;
112 if resp.ret_type != 0 {
113 bail!(
114 "trading_days ret_type={} msg={:?}",
115 resp.ret_type,
116 resp.ret_msg
117 );
118 }
119 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
120 let out: Vec<TradingDayOut> = s2c
121 .trade_date_list
122 .iter()
123 .map(|t| TradingDayOut {
124 time: t.time.clone(),
125 trade_date_type: t.trade_date_type.unwrap_or(0),
126 })
127 .collect();
128 Ok(serde_json::to_string_pretty(&out)?)
129}
130
131#[derive(Serialize)]
136struct RehabOut {
137 time: String,
138 fwd_factor_a: f64,
140 fwd_factor_b: f64,
141 bwd_factor_a: f64,
143 bwd_factor_b: f64,
144 company_act_flag: i64,
146 dividend: Option<f64>,
148 sp_dividend: Option<f64>,
149 bonus_base: Option<i32>,
150 bonus_ert: Option<i32>,
151 transfer_base: Option<i32>,
152 transfer_ert: Option<i32>,
153 allot_base: Option<i32>,
154 allot_ert: Option<i32>,
155 allot_price: Option<f64>,
156}
157
158pub async fn get_rehab(client: &Arc<FutuClient>, symbol: &str) -> Result<String> {
160 let s = parse_symbol(symbol)?;
161 let req = futu_proto::qot_request_rehab::Request {
162 c2s: futu_proto::qot_request_rehab::C2s {
163 security: futu_proto::qot_common::Security {
164 market: s.market as i32,
165 code: s.code,
166 },
167 header: None, },
169 };
170 let body = req.encode_to_vec();
171 let frame = client
172 .request(futu_core::proto_id::QOT_REQUEST_REHAB, body)
173 .await?;
174 let resp = futu_proto::qot_request_rehab::Response::decode(frame.body.as_ref())
175 .map_err(|e| anyhow!("decode rehab: {e}"))?;
176 if resp.ret_type != 0 {
177 bail!("rehab ret_type={} msg={:?}", resp.ret_type, resp.ret_msg);
178 }
179 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
180 let out: Vec<RehabOut> = s2c
181 .rehab_list
182 .iter()
183 .map(|r| RehabOut {
184 time: r.time.clone(),
185 fwd_factor_a: r.fwd_factor_a,
186 fwd_factor_b: r.fwd_factor_b,
187 bwd_factor_a: r.bwd_factor_a,
188 bwd_factor_b: r.bwd_factor_b,
189 company_act_flag: r.company_act_flag,
190 dividend: r.dividend,
191 sp_dividend: r.sp_dividend,
192 bonus_base: r.bonus_base,
193 bonus_ert: r.bonus_ert,
194 transfer_base: r.transfer_base,
195 transfer_ert: r.transfer_ert,
196 allot_base: r.allot_base,
197 allot_ert: r.allot_ert,
198 allot_price: r.allot_price,
199 })
200 .collect();
201 Ok(serde_json::to_string_pretty(&out)?)
202}
203
204#[derive(Serialize)]
209struct SuspendDayOut {
210 time: String,
211}
212
213#[derive(Serialize)]
214struct SecuritySuspendOut {
215 code: String,
216 suspend_list: Vec<SuspendDayOut>,
217}
218
219pub async fn get_suspend(
221 client: &Arc<FutuClient>,
222 symbols: &[String],
223 begin_time: &str,
224 end_time: &str,
225) -> Result<String> {
226 let sec_list: Vec<_> = symbols
227 .iter()
228 .map(|s| parse_symbol(s))
229 .collect::<Result<Vec<_>>>()?;
230 let proto_secs: Vec<_> = sec_list
231 .iter()
232 .map(|s| futu_proto::qot_common::Security {
233 market: s.market as i32,
234 code: s.code.clone(),
235 })
236 .collect();
237 let req = futu_proto::qot_get_suspend::Request {
238 c2s: futu_proto::qot_get_suspend::C2s {
239 security_list: proto_secs,
240 begin_time: begin_time.to_string(),
241 end_time: end_time.to_string(),
242 header: None,
243 },
244 };
245 let body = req.encode_to_vec();
246 let frame = client
247 .request(futu_core::proto_id::QOT_GET_SUSPEND, body)
248 .await?;
249 let resp = futu_proto::qot_get_suspend::Response::decode(frame.body.as_ref())
250 .map_err(|e| anyhow!("decode suspend: {e}"))?;
251 if resp.ret_type != 0 {
252 bail!("suspend ret_type={} msg={:?}", resp.ret_type, resp.ret_msg);
253 }
254 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
255 let out: Vec<SecuritySuspendOut> = s2c
256 .security_suspend_list
257 .iter()
258 .map(|ss| SecuritySuspendOut {
259 code: ss.security.code.clone(),
260 suspend_list: ss
261 .suspend_list
262 .iter()
263 .map(|s| SuspendDayOut {
264 time: s.time.clone(),
265 })
266 .collect(),
267 })
268 .collect();
269 Ok(serde_json::to_string_pretty(&out)?)
270}
271
272#[derive(Serialize)]
279struct HistoryKlQuotaOut {
280 used_quota: i32,
281 remain_quota: i32,
282 details_count: usize,
283}
284
285pub async fn get_history_kl_quota(client: &Arc<FutuClient>, get_detail: bool) -> Result<String> {
289 let req = futu_proto::qot_request_history_kl_quota::Request {
290 c2s: futu_proto::qot_request_history_kl_quota::C2s {
291 b_get_detail: Some(get_detail),
292 header: None,
293 },
294 };
295 let body = req.encode_to_vec();
296 let frame = client
297 .request(futu_core::proto_id::QOT_REQUEST_HISTORY_KL_QUOTA, body)
298 .await?;
299 let resp = futu_proto::qot_request_history_kl_quota::Response::decode(frame.body.as_ref())
300 .map_err(|e| anyhow!("decode history_kl_quota: {e}"))?;
301 if resp.ret_type != 0 {
302 bail!(
303 "history_kl_quota ret_type={} msg={:?}",
304 resp.ret_type,
305 resp.ret_msg
306 );
307 }
308 let s = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
309 let out = HistoryKlQuotaOut {
310 used_quota: s.used_quota,
311 remain_quota: s.remain_quota,
312 details_count: s.detail_list.len(),
313 };
314 Ok(serde_json::to_string_pretty(&out)?)
315}
316
317