1use rmcp::schemars;
4use serde::Deserialize;
5
6fn validate_optional_i32_range(
7 tool: &str,
8 field: &str,
9 value: Option<i32>,
10 min: i32,
11 max: i32,
12) -> Result<(), String> {
13 if let Some(value) = value
14 && !(min..=max).contains(&value)
15 {
16 return Err(format!(
17 "{field} must be in {min}..={max} for {tool}, got {value}"
18 ));
19 }
20 Ok(())
21}
22
23fn validate_optional_i32_where(
24 tool: &str,
25 field: &str,
26 value: Option<i32>,
27 expected: &str,
28 is_valid: impl Fn(i32) -> bool,
29) -> Result<(), String> {
30 if let Some(value) = value
31 && !is_valid(value)
32 {
33 return Err(format!(
34 "{field} must be {expected} for {tool}, got {value}"
35 ));
36 }
37 Ok(())
38}
39
40fn validate_required_yyyymmdd(tool: &str, field: &str, value: &str) -> Result<(), String> {
41 if value.len() != 8 || !value.bytes().all(|b| b.is_ascii_digit()) {
42 return Err(format!(
43 "{field} must be YYYYMMDD for {tool}, got {value:?}"
44 ));
45 }
46 Ok(())
47}
48
49fn validate_i32_list_range(
50 tool: &str,
51 field: &str,
52 values: &[i32],
53 min: i32,
54 max: i32,
55) -> Result<(), String> {
56 for value in values {
57 if !(min..=max).contains(value) {
58 return Err(format!(
59 "{field} entries must be in {min}..={max} for {tool}, got {value}"
60 ));
61 }
62 }
63 Ok(())
64}
65
66fn default_financial_calendar_estimate_type_list() -> Vec<i32> {
67 vec![1, 2, 3]
68}
69
70fn is_financial_calendar_ranking_type(value: i32) -> bool {
71 (1..=7).contains(&value) || value == 100
72}
73
74fn financial_calendar_new_pc_field_selector()
75-> futu_backend::proto_internal::financial_calendar::NewPcFieldSelector {
76 futu_backend::proto_internal::financial_calendar::NewPcFieldSelector {
77 with_pub_type: Some(true),
78 with_pub_time: Some(true),
79 with_financial_period: Some(true),
80 with_option_volume: Some(true),
81 with_iv: Some(true),
82 with_iv_rank: Some(true),
83 with_iv_pctl: Some(true),
84 with_rt_market_cap: Some(true),
85 with_rt_price: Some(true),
86 with_rt_pe_ttm: Some(true),
87 with_rt_price_last_close: Some(true),
88 }
89}
90
91#[derive(Debug, Clone, Deserialize, schemars::JsonSchema)]
92#[serde(deny_unknown_fields)]
93pub struct FinancialCalendarCustomFilterReq {
94 #[schemars(
95 description = "Financial calendar custom filter type: 1=market_cap, 2=option_volume, 3=iv, 4=iv_rank, 5=iv_pctl, 6=rt_market_cap. Alias: customerFilterType"
96 )]
97 #[serde(alias = "customerFilterType")]
98 pub customer_filter_type: i32,
99 #[schemars(description = "Inclusive lower bound. Alias: customerFilterValueStartAt")]
100 #[serde(default, alias = "customerFilterValueStartAt")]
101 pub customer_filter_value_start_at: Option<i64>,
102 #[schemars(description = "Inclusive upper bound. Alias: customerFilterValueEndAt")]
103 #[serde(default, alias = "customerFilterValueEndAt")]
104 pub customer_filter_value_end_at: Option<i64>,
105}
106
107impl FinancialCalendarCustomFilterReq {
108 fn validate(&self) -> Result<(), String> {
109 validate_i32_list_range(
110 "futu_get_financial_calendar",
111 "custom_filter.customer_filter_type",
112 &[self.customer_filter_type],
113 1,
114 6,
115 )
116 }
117
118 fn to_proto(&self) -> futu_backend::proto_internal::financial_calendar::CustomFilter {
119 futu_backend::proto_internal::financial_calendar::CustomFilter {
120 customer_filter_type: Some(self.customer_filter_type),
121 customer_filter_value_start_at: self.customer_filter_value_start_at,
122 customer_filter_value_end_at: self.customer_filter_value_end_at,
123 }
124 }
125}
126
127#[derive(Debug, Deserialize, schemars::JsonSchema)]
128#[serde(deny_unknown_fields)]
129pub struct FinancialCalendarReq {
130 #[schemars(description = "Start date in YYYYMMDD. Alias: beginDate")]
131 #[serde(alias = "beginDate")]
132 pub begin_date: String,
133 #[schemars(description = "Exclusive end date in YYYYMMDD. Alias: endDate")]
134 #[serde(alias = "endDate")]
135 pub end_date: String,
136 #[schemars(
137 description = "Financial calendar market enum list: HK=1, US=2, CN=3, AU=4, SG=5, CA=6, JP=7, MY=8. Alias: marketList"
138 )]
139 #[serde(default, alias = "marketList")]
140 pub market_list: Vec<i32>,
141 #[schemars(
142 description = "Publication type list: 0=regular/same day, 1=pre-market, 2=after-hours. Alias: pubTypeList"
143 )]
144 #[serde(default, alias = "pubTypeList")]
145 pub pub_type_list: Vec<i32>,
146 #[schemars(
147 description = "Stock filter type list: 1=watchlist, 2=holding, 3=special watch. Alias: stockTypeList"
148 )]
149 #[serde(default, alias = "stockTypeList")]
150 pub stock_type_list: Vec<i32>,
151 #[schemars(
152 description = "Convenience switch for watchlist-only calendar view. Pass watchlist_stock_ids when you want client-cache-exact filtering. Alias: watchlistOnly"
153 )]
154 #[serde(default, alias = "watchlistOnly")]
155 pub watchlist_only: bool,
156 #[schemars(
157 description = "Convenience switch for position-only calendar view. Pass holding_stock_ids when you want client-cache-exact filtering. Alias: positionsOnly"
158 )]
159 #[serde(default, alias = "positionsOnly")]
160 pub positions_only: bool,
161 #[schemars(description = "Result limit, 1-1000. Omit to use backend default.")]
162 #[serde(default)]
163 pub count: Option<i32>,
164 #[schemars(description = "Ranking type. Alias: rankingType")]
165 #[serde(default, alias = "rankingType")]
166 pub ranking_type: Option<i32>,
167 #[schemars(
168 description = "Estimate type list: 1=EPS_GAAP, 2=REVENUE, 3=EBIT. Defaults to [1,2,3]. Alias: estimateTypeList"
169 )]
170 #[serde(
171 default = "default_financial_calendar_estimate_type_list",
172 alias = "estimateTypeList"
173 )]
174 pub estimate_type_list: Vec<i32>,
175 #[schemars(description = "Custom filters. Alias: customFilterList")]
176 #[serde(default, alias = "customFilterList")]
177 pub custom_filter_list: Vec<FinancialCalendarCustomFilterReq>,
178 #[schemars(description = "Explicit watchlist stock_id list. Alias: watchlistStockIds")]
179 #[serde(default, alias = "watchlistStockIds")]
180 pub watchlist_stock_ids: Vec<u64>,
181 #[schemars(description = "Explicit holding stock_id list. Alias: holdingStockIds")]
182 #[serde(default, alias = "holdingStockIds")]
183 pub holding_stock_ids: Vec<u64>,
184}
185
186impl FinancialCalendarReq {
187 pub fn validate(&self) -> Result<(), String> {
188 let tool = "futu_get_financial_calendar";
189 validate_required_yyyymmdd(tool, "begin_date", &self.begin_date)?;
190 validate_required_yyyymmdd(tool, "end_date", &self.end_date)?;
191 if self.begin_date >= self.end_date {
192 return Err(
193 "begin_date must be earlier than end_date for futu_get_financial_calendar"
194 .to_string(),
195 );
196 }
197 validate_i32_list_range(tool, "market_list", &self.market_list, 1, 8)?;
198 validate_i32_list_range(tool, "pub_type_list", &self.pub_type_list, 0, 2)?;
199 validate_i32_list_range(tool, "stock_type_list", &self.stock_type_list, 1, 3)?;
200 validate_optional_i32_range(tool, "count", self.count, 1, 1000)?;
201 validate_optional_i32_where(
202 tool,
203 "ranking_type",
204 self.ranking_type,
205 "1..=7 or 100",
206 is_financial_calendar_ranking_type,
207 )?;
208 validate_i32_list_range(tool, "estimate_type_list", &self.estimate_type_list, 1, 3)?;
209 for filter in &self.custom_filter_list {
210 filter.validate()?;
211 }
212 Ok(())
213 }
214
215 pub fn to_proto(
216 &self,
217 ) -> futu_backend::proto_internal::financial_calendar::GetFinancialStatementCalendarViewReq
218 {
219 use futu_backend::proto_internal::financial_calendar as fc;
220
221 let mut stock_type_list = self.stock_type_list.clone();
222 if self.watchlist_only && !stock_type_list.contains(&1) {
223 stock_type_list.push(1);
224 }
225 if self.positions_only && !stock_type_list.contains(&2) {
226 stock_type_list.push(2);
227 }
228
229 fc::GetFinancialStatementCalendarViewReq {
230 market_list: self.market_list.clone(),
231 pub_type_list: self.pub_type_list.clone(),
232 stock_type_list,
233 count: self.count,
234 begin_date: Some(self.begin_date.clone()),
235 end_date: Some(self.end_date.clone()),
236 ranking_type: self.ranking_type,
237 estimate_type_list: self.estimate_type_list.clone(),
238 custom_filter_list: self
239 .custom_filter_list
240 .iter()
241 .map(FinancialCalendarCustomFilterReq::to_proto)
242 .collect(),
243 watchlist_stock_ids: self.watchlist_stock_ids.clone(),
244 holding_stock_ids: self.holding_stock_ids.clone(),
245 field_selector: None,
246 new_pc_field_selector: Some(financial_calendar_new_pc_field_selector()),
247 }
248 }
249}
250
251#[derive(Debug, Deserialize, schemars::JsonSchema)]
252#[serde(deny_unknown_fields)]
253pub struct TargetFinancialCalendarReq {
254 #[schemars(description = "Backend stock_id list to query. Alias: stockId")]
255 #[serde(alias = "stockId")]
256 pub stock_id: Vec<u64>,
257 #[schemars(
258 description = "Financial calendar market enum list: HK=1, US=2, CN=3, AU=4, SG=5, CA=6, JP=7, MY=8. Alias: marketList"
259 )]
260 #[serde(default, alias = "marketList")]
261 pub market_list: Vec<i32>,
262 #[schemars(description = "Number of calendar rows to fetch; must be positive.")]
263 pub size: i32,
264 #[schemars(description = "Pagination start offset. Alias: start")]
265 #[serde(default)]
266 pub start: Option<i32>,
267}
268
269impl TargetFinancialCalendarReq {
270 pub fn validate(&self) -> Result<(), String> {
271 let tool = "futu_search_target_financial_calendar";
272 if self.stock_id.is_empty() || self.stock_id.contains(&0) {
273 return Err(format!(
274 "{tool} stock_id must be a non-empty positive uint64 list"
275 ));
276 }
277 validate_i32_list_range(tool, "market_list", &self.market_list, 1, 8)?;
278 if self.size <= 0 {
279 return Err(format!("{tool} size must be positive, got {}", self.size));
280 }
281 if let Some(start) = self.start
282 && start < 0
283 {
284 return Err(format!("{tool} start must be non-negative, got {start}"));
285 }
286 Ok(())
287 }
288
289 pub fn to_proto(
290 &self,
291 ) -> futu_backend::proto_internal::financial_calendar::SearchTargetStockFinancialCalendarReq
292 {
293 futu_backend::proto_internal::financial_calendar::SearchTargetStockFinancialCalendarReq {
294 stock_id: self.stock_id.clone(),
295 market_list: self.market_list.clone(),
296 size: Some(self.size),
297 start: self.start,
298 }
299 }
300}
301
302fn is_financial_statements_f10_type(value: i32) -> bool {
303 (0..=7).contains(&value) || (9..=11).contains(&value)
307}
308
309fn is_revenue_breakdown_f10_type(value: i32) -> bool {
310 (0..=7).contains(&value) || value == 9
314}
315
316fn is_valuation_type_with_unknown(value: i32) -> bool {
317 (0..=3).contains(&value)
319}
320
321fn is_valuation_plate_sort_id(value: i32) -> bool {
322 matches!(value, 0 | 51 | 52 | 53 | 54)
326}
327
328#[derive(Debug, Deserialize, schemars::JsonSchema)]
329#[serde(deny_unknown_fields)]
330pub struct CompanyExecutiveBackgroundReq {
331 #[schemars(
332 description = "Security symbol in MARKET.CODE format, e.g. HK.00700, US.AAPL \
333 (alias: code / stock / security for SDK compat)"
334 )]
335 #[serde(alias = "code", alias = "stock", alias = "security")]
336 pub symbol: String,
337 #[schemars(description = "Executive/director leader name from get_company_executives")]
338 #[serde(alias = "leaderName")]
339 pub leader_name: String,
340}
341
342#[derive(Debug, Deserialize, schemars::JsonSchema)]
343#[serde(deny_unknown_fields)]
344pub struct CompanyOperationalEfficiencyReq {
345 #[schemars(
346 description = "Security symbol in MARKET.CODE format, e.g. HK.00700, US.AAPL \
347 (alias: code / stock / security for SDK compat)"
348 )]
349 #[serde(alias = "code", alias = "stock", alias = "security")]
350 pub symbol: String,
351 #[schemars(description = "Pagination key from previous response; alias: nextKey")]
352 #[serde(default, alias = "nextKey")]
353 pub next_key: Option<String>,
354 #[schemars(description = "Rows per page, 1-50. Omit to use backend default 10.")]
355 #[serde(default)]
356 pub num: Option<i32>,
357 #[schemars(description = "Currency code, e.g. CNY / USD / HKD; alias: currencyCode")]
358 #[serde(default, alias = "currencyCode")]
359 pub currency_code: Option<String>,
360 #[schemars(
361 description = "Compatibility-only field; the backend ignores financialType for company operational efficiency and always queries the fixed annual view. Alias: financialType"
362 )]
363 #[serde(default, alias = "financialType")]
364 pub financial_type: Option<i32>,
365}
366
367impl CompanyOperationalEfficiencyReq {
368 pub fn validate(&self) -> Result<(), String> {
369 validate_optional_i32_range(
370 "futu_get_company_operational_efficiency",
371 "num",
372 self.num,
373 1,
374 50,
375 )
376 }
377}
378
379#[derive(Debug, Deserialize, schemars::JsonSchema)]
380#[serde(deny_unknown_fields)]
381pub struct FinancialsEarningsPriceMoveReq {
382 #[schemars(
383 description = "Security symbol in MARKET.CODE format, e.g. HK.00700, US.AAPL \
384 (alias: code / stock / security for SDK compat)"
385 )]
386 #[serde(alias = "code", alias = "stock", alias = "security")]
387 pub symbol: String,
388 #[schemars(description = "Financial period count, 1-50. Omit to use backend default 10.")]
389 #[serde(default, alias = "periodCount")]
390 pub period_count: Option<i32>,
391}
392
393impl FinancialsEarningsPriceMoveReq {
394 pub fn validate(&self) -> Result<(), String> {
395 validate_optional_i32_range(
396 "futu_get_financials_earnings_price_move",
397 "period_count",
398 self.period_count,
399 1,
400 50,
401 )
402 }
403}
404
405#[derive(Debug, Deserialize, schemars::JsonSchema)]
406#[serde(deny_unknown_fields)]
407pub struct FinancialsEarningsPriceHistoryReq {
408 #[schemars(
409 description = "Security symbol in MARKET.CODE format, e.g. HK.00700, US.AAPL \
410 (alias: code / stock / security for SDK compat)"
411 )]
412 #[serde(alias = "code", alias = "stock", alias = "security")]
413 pub symbol: String,
414}
415
416#[derive(Debug, Deserialize, schemars::JsonSchema)]
417#[serde(deny_unknown_fields)]
418pub struct FinancialsStatementsReq {
419 #[schemars(
420 description = "Security symbol in MARKET.CODE format, e.g. HK.00700, US.AAPL \
421 (alias: code / stock / security for SDK compat)"
422 )]
423 #[serde(alias = "code", alias = "stock", alias = "security")]
424 pub symbol: String,
425 #[schemars(
426 description = "FinancialStatementsType: 0=unknown, 1=income, 2=balance_sheet, 3=cash_flow, 4=main_index; omit to use backend default 1. Alias: statementType"
427 )]
428 #[serde(default, alias = "statementType")]
429 pub statement_type: Option<i32>,
430 #[schemars(
431 description = "F10Type financial period filter, supports 0-7 and 9-11; omit to use backend default 10. Alias: financialType"
432 )]
433 #[serde(default, alias = "financialType")]
434 pub financial_type: Option<i32>,
435 #[schemars(description = "Currency code, e.g. CNY / USD / HKD; alias: currencyCode")]
436 #[serde(default, alias = "currencyCode")]
437 pub currency_code: Option<String>,
438 #[schemars(description = "Pagination key from previous response; alias: nextKey")]
439 #[serde(default, alias = "nextKey")]
440 pub next_key: Option<String>,
441 #[schemars(description = "Rows per page, 1-50. Omit to use backend default 10.")]
442 #[serde(default)]
443 pub num: Option<i32>,
444}
445
446impl FinancialsStatementsReq {
447 pub fn validate(&self) -> Result<(), String> {
448 validate_optional_i32_range("futu_get_financials_statements", "num", self.num, 1, 50)?;
449 validate_optional_i32_range(
450 "futu_get_financials_statements",
451 "statement_type",
452 self.statement_type,
453 0,
454 4,
455 )?;
456 validate_optional_i32_where(
457 "futu_get_financials_statements",
458 "financial_type",
459 self.financial_type,
460 "0..=7 or 9..=11",
461 is_financial_statements_f10_type,
462 )
463 }
464}
465
466#[derive(Debug, Deserialize, schemars::JsonSchema)]
467#[serde(deny_unknown_fields)]
468pub struct FinancialsRevenueBreakdownReq {
469 #[schemars(
470 description = "Security symbol in MARKET.CODE format, e.g. HK.00700, US.AAPL \
471 (alias: code / stock / security for SDK compat)"
472 )]
473 #[serde(alias = "code", alias = "stock", alias = "security")]
474 pub symbol: String,
475 #[schemars(description = "Screen date timestamp from screenDateList; alias: screenDate")]
476 #[serde(default, alias = "screenDate")]
477 pub date: Option<u32>,
478 #[schemars(
479 description = "F10Type financial period filter, supports 0-7 and 9; omit to use backend default 0. Alias: financialType"
480 )]
481 #[serde(default, alias = "financialType")]
482 pub financial_type: Option<i32>,
483 #[schemars(description = "Currency code, e.g. CNY / USD / HKD; alias: currencyCode")]
484 #[serde(default, alias = "currencyCode")]
485 pub currency_code: Option<String>,
486}
487
488impl FinancialsRevenueBreakdownReq {
489 pub fn validate(&self) -> Result<(), String> {
490 validate_optional_i32_where(
491 "futu_get_financials_revenue_breakdown",
492 "financial_type",
493 self.financial_type,
494 "0..=7 or 9",
495 is_revenue_breakdown_f10_type,
496 )
497 }
498}
499
500#[derive(Debug, Deserialize, schemars::JsonSchema)]
501#[serde(deny_unknown_fields)]
502pub struct ResearchAnalystConsensusReq {
503 #[schemars(
504 description = "Security symbol in MARKET.CODE format, e.g. HK.00700, US.AAPL \
505 (alias: code / stock / security for SDK compat)"
506 )]
507 #[serde(alias = "code", alias = "stock", alias = "security")]
508 pub symbol: String,
509}
510
511#[derive(Debug, Deserialize, schemars::JsonSchema)]
512#[serde(deny_unknown_fields)]
513pub struct ResearchRatingSummaryReq {
514 #[schemars(
515 description = "Security symbol in MARKET.CODE format, e.g. US.AAPL, CA.SHOP \
516 (alias: code / stock / security for SDK compat)"
517 )]
518 #[serde(alias = "code", alias = "stock", alias = "security")]
519 pub symbol: String,
520 #[schemars(description = "ResearchRatingDimensionType: 0/1=institution, 2=analyst")]
521 #[serde(default, alias = "ratingDimensionType")]
522 pub rating_dimension_type: Option<i32>,
523 #[schemars(description = "Institution or analyst uid; empty means summary list")]
524 #[serde(default)]
525 pub uid: Option<String>,
526 #[schemars(description = "Pagination key; alias: nextKey")]
527 #[serde(default, alias = "nextKey")]
528 pub next_key: Option<String>,
529 #[schemars(description = "Page size, 1..20; omitted uses backend default 10")]
530 #[serde(default)]
531 pub num: Option<i32>,
532}
533
534impl ResearchRatingSummaryReq {
535 pub fn validate(&self) -> Result<(), String> {
536 validate_optional_i32_range("futu_get_research_rating_summary", "num", self.num, 1, 20)?;
537 validate_optional_i32_range(
538 "futu_get_research_rating_summary",
539 "rating_dimension_type",
540 self.rating_dimension_type,
541 0,
542 2,
543 )
544 }
545}
546
547#[derive(Debug, Deserialize, schemars::JsonSchema)]
548#[serde(deny_unknown_fields)]
549pub struct ResearchMorningstarReportReq {
550 #[schemars(description = "Security symbol in MARKET.CODE format, e.g. US.AAPL \
551 (alias: code / stock / security for SDK compat)")]
552 #[serde(alias = "code", alias = "stock", alias = "security")]
553 pub symbol: String,
554}
555
556#[derive(Debug, Deserialize, schemars::JsonSchema)]
557#[serde(deny_unknown_fields)]
558pub struct ValuationDetailReq {
559 #[schemars(
560 description = "Security symbol in MARKET.CODE format, e.g. HK.00700, US.AAPL, HK.800000 \
561 (alias: code / stock / security for SDK compat)"
562 )]
563 #[serde(alias = "code", alias = "stock", alias = "security")]
564 pub symbol: String,
565 #[schemars(
566 description = "ValuationType: 0=recommended, 1=PE, 2=PB, 3=PS; alias: valuationType"
567 )]
568 #[serde(default, alias = "valuationType")]
569 pub valuation_type: Option<i32>,
570 #[schemars(
571 description = "ValuationIntervalType: 0/3=1y default, 1=3m, 2=6m, 4=3y, 5=since2019, 6=5y, 7=10y, 8=2y, 9=20y, 10=30y; omit uses backend default 3. Alias: intervalType"
572 )]
573 #[serde(default, alias = "intervalType")]
574 pub interval_type: Option<i32>,
575}
576
577impl ValuationDetailReq {
578 pub fn validate(&self) -> Result<(), String> {
579 validate_optional_i32_where(
580 "futu_get_valuation_detail",
581 "valuation_type",
582 self.valuation_type,
583 "0..=3",
584 is_valuation_type_with_unknown,
585 )?;
586 validate_optional_i32_range(
587 "futu_get_valuation_detail",
588 "interval_type",
589 self.interval_type,
590 0,
591 10,
592 )
593 }
594}
595
596#[derive(Debug, Deserialize, schemars::JsonSchema)]
597#[serde(deny_unknown_fields)]
598pub struct ValuationPlateStockListReq {
599 #[schemars(
600 description = "Plate or index symbol in MARKET.CODE format, e.g. HK.BK0001, HK.800000 \
601 (alias: code / stock / security for SDK compat)"
602 )]
603 #[serde(alias = "code", alias = "stock", alias = "security")]
604 pub symbol: String,
605 #[schemars(description = "ValuationType: 0/1=PE, 2=PB, 3=PS; alias: valuationType")]
606 #[serde(default, alias = "valuationType")]
607 pub valuation_type: Option<i32>,
608 #[schemars(description = "Pagination key; alias: nextKey")]
609 #[serde(default, alias = "nextKey")]
610 pub next_key: Option<String>,
611 #[schemars(description = "Page size, 1..50; omitted uses backend default 10")]
612 #[serde(default)]
613 pub num: Option<i32>,
614 #[schemars(
615 description = "SortType: 0/2=asc, 1=desc; omit to use backend default asc. Alias: sortType"
616 )]
617 #[serde(default, alias = "sortType")]
618 pub sort_type: Option<i32>,
619 #[schemars(
620 description = "SortField: 51=market_cap, 52=valuation, 53=forward, 54=percentile; alias: sortId"
621 )]
622 #[serde(default, alias = "sortId")]
623 pub sort_id: Option<i32>,
624 #[schemars(
625 description = "Optional plate filter for index components, MARKET.CODE; alias: filterSecurity"
626 )]
627 #[serde(default, alias = "filterSecurity")]
628 pub filter_security: Option<String>,
629}
630
631impl ValuationPlateStockListReq {
632 pub fn validate(&self) -> Result<(), String> {
633 validate_optional_i32_range(
634 "futu_get_valuation_plate_stock_list",
635 "num",
636 self.num,
637 1,
638 50,
639 )?;
640 validate_optional_i32_where(
641 "futu_get_valuation_plate_stock_list",
642 "valuation_type",
643 self.valuation_type,
644 "0..=3",
645 is_valuation_type_with_unknown,
646 )?;
647 validate_optional_i32_where(
648 "futu_get_valuation_plate_stock_list",
649 "sort_id",
650 self.sort_id,
651 "0, 51, 52, 53, or 54",
652 is_valuation_plate_sort_id,
653 )
654 }
655}
656
657#[cfg(test)]
658#[path = "f10_tests.rs"]
659mod f10_tests;