futucli/cmd/analysis/
skill_wrap.rs1use 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}