1use 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}