futu_mcp/handlers/reference/
user_security.rs1use std::sync::Arc;
5
6use anyhow::{Result, anyhow, bail};
7use futu_net::client::FutuClient;
8use prost::Message;
9use serde::Serialize;
10
11use crate::state::parse_symbol;
12
13#[derive(Serialize)]
18struct UserSecurityGroupOut {
19 group_name: String,
20 group_type: i32,
21}
22
23pub async fn get_user_security_group(client: &Arc<FutuClient>, group_type: i32) -> Result<String> {
25 let req = futu_proto::qot_get_user_security_group::Request {
26 c2s: futu_proto::qot_get_user_security_group::C2s {
27 group_type,
28 header: None, },
30 };
31 let body = req.encode_to_vec();
32 let frame = client
33 .request(futu_core::proto_id::QOT_GET_USER_SECURITY_GROUP, body)
34 .await?;
35 let resp = futu_proto::qot_get_user_security_group::Response::decode(frame.body.as_ref())
36 .map_err(|e| anyhow!("decode user_security_group: {e}"))?;
37 if resp.ret_type != 0 {
38 bail!(
39 "user_security_group ret_type={} msg={:?}",
40 resp.ret_type,
41 resp.ret_msg
42 );
43 }
44 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
45 let out: Vec<UserSecurityGroupOut> = s2c
46 .group_list
47 .iter()
48 .map(|g| UserSecurityGroupOut {
49 group_name: g.group_name.clone(),
50 group_type: g.group_type,
51 })
52 .collect();
53 Ok(serde_json::to_string_pretty(&out)?)
54}
55
56#[derive(Serialize)]
61struct HoldingChangeOut {
62 code: String,
63 holder_name: String,
64 holding_qty: f64,
65 holding_ratio: f64,
66 change_qty: f64,
67 change_ratio: f64,
68 time: String,
69}
70
71pub async fn get_holding_change(
73 client: &Arc<FutuClient>,
74 symbol: &str,
75 holder_category: i32,
76 begin_time: Option<&str>,
77 end_time: Option<&str>,
78) -> Result<String> {
79 let s = parse_symbol(symbol)?;
80 let req = futu_proto::qot_get_holding_change_list::Request {
81 c2s: futu_proto::qot_get_holding_change_list::C2s {
82 security: futu_proto::qot_common::Security {
83 market: s.market as i32,
84 code: s.code,
85 },
86 holder_category,
87 begin_time: begin_time.map(String::from),
88 end_time: end_time.map(String::from),
89 header: None, },
91 };
92 let body = req.encode_to_vec();
93 let frame = client
94 .request(futu_core::proto_id::QOT_GET_HOLDING_CHANGE_LIST, body)
95 .await?;
96 let resp = futu_proto::qot_get_holding_change_list::Response::decode(frame.body.as_ref())
97 .map_err(|e| anyhow!("decode holding_change: {e}"))?;
98 if resp.ret_type != 0 {
99 bail!(
100 "holding_change ret_type={} msg={:?}",
101 resp.ret_type,
102 resp.ret_msg
103 );
104 }
105 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
106 let out: Vec<HoldingChangeOut> = s2c
107 .holding_change_list
108 .iter()
109 .map(|h| HoldingChangeOut {
110 code: s2c.security.code.clone(),
111 holder_name: h.holder_name.clone(),
112 holding_qty: h.holding_qty,
113 holding_ratio: h.holding_ratio,
114 change_qty: h.change_qty,
115 change_ratio: h.change_ratio,
116 time: h.time.clone(),
117 })
118 .collect();
119 Ok(serde_json::to_string_pretty(&out)?)
120}
121
122pub async fn modify_user_security(
128 client: &Arc<FutuClient>,
129 group_name: &str,
130 op: i32,
131 symbols: &[String],
132) -> Result<String> {
133 let sec_list: Vec<_> = symbols
134 .iter()
135 .map(|s| parse_symbol(s))
136 .collect::<Result<Vec<_>>>()?;
137 let proto_secs: Vec<_> = sec_list
138 .iter()
139 .map(|s| futu_proto::qot_common::Security {
140 market: s.market as i32,
141 code: s.code.clone(),
142 })
143 .collect();
144 let req = futu_proto::qot_modify_user_security::Request {
145 c2s: futu_proto::qot_modify_user_security::C2s {
146 group_name: group_name.to_string(),
147 op,
148 security_list: proto_secs,
149 header: None,
150 },
151 };
152 let body = req.encode_to_vec();
153 let frame = client
154 .request(futu_core::proto_id::QOT_MODIFY_USER_SECURITY, body)
155 .await?;
156 let resp = futu_proto::qot_modify_user_security::Response::decode(frame.body.as_ref())
157 .map_err(|e| anyhow!("decode modify_user_security: {e}"))?;
158 if resp.ret_type != 0 {
159 bail!(
160 "modify_user_security ret_type={} msg={:?}",
161 resp.ret_type,
162 resp.ret_msg
163 );
164 }
165 Ok(serde_json::to_string_pretty(&serde_json::json!({
166 "ok": true,
167 "op": op,
168 "group_name": group_name,
169 "count": symbols.len(),
170 }))?)
171}
172
173#[derive(Serialize)]
178struct UserSecurityOut {
179 code: String,
180 name: String,
181 lot_size: i32,
182 sec_type: i32,
183}
184
185pub async fn get_user_security(client: &Arc<FutuClient>, group_name: &str) -> Result<String> {
188 let req = futu_proto::qot_get_user_security::Request {
189 c2s: futu_proto::qot_get_user_security::C2s {
190 group_name: group_name.to_string(),
191 header: None,
192 },
193 };
194 let body = req.encode_to_vec();
195 let frame = client
196 .request(futu_core::proto_id::QOT_GET_USER_SECURITY, body)
197 .await?;
198 let resp = futu_proto::qot_get_user_security::Response::decode(frame.body.as_ref())
199 .map_err(|e| anyhow!("decode user_security: {e}"))?;
200 if resp.ret_type != 0 {
201 bail!(
202 "user_security ret_type={} msg={:?}",
203 resp.ret_type,
204 resp.ret_msg
205 );
206 }
207 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
208 let out: Vec<UserSecurityOut> = s2c
209 .static_info_list
210 .iter()
211 .map(|s| UserSecurityOut {
212 code: s.basic.security.code.clone(),
213 name: s.basic.name.clone(),
214 lot_size: s.basic.lot_size,
215 sec_type: s.basic.sec_type,
216 })
217 .collect();
218 Ok(serde_json::to_string_pretty(&out)?)
219}