Skip to main content

futucli/cmd/analysis/
skill_wrap.rs

1//! v10.6 SkillWrap unusual summary commands.
2
3use anyhow::{Result, anyhow, bail};
4use prost::Message;
5use serde::Serialize;
6use tabled::Tabled;
7
8use futu_proto::skill_wrap_api;
9
10use crate::common::connect_gateway;
11use crate::output::OutputFormat;
12
13trait SkillWrapRsp {
14    fn ret_type(&self) -> i32;
15    fn ret_msg(&self) -> Option<&str>;
16    fn err_code(&self) -> Option<i32>;
17    fn time_range(&self) -> Option<&str>;
18    fn content(&self) -> Option<&str>;
19}
20
21impl SkillWrapRsp for skill_wrap_api::TechnicalUnusualRsp {
22    fn ret_type(&self) -> i32 {
23        self.ret_type
24    }
25
26    fn ret_msg(&self) -> Option<&str> {
27        self.ret_msg.as_deref()
28    }
29
30    fn err_code(&self) -> Option<i32> {
31        self.err_code
32    }
33
34    fn time_range(&self) -> Option<&str> {
35        self.time_range.as_deref()
36    }
37
38    fn content(&self) -> Option<&str> {
39        self.content.as_deref()
40    }
41}
42
43impl SkillWrapRsp for skill_wrap_api::FinancialUnusualRsp {
44    fn ret_type(&self) -> i32 {
45        self.ret_type
46    }
47
48    fn ret_msg(&self) -> Option<&str> {
49        self.ret_msg.as_deref()
50    }
51
52    fn err_code(&self) -> Option<i32> {
53        self.err_code
54    }
55
56    fn time_range(&self) -> Option<&str> {
57        self.time_range.as_deref()
58    }
59
60    fn content(&self) -> Option<&str> {
61        self.content.as_deref()
62    }
63}
64
65impl SkillWrapRsp for skill_wrap_api::DerivativeUnusualRsp {
66    fn ret_type(&self) -> i32 {
67        self.ret_type
68    }
69
70    fn ret_msg(&self) -> Option<&str> {
71        self.ret_msg.as_deref()
72    }
73
74    fn err_code(&self) -> Option<i32> {
75        self.err_code
76    }
77
78    fn time_range(&self) -> Option<&str> {
79        self.time_range.as_deref()
80    }
81
82    fn content(&self) -> Option<&str> {
83        self.content.as_deref()
84    }
85}
86
87#[derive(Tabled)]
88struct SkillWrapUnusualRow {
89    #[tabled(rename = "Symbol")]
90    stock_symbol: String,
91    #[tabled(rename = "Range")]
92    time_range: String,
93    #[tabled(rename = "Err")]
94    err_code: String,
95    #[tabled(rename = "Content")]
96    content: String,
97}
98
99#[derive(Serialize)]
100struct SkillWrapUnusualJson {
101    stock_symbol: String,
102    time_range: Option<String>,
103    content: Option<String>,
104    err_code: Option<i32>,
105    ret_msg: Option<String>,
106}
107
108async fn run_skill_wrap_unusual<Rsp>(
109    gateway: &str,
110    client_label: &str,
111    proto_id: u32,
112    decode_label: &str,
113    stock_symbol: &str,
114    body: Vec<u8>,
115    format: OutputFormat,
116) -> Result<()>
117where
118    Rsp: Message + Default + SkillWrapRsp,
119{
120    let (client, _rx) = connect_gateway(gateway, client_label).await?;
121    let frame = client.request(proto_id, body).await?;
122    let resp =
123        Rsp::decode(frame.body.as_ref()).map_err(|e| anyhow!("decode {decode_label}: {e}"))?;
124    if resp.ret_type() != 0 {
125        bail!(
126            "{decode_label} ret_type={} msg={:?}",
127            resp.ret_type(),
128            resp.ret_msg()
129        );
130    }
131    let json = SkillWrapUnusualJson {
132        stock_symbol: stock_symbol.to_string(),
133        time_range: resp.time_range().map(str::to_string),
134        content: resp.content().map(str::to_string),
135        err_code: resp.err_code(),
136        ret_msg: resp.ret_msg().map(str::to_string),
137    };
138    let rows = [SkillWrapUnusualRow {
139        stock_symbol: stock_symbol.to_string(),
140        time_range: json.time_range.clone().unwrap_or_default(),
141        err_code: json
142            .err_code
143            .map(|value| value.to_string())
144            .unwrap_or_default(),
145        content: json.content.clone().unwrap_or_default(),
146    }];
147    format.print_rows(&rows, &[json])?;
148    Ok(())
149}
150
151pub async fn run_technical_unusual(
152    gateway: &str,
153    stock_symbol: &str,
154    time_range: Option<i32>,
155    indicator_filters: Vec<String>,
156    language_id: Option<i32>,
157    format: OutputFormat,
158) -> Result<()> {
159    let req = skill_wrap_api::TechnicalUnusualReq {
160        stock_symbol: Some(stock_symbol.to_string()),
161        time_range,
162        indicator_filters,
163        language_id,
164    };
165    run_skill_wrap_unusual::<skill_wrap_api::TechnicalUnusualRsp>(
166        gateway,
167        "futucli-technical-unusual",
168        futu_core::proto_id::QOT_GET_TECHNICAL_UNUSUAL,
169        "technical_unusual",
170        stock_symbol,
171        req.encode_to_vec(),
172        format,
173    )
174    .await
175}
176
177pub async fn run_financial_unusual(
178    gateway: &str,
179    stock_symbol: &str,
180    time_range: Option<i32>,
181    analysis_dimensions: Vec<String>,
182    language_id: Option<i32>,
183    format: OutputFormat,
184) -> Result<()> {
185    let req = skill_wrap_api::FinancialUnusualReq {
186        stock_symbol: Some(stock_symbol.to_string()),
187        time_range,
188        analysis_dimensions,
189        language_id,
190    };
191    run_skill_wrap_unusual::<skill_wrap_api::FinancialUnusualRsp>(
192        gateway,
193        "futucli-financial-unusual",
194        futu_core::proto_id::QOT_GET_FINANCIAL_UNUSUAL,
195        "financial_unusual",
196        stock_symbol,
197        req.encode_to_vec(),
198        format,
199    )
200    .await
201}
202
203pub async fn run_derivative_unusual(
204    gateway: &str,
205    stock_symbol: &str,
206    time_range: Option<i32>,
207    analysis_dimensions: Vec<String>,
208    language_id: Option<i32>,
209    format: OutputFormat,
210) -> Result<()> {
211    let req = skill_wrap_api::DerivativeUnusualReq {
212        stock_symbol: Some(stock_symbol.to_string()),
213        time_range,
214        analysis_dimensions,
215        language_id,
216    };
217    run_skill_wrap_unusual::<skill_wrap_api::DerivativeUnusualRsp>(
218        gateway,
219        "futucli-derivative-unusual",
220        futu_core::proto_id::QOT_GET_DERIVATIVE_UNUSUAL,
221        "derivative_unusual",
222        stock_symbol,
223        req.encode_to_vec(),
224        format,
225    )
226    .await
227}