Skip to main content

futu_mcp/handlers/reference/
skill_wrap.rs

1//! MCP adapters for Futu API v10.6 SkillWrap unusual endpoints.
2
3use 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;