Skip to main content

futu_mcp/tool_args/qot/
reference.rs

1//! MCP QOT request schemas split from the tool-args root.
2
3use super::*;
4
5#[derive(Debug, Deserialize, schemars::JsonSchema)]
6#[serde(deny_unknown_fields)]
7pub struct HistoryKLineReq {
8    #[schemars(description = "Security symbol (MARKET.CODE); alias: code / stock")]
9    // v1.4.83 §5 Phase 3
10    #[serde(alias = "code", alias = "stock")]
11    pub symbol: String,
12    #[schemars(
13        description = "K-line type: day|week|month|quarter|year|1min|3min|5min|15min|30min|60min \
14                       (default day); alias: ktype / k_type"
15    )]
16    #[serde(default = "default_kl_type", alias = "ktype", alias = "k_type")]
17    pub kl_type: String,
18    #[schemars(description = "Rehab type: none|forward|backward (default none)")]
19    #[serde(default = "default_rehab_none")]
20    pub rehab_type: String,
21    #[schemars(description = "Start date yyyy-MM-dd; alias: begin_time / start_time / from")]
22    #[serde(alias = "begin_time", alias = "start_time", alias = "from")]
23    pub begin: String,
24    #[schemars(description = "End date yyyy-MM-dd; alias: end_time / to")]
25    #[serde(alias = "end_time", alias = "to")]
26    pub end: String,
27    #[schemars(
28        description = "Max number of candles to return (default 1000, range 1-1000). \
29                       If omitted, the gateway uses 1000; pass explicit 0 to request no limit \
30                       only when you can handle a large response. alias: num / count / req_count"
31    )]
32    // v1.4.106 codex 0635 ζ36 F5: schema 文案承诺 default 1000, 给一个真 default.
33    // 之前是 Option<i32> 不带 serde(default), MCP 用户省略 max_count 实际不是 1000
34    // 而是不传限制 → 响应规模超 schema 预期 → 上下文膨胀.
35    #[serde(
36        default = "default_history_kline_max_count",
37        alias = "num",
38        alias = "count",
39        alias = "req_count"
40    )]
41    pub max_count: Option<i32>,
42}
43
44impl HistoryKLineReq {
45    pub fn validated_max_count(&self) -> Result<Option<i32>, String> {
46        futu_qot::page_bounds::validate_optional_max_count(self.max_count, 1000, "history_kline")
47            .map_err(|e| e.to_string())
48    }
49}
50
51#[derive(Debug, Deserialize, schemars::JsonSchema)]
52#[serde(deny_unknown_fields)]
53pub struct ReferenceReq {
54    #[schemars(description = "Underlying symbol (e.g. HK.00700, US.AAPL); alias: code / stock")]
55    // v1.4.84 §5 B1
56    #[serde(alias = "code", alias = "stock")]
57    pub symbol: String,
58    // v1.4.41 (external reviewer v1.4.40 报告 P2.5 修): description 从 "warrant|future|option
59    // (default option)" 改成 "warrant|future (default warrant)"。真实 backend
60    // 不支持 reference_type=option(返 "unsupported reference type"),旧 schema
61    // 谎报误导 LLM agent。option 场景用 `futu_get_option_chain` 或 `futu_snapshot`。
62    #[schemars(
63        description = "Reference type: warrant|future (default warrant). Note: option is NOT supported — use futu_get_option_chain instead."
64    )]
65    #[serde(default = "default_reference_type")]
66    pub reference_type: String,
67}
68
69#[derive(Debug, Deserialize, schemars::JsonSchema)]
70#[serde(deny_unknown_fields)]
71pub struct OptionChainReq {
72    #[schemars(
73        description = "Underlying stock symbol (e.g. HK.00700, US.AAPL); alias: symbol / owner / code / stock"
74    )]
75    // v1.4.84 §5 B1
76    #[serde(alias = "symbol", alias = "owner", alias = "code", alias = "stock")]
77    pub owner_symbol: String,
78    #[schemars(
79        description = "Expiry range begin date yyyy-MM-dd; alias: begin / start_time / from"
80    )]
81    #[serde(alias = "begin", alias = "start_time", alias = "from")]
82    pub begin_time: String,
83    #[schemars(description = "Expiry range end date yyyy-MM-dd; alias: end / to")]
84    #[serde(alias = "end", alias = "to")]
85    pub end_time: String,
86    #[schemars(description = "Option type: all|call|put (default all)")]
87    pub option_type: Option<String>,
88
89    // v1.4.38 Phase 3: server-side Greek filter (via backend CMD 6736)
90    // 所有 filter 字段 optional;全 None 时不发送 filter 请求,保持 v1.4.37 行为
91    #[schemars(
92        description = "Optional Greek filter: only return options with delta in [min, max]; min must be <= max when both are provided. Typical ATM range: 0.3 to 0.7 for calls, -0.7 to -0.3 for puts."
93    )]
94    pub delta_min: Option<f64>,
95    #[schemars(description = "See delta_min; must be >= delta_min when both are provided.")]
96    pub delta_max: Option<f64>,
97    #[schemars(
98        description = "Implied volatility filter min (decimal, e.g. 0.3 = 30%); must be <= iv_max when both are provided."
99    )]
100    pub iv_min: Option<f64>,
101    #[schemars(description = "See iv_min; must be >= iv_min when both are provided.")]
102    pub iv_max: Option<f64>,
103    #[schemars(
104        description = "Open interest (contracts) filter min, integer; must be <= oi_max when both are provided."
105    )]
106    pub oi_min: Option<f64>,
107    #[schemars(description = "See oi_min; must be >= oi_min when both are provided.")]
108    pub oi_max: Option<f64>,
109    #[schemars(
110        description = "Gamma filter min (decimal); must be <= gamma_max when both are provided."
111    )]
112    pub gamma_min: Option<f64>,
113    #[schemars(description = "See gamma_min; must be >= gamma_min when both are provided.")]
114    pub gamma_max: Option<f64>,
115    #[schemars(
116        description = "Vega filter min (decimal); must be <= vega_max when both are provided."
117    )]
118    pub vega_min: Option<f64>,
119    #[schemars(description = "See vega_min; must be >= vega_min when both are provided.")]
120    pub vega_max: Option<f64>,
121    #[schemars(
122        description = "Theta filter min (decimal); must be <= theta_max when both are provided."
123    )]
124    pub theta_min: Option<f64>,
125    #[schemars(description = "See theta_min; must be >= theta_min when both are provided.")]
126    pub theta_max: Option<f64>,
127}
128
129impl OptionChainReq {
130    pub fn validate(&self) -> Result<(), String> {
131        let tool = "futu_get_option_chain";
132        validate_optional_f64_min_max(tool, "iv", self.iv_min, self.iv_max)?;
133        validate_optional_f64_min_max(tool, "delta", self.delta_min, self.delta_max)?;
134        validate_optional_f64_min_max(tool, "gamma", self.gamma_min, self.gamma_max)?;
135        validate_optional_f64_min_max(tool, "vega", self.vega_min, self.vega_max)?;
136        validate_optional_f64_min_max(tool, "theta", self.theta_min, self.theta_max)?;
137        validate_optional_f64_min_max(tool, "oi", self.oi_min, self.oi_max)?;
138        Ok(())
139    }
140}
141
142#[derive(Debug, Deserialize, schemars::JsonSchema)]
143#[serde(deny_unknown_fields)]
144pub struct WarrantReq {
145    #[schemars(
146        description = "Underlying stock symbol (e.g. HK.00700); None = whole-market warrants. Alias: symbol / owner / code"
147    )]
148    // v1.4.84 §5 B1
149    #[serde(default, alias = "symbol", alias = "owner", alias = "code")]
150    pub owner_symbol: Option<String>,
151    /// v1.4.106 codex 0635 ζ36 F1: 暴露 begin 让用户能拿下一页. 之前 wrapper
152    /// 硬编码 begin=0, 响应却含 last_page / all_count, 调用者看到 "还有下一页"
153    /// 但无法翻页. C++ backend 自始支持 data_from / data_max_count.
154    #[schemars(description = "Pagination begin index (default 0); alias: offset / skip")]
155    #[serde(default, alias = "offset", alias = "skip")]
156    pub begin: i32,
157    #[schemars(description = "Max rows (0-200, default 20); alias: count / max_count / req_count")]
158    #[serde(
159        default = "default_warrant_num",
160        alias = "count",
161        alias = "max_count",
162        alias = "req_count"
163    )]
164    pub num: i32,
165}
166
167impl WarrantReq {
168    pub fn validate(&self) -> Result<(), String> {
169        futu_qot::page_bounds::validate_begin_num(self.begin, self.num, 200, "warrant")
170            .map(|_| ())
171            .map_err(|err| err.to_string())
172    }
173}
174
175#[derive(Debug, Deserialize, schemars::JsonSchema)]
176#[serde(deny_unknown_fields)]
177pub struct IpoListReq {
178    #[schemars(
179        description = "Market code accepted by the IPO list backend. Accept int (1=HK, 2=HK_FUTURE, 11=US, 21=SH/CN, 22=SZ, 31=SG, 41=JP, 61=MY) \
180                       OR string (\"HK\" / \"HK_FUTURE\" / \"US\" / \"SH\" / \"SZ\" / \"CN\" / \"SG\" / \"JP\" / \"MY\")."
181    )]
182    // v1.4.84 §5 B2 field migration
183    #[serde(deserialize_with = "deser_hk_hkfuture_us_cn_market_as_i32")]
184    pub market: i32,
185}
186
187impl IpoListReq {
188    pub fn validate(&self) -> Result<(), String> {
189        if matches!(self.market, 1 | 2 | 11 | 21 | 22 | 31 | 41 | 61) {
190            return Ok(());
191        }
192        Err(format!(
193            "futu_get_ipo_list market must be 1, 2, 11, 21, 22, 31, 41, or 61 (HK/HK_Future/US/SH/SZ/SG/JP/MY), got {}",
194            self.market
195        ))
196    }
197}
198
199#[derive(Debug, Deserialize, schemars::JsonSchema)]
200#[serde(deny_unknown_fields)]
201pub struct FutureInfoReq {
202    #[schemars(
203        description = "Array of future contract symbols in MARKET.CODE format \
204                       (e.g. [\"HK.HSImain\", \"US.MNQmain\"]). Alias: stocks / \
205                       code_list / symbol_list / security_list"
206    )]
207    // v1.4.84 §5 B1
208    #[serde(
209        alias = "stocks",
210        alias = "code_list",
211        alias = "symbol_list",
212        alias = "security_list"
213    )]
214    pub symbols: Vec<String>,
215}