Skip to main content

futu_mcp/tool_args/qot/
shareholders.rs

1//! MCP QOT shareholders and insider request schemas.
2
3use rmcp::schemars;
4use serde::Deserialize;
5
6use super::validate_optional_i32_range;
7
8#[derive(Debug, Deserialize, schemars::JsonSchema)]
9#[serde(deny_unknown_fields)]
10pub struct ShareholdersOverviewReq {
11    #[schemars(
12        description = "HK/US security symbol in MARKET.CODE format, e.g. HK.00700 or US.AAPL \
13                       (alias: code / stock / security for SDK compat)"
14    )]
15    #[serde(alias = "code", alias = "stock", alias = "security")]
16    pub symbol: String,
17    #[schemars(
18        description = "Optional reporting period id from holdingPeriodList; alias: periodId"
19    )]
20    #[serde(default, alias = "periodId")]
21    pub period_id: Option<i32>,
22}
23
24#[derive(Debug, Deserialize, schemars::JsonSchema)]
25#[serde(deny_unknown_fields)]
26pub struct ShareholdersHoldingChangesReq {
27    #[schemars(
28        description = "HK/US security symbol in MARKET.CODE format, e.g. HK.00700 or US.AAPL \
29                       (alias: code / stock / security for SDK compat)"
30    )]
31    #[serde(alias = "code", alias = "stock", alias = "security")]
32    pub symbol: String,
33    #[schemars(description = "Pagination key from previous response; \"-1\" returns empty page")]
34    #[serde(default, alias = "nextKey")]
35    pub next_key: Option<String>,
36    #[schemars(description = "Page size, 1..50; omitted uses backend default 10")]
37    #[serde(default)]
38    pub num: Option<i32>,
39    #[schemars(description = "Qot_Common.SortType: 0/1=desc, 2=asc; alias: sortType")]
40    #[serde(default, alias = "sortType")]
41    pub sort_type: Option<i32>,
42    #[schemars(description = "Qot_Common.SortField: 62/63/64/65/66; alias: sortColumn")]
43    #[serde(default, alias = "sortColumn")]
44    pub sort_column: Option<i32>,
45    #[schemars(
46        description = "Qot_Common.HoldingChangesFilterType: 0=all,1=increase,2=decrease,3=new-in,4=close-out; alias: filterType"
47    )]
48    #[serde(default, alias = "filterType")]
49    pub filter_type: Option<i32>,
50}
51
52impl ShareholdersHoldingChangesReq {
53    pub fn validate(&self) -> Result<(), String> {
54        validate_optional_i32_range(
55            "futu_get_shareholders_holding_changes",
56            "num",
57            self.num,
58            1,
59            50,
60        )?;
61        if let Some(sort_type) = self.sort_type
62            && !matches!(sort_type, 0..=2)
63        {
64            return Err(format!(
65                "futu_get_shareholders_holding_changes sort_type must be 0, 1, or 2, got {sort_type}"
66            ));
67        }
68        if let Some(sort_column) = self.sort_column
69            && !matches!(sort_column, 0 | 62 | 63 | 64 | 65 | 66)
70        {
71            return Err(format!(
72                "futu_get_shareholders_holding_changes sort_column must be 0, 62, 63, 64, 65, or 66, got {sort_column}"
73            ));
74        }
75        if let Some(filter_type) = self.filter_type
76            && !(0..=4).contains(&filter_type)
77        {
78            return Err(format!(
79                "futu_get_shareholders_holding_changes filter_type must be in 0..=4, got {filter_type}"
80            ));
81        }
82        Ok(())
83    }
84}
85
86#[derive(Debug, Deserialize, schemars::JsonSchema)]
87#[serde(deny_unknown_fields)]
88pub struct ShareholdersHolderDetailReq {
89    #[schemars(
90        description = "HK/US security symbol in MARKET.CODE format, e.g. HK.00700 or US.AAPL \
91                       (alias: code / stock / security for SDK compat)"
92    )]
93    #[serde(alias = "code", alias = "stock", alias = "security")]
94    pub symbol: String,
95    #[schemars(
96        description = "Qot_Common.HolderDetailType: 0 default, 1..15 institution types, 100/200/300/400/500, 1000 all; alias: requestType"
97    )]
98    #[serde(default, alias = "requestType")]
99    pub request_type: Option<i32>,
100    #[schemars(description = "Pagination key from previous response; \"-1\" returns empty page")]
101    #[serde(default, alias = "nextKey")]
102    pub next_key: Option<String>,
103    #[schemars(description = "Page size, 1..50; omitted uses backend default 10")]
104    #[serde(default)]
105    pub num: Option<i32>,
106    #[schemars(
107        description = "Qot_Common.SortField: 0/61=holder quantity, 62=share change num; alias: sortColumn"
108    )]
109    #[serde(default, alias = "sortColumn")]
110    pub sort_column: Option<i32>,
111    #[schemars(description = "Qot_Common.SortType: 0/1=desc, 2=asc; alias: sortType")]
112    #[serde(default, alias = "sortType")]
113    pub sort_type: Option<i32>,
114    #[schemars(description = "Reporting period id from GetShareholdersOverview; alias: periodId")]
115    #[serde(default, alias = "periodId")]
116    pub period_id: Option<i32>,
117    #[schemars(description = "Holder id filter; alias: holderId")]
118    #[serde(default, alias = "holderId")]
119    pub holder_id: Option<i32>,
120}
121
122impl ShareholdersHolderDetailReq {
123    pub fn validate(&self) -> Result<(), String> {
124        validate_optional_i32_range(
125            "futu_get_shareholders_holder_detail",
126            "num",
127            self.num,
128            1,
129            50,
130        )?;
131        if let Some(request_type) = self.request_type {
132            let valid = request_type == 0
133                || (1..=15).contains(&request_type)
134                || matches!(request_type, 100 | 200 | 300 | 400 | 500 | 1000);
135            if !valid {
136                return Err(format!(
137                    "futu_get_shareholders_holder_detail request_type must be 0, 1..=15, 100, 200, 300, 400, 500, or 1000, got {request_type}"
138                ));
139            }
140        }
141        if let Some(sort_column) = self.sort_column
142            && !matches!(sort_column, 0 | 61 | 62)
143        {
144            return Err(format!(
145                "futu_get_shareholders_holder_detail sort_column must be 0, 61, or 62, got {sort_column}"
146            ));
147        }
148        if let Some(sort_type) = self.sort_type
149            && !matches!(sort_type, 0..=2)
150        {
151            return Err(format!(
152                "futu_get_shareholders_holder_detail sort_type must be 0, 1, or 2, got {sort_type}"
153            ));
154        }
155        Ok(())
156    }
157}
158
159#[derive(Debug, Deserialize, schemars::JsonSchema)]
160#[serde(deny_unknown_fields)]
161pub struct ShareholdersInstitutionalReq {
162    #[schemars(
163        description = "HK/US security symbol in MARKET.CODE format, e.g. HK.00700 or US.AAPL \
164                       (alias: code / stock / security for SDK compat)"
165    )]
166    #[serde(alias = "code", alias = "stock", alias = "security")]
167    pub symbol: String,
168    #[schemars(description = "Pagination key from previous response; \"-1\" returns empty page")]
169    #[serde(default, alias = "nextKey")]
170    pub next_key: Option<String>,
171    #[schemars(description = "Page size, 1..50; omitted uses backend default 10")]
172    #[serde(default)]
173    pub num: Option<i32>,
174}
175
176impl ShareholdersInstitutionalReq {
177    pub fn validate(&self) -> Result<(), String> {
178        validate_optional_i32_range(
179            "futu_get_shareholders_institutional",
180            "num",
181            self.num,
182            1,
183            50,
184        )
185    }
186}
187
188#[derive(Debug, Deserialize, schemars::JsonSchema)]
189#[serde(deny_unknown_fields)]
190pub struct InsiderHolderListReq {
191    #[schemars(
192        description = "US security symbol in MARKET.CODE format, e.g. US.AAPL \
193                       (alias: code / stock / security for SDK compat)"
194    )]
195    #[serde(alias = "code", alias = "stock", alias = "security")]
196    pub symbol: String,
197    #[schemars(description = "Pagination key from previous response; \"-1\" returns empty page")]
198    #[serde(default, alias = "nextKey")]
199    pub next_key: Option<String>,
200    #[schemars(description = "Page size, 1..20; omitted uses backend default 10")]
201    #[serde(default)]
202    pub num: Option<i32>,
203}
204
205impl InsiderHolderListReq {
206    pub fn validate(&self) -> Result<(), String> {
207        validate_optional_i32_range("futu_get_insider_holder_list", "num", self.num, 1, 20)
208    }
209}
210
211#[derive(Debug, Deserialize, schemars::JsonSchema)]
212#[serde(deny_unknown_fields)]
213pub struct InsiderTradeListReq {
214    #[schemars(
215        description = "US security symbol in MARKET.CODE format, e.g. US.AAPL \
216                       (alias: code / stock / security for SDK compat)"
217    )]
218    #[serde(alias = "code", alias = "stock", alias = "security")]
219    pub symbol: String,
220    #[schemars(description = "Optional insider holder id filter")]
221    #[serde(default, alias = "holderId")]
222    pub holder_id: Option<i64>,
223    #[schemars(description = "Pagination key from previous response; \"-1\" returns empty page")]
224    #[serde(default, alias = "nextKey")]
225    pub next_key: Option<String>,
226    #[schemars(description = "Page size, 1..50; omitted uses backend default 10")]
227    #[serde(default)]
228    pub num: Option<i32>,
229}
230
231impl InsiderTradeListReq {
232    pub fn validate(&self) -> Result<(), String> {
233        validate_optional_i32_range("futu_get_insider_trade_list", "num", self.num, 1, 50)
234    }
235}