futu_mcp/tool_args/
mod.rs1mod push;
11mod qot;
12mod trd;
13
14pub use push::*;
15pub use qot::*;
16pub use trd::*;
17
18use rmcp::schemars;
19use serde::{Deserialize, Serialize};
20
21use crate::tool_enums::ToolEnum;
22
23#[derive(Debug, Default, Deserialize, schemars::JsonSchema)]
29#[serde(deny_unknown_fields)]
30pub struct NoArgs {}
31
32#[derive(Debug, Deserialize, Serialize, schemars::JsonSchema)]
35#[serde(deny_unknown_fields)]
36pub struct ProtoJsonReq {
37 #[schemars(
38 description = "Official generated C2S JSON. Field names use generated proto serde snake_case, e.g. multi_legs / combo_legs / order_type."
39 )]
40 pub c2s_json: String,
41
42 #[schemars(
43 description = "Optional per-call API key plaintext. Priority: tool args > HTTP Bearer > startup key."
44 )]
45 #[serde(default, skip_serializing_if = "Option::is_none")]
46 pub api_key: Option<String>,
47}
48
49fn deser_int_or_order_type_str<'de, D>(deserializer: D) -> std::result::Result<i32, D::Error>
64where
65 D: serde::Deserializer<'de>,
66{
67 #[derive(Deserialize)]
68 #[serde(untagged)]
69 enum IntOrStr {
70 Int(i32),
71 Str(String),
72 }
73 match IntOrStr::deserialize(deserializer)? {
74 IntOrStr::Int(i) => Ok(i),
75 IntOrStr::Str(s) => match s.trim().to_ascii_uppercase().as_str() {
76 "NORMAL" | "LIMIT" => Ok(1),
77 "MARKET" => Ok(2),
78 "ABSOLUTE_LIMIT" => Ok(5),
79 "AUCTION" => Ok(6),
80 "AUCTION_LIMIT" => Ok(7),
81 "SPECIAL_LIMIT" => Ok(8),
82 other => other.parse::<i32>().map_err(|_| {
84 serde::de::Error::custom(format!(
85 "unknown order_type {other:?}: expect integer or one of \
86 NORMAL|LIMIT|MARKET|ABSOLUTE_LIMIT|AUCTION|AUCTION_LIMIT|SPECIAL_LIMIT"
87 ))
88 }),
89 },
90 }
91}
92
93fn deser_order_id_raw_from_int_or_str<'de, D>(
100 deserializer: D,
101) -> std::result::Result<String, D::Error>
102where
103 D: serde::Deserializer<'de>,
104{
105 #[derive(Deserialize)]
106 #[serde(untagged)]
107 enum IntOrStr {
108 Int(u64),
109 Str(String),
110 }
111
112 match IntOrStr::deserialize(deserializer)? {
113 IntOrStr::Int(i) => Ok(i.to_string()),
114 IntOrStr::Str(s) => {
115 let trimmed = s.trim();
116 if trimmed.is_empty() {
117 return Err(serde::de::Error::custom(
118 "invalid order_id string: must not be empty",
119 ));
120 }
121 Ok(trimmed.to_string())
122 }
123 }
124}
125
126fn deser_trd_market_string_allow_empty<'de, D>(
135 deserializer: D,
136) -> std::result::Result<String, D::Error>
137where
138 D: serde::Deserializer<'de>,
139{
140 let opt: Option<serde_json::Value> = Option::deserialize(deserializer)?;
141 match opt {
142 None | Some(serde_json::Value::Null) => Ok(String::new()),
143 Some(serde_json::Value::String(s)) if s.trim().is_empty() => Ok(String::new()),
144 Some(v) => {
145 let e = match v {
147 serde_json::Value::Number(n) => {
148 let raw = n.as_i64().ok_or_else(|| {
149 serde::de::Error::custom(format!("trd_market number invalid: {n}"))
150 })?;
151 let i = i32::try_from(raw).map_err(|_| {
152 serde::de::Error::custom(format!(
153 "trd_market number out of i32 range: {raw}"
154 ))
155 })?;
156 crate::tool_enums::TrdMarketEnum::from_i32(i).ok_or_else(|| {
157 serde::de::Error::custom(format!(
158 "unknown trd_market int {i}: valid = {:?}",
159 crate::tool_enums::TrdMarketEnum::all_int_values()
160 ))
161 })?
162 }
163 serde_json::Value::String(s) => {
164 let t = s.trim();
165 crate::tool_enums::TrdMarketEnum::from_str(t)
166 .or_else(|| {
167 t.parse::<i32>()
168 .ok()
169 .and_then(crate::tool_enums::TrdMarketEnum::from_i32)
170 })
171 .ok_or_else(|| {
172 serde::de::Error::custom(format!(
173 "unknown trd_market {s:?}: valid = {:?}",
174 crate::tool_enums::TrdMarketEnum::all_string_values()
175 ))
176 })?
177 }
178 _ => {
179 return Err(serde::de::Error::custom(format!(
180 "trd_market must be int or string, got: {v}"
181 )));
182 }
183 };
184 let i = e.as_i32();
186 let names = crate::tool_enums::TrdMarketEnum::all_string_values();
187 let ints = crate::tool_enums::TrdMarketEnum::all_int_values();
188 let idx = ints
189 .iter()
190 .position(|&v| v == i)
191 .ok_or_else(|| serde::de::Error::custom("trd_market i32 has no canonical"))?;
192 Ok(names[idx].to_string())
193 }
194 }
195}
196
197fn default_kl_type() -> String {
198 "day".to_string()
199}
200
201fn default_depth() -> i32 {
202 10
203}
204
205fn default_ticker_count() -> i32 {
206 100
207}
208
209fn default_plate_set() -> String {
210 "all".to_string()
211}
212
213fn default_env() -> String {
214 "real".to_string()
215}
216
217fn default_env_simulate() -> String {
218 "simulate".to_string()
219}
220
221fn default_order_type() -> String {
222 "NORMAL".to_string()
223}
224
225fn default_modify_op() -> String {
226 "NORMAL".to_string()
227}
228
229fn default_true() -> bool {
230 true
231}
232
233fn default_rehab_none() -> String {
234 "none".to_string()
235}
236
237fn default_history_kline_max_count() -> Option<i32> {
240 Some(1000)
241}
242
243fn default_reference_type() -> String {
244 "warrant".to_string()
246}
247
248fn default_warrant_num() -> i32 {
249 20
250}
251
252fn default_user_security_group_type() -> i32 {
253 1
254}
255
256fn default_stock_filter_num() -> i32 {
257 50
258}
259
260fn default_is_first_push() -> bool {
261 true
262}
263
264fn default_is_reg_push() -> bool {
265 true
266}