futu_mcp/handlers/reference/
skill_wrap.rs1use std::{sync::Arc, time::Duration};
4
5use anyhow::{Result, anyhow, bail};
6use futu_net::client::FutuClient;
7use prost::Message;
8use serde::Serialize;
9
10use futu_proto::skill_wrap_api;
11
12const SKILL_WRAP_MCP_REQUEST_TIMEOUT: Duration = Duration::from_secs(8);
13
14trait SkillWrapRsp {
15 fn ret_type(&self) -> i32;
16 fn ret_msg(&self) -> Option<&str>;
17 fn err_code(&self) -> Option<i32>;
18 fn time_range(&self) -> Option<&str>;
19 fn content(&self) -> Option<&str>;
20}
21
22impl SkillWrapRsp for skill_wrap_api::TechnicalUnusualRsp {
23 fn ret_type(&self) -> i32 {
24 self.ret_type
25 }
26
27 fn ret_msg(&self) -> Option<&str> {
28 self.ret_msg.as_deref()
29 }
30
31 fn err_code(&self) -> Option<i32> {
32 self.err_code
33 }
34
35 fn time_range(&self) -> Option<&str> {
36 self.time_range.as_deref()
37 }
38
39 fn content(&self) -> Option<&str> {
40 self.content.as_deref()
41 }
42}
43
44impl SkillWrapRsp for skill_wrap_api::FinancialUnusualRsp {
45 fn ret_type(&self) -> i32 {
46 self.ret_type
47 }
48
49 fn ret_msg(&self) -> Option<&str> {
50 self.ret_msg.as_deref()
51 }
52
53 fn err_code(&self) -> Option<i32> {
54 self.err_code
55 }
56
57 fn time_range(&self) -> Option<&str> {
58 self.time_range.as_deref()
59 }
60
61 fn content(&self) -> Option<&str> {
62 self.content.as_deref()
63 }
64}
65
66impl SkillWrapRsp for skill_wrap_api::DerivativeUnusualRsp {
67 fn ret_type(&self) -> i32 {
68 self.ret_type
69 }
70
71 fn ret_msg(&self) -> Option<&str> {
72 self.ret_msg.as_deref()
73 }
74
75 fn err_code(&self) -> Option<i32> {
76 self.err_code
77 }
78
79 fn time_range(&self) -> Option<&str> {
80 self.time_range.as_deref()
81 }
82
83 fn content(&self) -> Option<&str> {
84 self.content.as_deref()
85 }
86}
87
88#[derive(Serialize)]
89struct SkillWrapUnusualOut {
90 stock_symbol: String,
91 time_range: Option<String>,
92 content: Option<String>,
93 err_code: Option<i32>,
94 ret_msg: Option<String>,
95}
96
97async fn request_skill_wrap<Rsp>(
98 client: &Arc<FutuClient>,
99 proto_id: u32,
100 decode_label: &str,
101 stock_symbol: &str,
102 body: Vec<u8>,
103) -> Result<String>
104where
105 Rsp: Message + Default + SkillWrapRsp,
106{
107 let frame = client
108 .request_with_timeout(proto_id, body, SKILL_WRAP_MCP_REQUEST_TIMEOUT)
109 .await?;
110 format_skill_wrap_response::<Rsp>(decode_label, stock_symbol, frame.body.as_ref())
111}
112
113fn format_skill_wrap_response<Rsp>(
114 decode_label: &str,
115 stock_symbol: &str,
116 body: &[u8],
117) -> Result<String>
118where
119 Rsp: Message + Default + SkillWrapRsp,
120{
121 let resp = Rsp::decode(body).map_err(|e| anyhow!("decode {decode_label}: {e}"))?;
122 if resp.ret_type() != 0 {
123 bail!(
124 "{decode_label} ret_type={} msg={:?}",
125 resp.ret_type(),
126 resp.ret_msg()
127 );
128 }
129 let out = SkillWrapUnusualOut {
130 stock_symbol: stock_symbol.to_string(),
131 time_range: resp.time_range().map(str::to_string),
132 content: resp.content().map(str::to_string),
133 err_code: resp.err_code(),
134 ret_msg: resp.ret_msg().map(str::to_string),
135 };
136 Ok(serde_json::to_string_pretty(&out)?)
137}
138
139pub async fn get_technical_unusual(
140 client: &Arc<FutuClient>,
141 stock_symbol: &str,
142 time_range: Option<i32>,
143 indicator_filters: Vec<String>,
144 language_id: Option<i32>,
145) -> Result<String> {
146 let req = skill_wrap_api::TechnicalUnusualReq {
147 stock_symbol: Some(stock_symbol.to_string()),
148 time_range,
149 indicator_filters,
150 language_id,
151 };
152 request_skill_wrap::<skill_wrap_api::TechnicalUnusualRsp>(
153 client,
154 futu_core::proto_id::QOT_GET_TECHNICAL_UNUSUAL,
155 "technical_unusual",
156 stock_symbol,
157 req.encode_to_vec(),
158 )
159 .await
160}
161
162pub async fn get_financial_unusual(
163 client: &Arc<FutuClient>,
164 stock_symbol: &str,
165 time_range: Option<i32>,
166 analysis_dimensions: Vec<String>,
167 language_id: Option<i32>,
168) -> Result<String> {
169 let req = skill_wrap_api::FinancialUnusualReq {
170 stock_symbol: Some(stock_symbol.to_string()),
171 time_range,
172 analysis_dimensions,
173 language_id,
174 };
175 request_skill_wrap::<skill_wrap_api::FinancialUnusualRsp>(
176 client,
177 futu_core::proto_id::QOT_GET_FINANCIAL_UNUSUAL,
178 "financial_unusual",
179 stock_symbol,
180 req.encode_to_vec(),
181 )
182 .await
183}
184
185pub async fn get_derivative_unusual(
186 client: &Arc<FutuClient>,
187 stock_symbol: &str,
188 time_range: Option<i32>,
189 analysis_dimensions: Vec<String>,
190 language_id: Option<i32>,
191) -> Result<String> {
192 let req = skill_wrap_api::DerivativeUnusualReq {
193 stock_symbol: Some(stock_symbol.to_string()),
194 time_range,
195 analysis_dimensions,
196 language_id,
197 };
198 request_skill_wrap::<skill_wrap_api::DerivativeUnusualRsp>(
199 client,
200 futu_core::proto_id::QOT_GET_DERIVATIVE_UNUSUAL,
201 "derivative_unusual",
202 stock_symbol,
203 req.encode_to_vec(),
204 )
205 .await
206}
207
208#[cfg(test)]
209mod tests;