Skip to main content

futu_mcp/handlers/reference/
ownership.rs

1//! MCP handlers for v10.6 shareholder / ownership reference endpoints.
2
3use std::sync::Arc;
4
5use anyhow::{Result, anyhow, bail};
6use futu_net::client::FutuClient;
7use prost::Message;
8use serde::Serialize;
9
10use crate::state::parse_symbol;
11
12#[derive(Serialize)]
13struct ShareholdersOverviewOut {
14    symbol: String,
15    s2c: futu_proto::qot_get_shareholders_overview::S2c,
16}
17
18#[derive(Serialize)]
19struct ShareholdersHoldingChangesOut {
20    symbol: String,
21    s2c: futu_proto::qot_get_shareholders_holding_changes::S2c,
22}
23
24#[derive(Serialize)]
25struct ShareholdersHolderDetailOut {
26    symbol: String,
27    s2c: futu_proto::qot_get_shareholders_holder_detail::S2c,
28}
29
30#[derive(Serialize)]
31struct ShareholdersInstitutionalOut {
32    symbol: String,
33    s2c: futu_proto::qot_get_shareholders_institutional::S2c,
34}
35
36#[derive(Serialize)]
37struct InsiderHolderListOut {
38    symbol: String,
39    s2c: futu_proto::qot_get_insider_holder_list::S2c,
40}
41
42#[derive(Serialize)]
43struct InsiderTradeListOut {
44    symbol: String,
45    s2c: futu_proto::qot_get_insider_trade_list::S2c,
46}
47
48pub async fn get_shareholders_overview(
49    client: &Arc<FutuClient>,
50    symbol: &str,
51    period_id: Option<i32>,
52) -> Result<String> {
53    let sec = parse_symbol(symbol)?;
54    let req = futu_proto::qot_get_shareholders_overview::Request {
55        c2s: futu_proto::qot_get_shareholders_overview::C2s {
56            security: futu_proto::qot_common::Security {
57                market: sec.market as i32,
58                code: sec.code.clone(),
59            },
60            period_id,
61        },
62    };
63    let frame = client
64        .request(
65            futu_core::proto_id::QOT_GET_SHAREHOLDERS_OVERVIEW,
66            req.encode_to_vec(),
67        )
68        .await?;
69    let resp = futu_proto::qot_get_shareholders_overview::Response::decode(frame.body.as_ref())
70        .map_err(|e| anyhow!("decode shareholders_overview: {e}"))?;
71    if resp.ret_type != 0 {
72        bail!(
73            "shareholders_overview ret_type={} msg={:?}",
74            resp.ret_type,
75            resp.ret_msg
76        );
77    }
78    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
79    Ok(serde_json::to_string_pretty(&ShareholdersOverviewOut {
80        symbol: symbol.to_string(),
81        s2c,
82    })?)
83}
84
85pub async fn get_shareholders_holding_changes(
86    client: &Arc<FutuClient>,
87    symbol: &str,
88    next_key: Option<String>,
89    num: Option<i32>,
90    sort_type: Option<i32>,
91    sort_column: Option<i32>,
92    filter_type: Option<i32>,
93) -> Result<String> {
94    let sec = parse_symbol(symbol)?;
95    let req = futu_proto::qot_get_shareholders_holding_changes::Request {
96        c2s: futu_proto::qot_get_shareholders_holding_changes::C2s {
97            security: futu_proto::qot_common::Security {
98                market: sec.market as i32,
99                code: sec.code.clone(),
100            },
101            next_key,
102            num,
103            sort_type,
104            sort_column,
105            filter_type,
106        },
107    };
108    let frame = client
109        .request(
110            futu_core::proto_id::QOT_GET_SHAREHOLDERS_HOLDING_CHANGES,
111            req.encode_to_vec(),
112        )
113        .await?;
114    let resp =
115        futu_proto::qot_get_shareholders_holding_changes::Response::decode(frame.body.as_ref())
116            .map_err(|e| anyhow!("decode shareholders_holding_changes: {e}"))?;
117    if resp.ret_type != 0 {
118        bail!(
119            "shareholders_holding_changes ret_type={} msg={:?}",
120            resp.ret_type,
121            resp.ret_msg
122        );
123    }
124    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
125    Ok(serde_json::to_string_pretty(
126        &ShareholdersHoldingChangesOut {
127            symbol: symbol.to_string(),
128            s2c,
129        },
130    )?)
131}
132
133pub async fn get_shareholders_holder_detail(
134    client: &Arc<FutuClient>,
135    symbol: &str,
136    request_type: Option<i32>,
137    next_key: Option<String>,
138    num: Option<i32>,
139    sort_column: Option<i32>,
140    sort_type: Option<i32>,
141    period_id: Option<i32>,
142    holder_id: Option<i32>,
143) -> Result<String> {
144    let sec = parse_symbol(symbol)?;
145    let req = futu_proto::qot_get_shareholders_holder_detail::Request {
146        c2s: futu_proto::qot_get_shareholders_holder_detail::C2s {
147            security: futu_proto::qot_common::Security {
148                market: sec.market as i32,
149                code: sec.code.clone(),
150            },
151            request_type,
152            next_key,
153            num,
154            sort_column,
155            sort_type,
156            period_id,
157            holder_id,
158        },
159    };
160    let frame = client
161        .request(
162            futu_core::proto_id::QOT_GET_SHAREHOLDERS_HOLDER_DETAIL,
163            req.encode_to_vec(),
164        )
165        .await?;
166    let resp =
167        futu_proto::qot_get_shareholders_holder_detail::Response::decode(frame.body.as_ref())
168            .map_err(|e| anyhow!("decode shareholders_holder_detail: {e}"))?;
169    if resp.ret_type != 0 {
170        bail!(
171            "shareholders_holder_detail ret_type={} msg={:?}",
172            resp.ret_type,
173            resp.ret_msg
174        );
175    }
176    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
177    Ok(serde_json::to_string_pretty(
178        &ShareholdersHolderDetailOut {
179            symbol: symbol.to_string(),
180            s2c,
181        },
182    )?)
183}
184
185pub async fn get_shareholders_institutional(
186    client: &Arc<FutuClient>,
187    symbol: &str,
188    next_key: Option<String>,
189    num: Option<i32>,
190) -> Result<String> {
191    let sec = parse_symbol(symbol)?;
192    let req = futu_proto::qot_get_shareholders_institutional::Request {
193        c2s: futu_proto::qot_get_shareholders_institutional::C2s {
194            security: futu_proto::qot_common::Security {
195                market: sec.market as i32,
196                code: sec.code.clone(),
197            },
198            next_key,
199            num,
200        },
201    };
202    let frame = client
203        .request(
204            futu_core::proto_id::QOT_GET_SHAREHOLDERS_INSTITUTIONAL,
205            req.encode_to_vec(),
206        )
207        .await?;
208    let resp =
209        futu_proto::qot_get_shareholders_institutional::Response::decode(frame.body.as_ref())
210            .map_err(|e| anyhow!("decode shareholders_institutional: {e}"))?;
211    if resp.ret_type != 0 {
212        bail!(
213            "shareholders_institutional ret_type={} msg={:?}",
214            resp.ret_type,
215            resp.ret_msg
216        );
217    }
218    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
219    Ok(serde_json::to_string_pretty(
220        &ShareholdersInstitutionalOut {
221            symbol: symbol.to_string(),
222            s2c,
223        },
224    )?)
225}
226
227pub async fn get_insider_holder_list(
228    client: &Arc<FutuClient>,
229    symbol: &str,
230    next_key: Option<String>,
231    num: Option<i32>,
232) -> Result<String> {
233    let sec = parse_symbol(symbol)?;
234    let req = futu_proto::qot_get_insider_holder_list::Request {
235        c2s: futu_proto::qot_get_insider_holder_list::C2s {
236            security: futu_proto::qot_common::Security {
237                market: sec.market as i32,
238                code: sec.code.clone(),
239            },
240            next_key,
241            num,
242        },
243    };
244    let frame = client
245        .request(
246            futu_core::proto_id::QOT_GET_INSIDER_HOLDER_LIST,
247            req.encode_to_vec(),
248        )
249        .await?;
250    let resp = futu_proto::qot_get_insider_holder_list::Response::decode(frame.body.as_ref())
251        .map_err(|e| anyhow!("decode insider_holder_list: {e}"))?;
252    if resp.ret_type != 0 {
253        bail!(
254            "insider_holder_list ret_type={} msg={:?}",
255            resp.ret_type,
256            resp.ret_msg
257        );
258    }
259    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
260    Ok(serde_json::to_string_pretty(&InsiderHolderListOut {
261        symbol: symbol.to_string(),
262        s2c,
263    })?)
264}
265
266pub async fn get_insider_trade_list(
267    client: &Arc<FutuClient>,
268    symbol: &str,
269    holder_id: Option<i64>,
270    next_key: Option<String>,
271    num: Option<i32>,
272) -> Result<String> {
273    let sec = parse_symbol(symbol)?;
274    let req = futu_proto::qot_get_insider_trade_list::Request {
275        c2s: futu_proto::qot_get_insider_trade_list::C2s {
276            security: futu_proto::qot_common::Security {
277                market: sec.market as i32,
278                code: sec.code.clone(),
279            },
280            holder_id,
281            next_key,
282            num,
283        },
284    };
285    let frame = client
286        .request(
287            futu_core::proto_id::QOT_GET_INSIDER_TRADE_LIST,
288            req.encode_to_vec(),
289        )
290        .await?;
291    let resp = futu_proto::qot_get_insider_trade_list::Response::decode(frame.body.as_ref())
292        .map_err(|e| anyhow!("decode insider_trade_list: {e}"))?;
293    if resp.ret_type != 0 {
294        bail!(
295            "insider_trade_list ret_type={} msg={:?}",
296            resp.ret_type,
297            resp.ret_msg
298        );
299    }
300    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
301    Ok(serde_json::to_string_pretty(&InsiderTradeListOut {
302        symbol: symbol.to_string(),
303        s2c,
304    })?)
305}