futucli/cmd/analysis/short_info/
corporate_actions.rs1use anyhow::{Result, anyhow, bail};
2use prost::Message;
3use serde::Serialize;
4use tabled::Tabled;
5
6use crate::common::{connect_gateway, parse_symbol};
7use crate::output::OutputFormat;
8
9use super::{display_f64, display_i64, display_opt};
10
11#[derive(Tabled)]
12struct CorporateActionsDividendsRow {
13 #[tabled(rename = "Pub Date")]
14 pub_date: String,
15 #[tabled(rename = "Statement")]
16 statement: String,
17 #[tabled(rename = "Process")]
18 process: String,
19 #[tabled(rename = "Record Date")]
20 record_date: String,
21 #[tabled(rename = "Ex Date")]
22 ex_date: String,
23 #[tabled(rename = "Payable Date")]
24 payable_date: String,
25 #[tabled(rename = "Fiscal Year")]
26 fiscal_year: String,
27}
28
29#[derive(Serialize)]
30struct CorporateActionsDividendsJson {
31 symbol: String,
32 s2c: futu_proto::qot_get_corporate_actions_dividends::S2c,
33}
34
35#[derive(Tabled)]
36struct CorporateActionsStockSplitsRow {
37 #[tabled(rename = "Pub Date")]
38 pub_date: String,
39 #[tabled(rename = "Type")]
40 reform_type: String,
41 #[tabled(rename = "Rate")]
42 rate: String,
43 #[tabled(rename = "Ex Date")]
44 ex_date: String,
45 #[tabled(rename = "Status")]
46 status: String,
47 #[tabled(rename = "Temp Code")]
48 temp_code: String,
49 #[tabled(rename = "New Unit")]
50 new_unit: String,
51 #[tabled(rename = "Next Key")]
52 next_key: String,
53}
54
55#[derive(Serialize)]
56struct CorporateActionsStockSplitsJson {
57 symbol: String,
58 s2c: futu_proto::qot_get_corporate_actions_stock_splits::S2c,
59}
60
61pub async fn run_corporate_actions_dividends(
62 gateway: &str,
63 symbol: &str,
64 format: OutputFormat,
65) -> Result<()> {
66 let sec = parse_symbol(symbol)?;
67 let (client, _rx) = connect_gateway(gateway, "futucli-corporate-actions-dividends").await?;
68 let req = futu_proto::qot_get_corporate_actions_dividends::Request {
69 c2s: futu_proto::qot_get_corporate_actions_dividends::C2s {
70 security: futu_proto::qot_common::Security {
71 market: sec.market as i32,
72 code: sec.code.clone(),
73 },
74 },
75 };
76 let frame = client
77 .request(
78 futu_core::proto_id::QOT_GET_CORPORATE_ACTIONS_DIVIDENDS,
79 req.encode_to_vec(),
80 )
81 .await?;
82 let resp =
83 futu_proto::qot_get_corporate_actions_dividends::Response::decode(frame.body.as_ref())
84 .map_err(|e| anyhow!("decode corporate_actions_dividends: {e}"))?;
85 if resp.ret_type != 0 {
86 bail!(
87 "corporate_actions_dividends ret_type={} msg={:?}",
88 resp.ret_type,
89 resp.ret_msg
90 );
91 }
92 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
93 let rows: Vec<_> = s2c
94 .dividend_list
95 .iter()
96 .map(|item| CorporateActionsDividendsRow {
97 pub_date: display_opt(&item.pub_date),
98 statement: display_opt(&item.statement),
99 process: display_opt(&item.process),
100 record_date: display_opt(&item.record_date),
101 ex_date: display_opt(&item.ex_date),
102 payable_date: display_opt(&item.dividend_payable_date),
103 fiscal_year: display_opt(&item.fiscal_year),
104 })
105 .collect();
106 let json = CorporateActionsDividendsJson {
107 symbol: symbol.to_string(),
108 s2c,
109 };
110 format.print_rows(&rows, &[json])?;
111 Ok(())
112}
113
114pub async fn run_corporate_actions_stock_splits(
115 gateway: &str,
116 symbol: &str,
117 next_key: Option<&str>,
118 num: Option<i32>,
119 format: OutputFormat,
120) -> Result<()> {
121 let sec = parse_symbol(symbol)?;
122 let (client, _rx) = connect_gateway(gateway, "futucli-corporate-actions-stock-splits").await?;
123 let req = futu_proto::qot_get_corporate_actions_stock_splits::Request {
124 c2s: futu_proto::qot_get_corporate_actions_stock_splits::C2s {
125 security: futu_proto::qot_common::Security {
126 market: sec.market as i32,
127 code: sec.code.clone(),
128 },
129 next_key: next_key.map(str::to_string),
130 num,
131 },
132 };
133 let frame = client
134 .request(
135 futu_core::proto_id::QOT_GET_CORPORATE_ACTIONS_STOCK_SPLITS,
136 req.encode_to_vec(),
137 )
138 .await?;
139 let resp =
140 futu_proto::qot_get_corporate_actions_stock_splits::Response::decode(frame.body.as_ref())
141 .map_err(|e| anyhow!("decode corporate_actions_stock_splits: {e}"))?;
142 if resp.ret_type != 0 {
143 bail!(
144 "corporate_actions_stock_splits ret_type={} msg={:?}",
145 resp.ret_type,
146 resp.ret_msg
147 );
148 }
149 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
150 let next_key_text = display_opt(&s2c.next_key);
151 let rows: Vec<_> = s2c
152 .split_item_list
153 .iter()
154 .map(|item| CorporateActionsStockSplitsRow {
155 pub_date: display_opt(&item.dir_deci_pub_date_str),
156 reform_type: display_opt(&item.reform_type),
157 rate: display_opt(&item.rate),
158 ex_date: display_opt(&item.ex_date_str),
159 status: display_opt(&item.event_status),
160 temp_code: display_opt(&item.temp_share_code),
161 new_unit: display_i64(item.new_trade_unit),
162 next_key: next_key_text.clone(),
163 })
164 .collect();
165 let json = CorporateActionsStockSplitsJson {
166 symbol: symbol.to_string(),
167 s2c,
168 };
169 format.print_rows(&rows, &[json])?;
170 Ok(())
171}
172
173#[derive(Tabled)]
174struct CorporateActionsBuybacksRow {
175 #[tabled(rename = "Market")]
176 market: String,
177 #[tabled(rename = "Date")]
178 date: String,
179 #[tabled(rename = "Shares")]
180 shares: String,
181 #[tabled(rename = "Amount")]
182 amount: String,
183 #[tabled(rename = "Percent")]
184 percent: String,
185 #[tabled(rename = "Price/Value Range")]
186 range: String,
187 #[tabled(rename = "Share Type")]
188 share_type: String,
189 #[tabled(rename = "Next Key")]
190 next_key: String,
191}
192
193#[derive(Serialize)]
194struct CorporateActionsBuybacksJson {
195 symbol: String,
196 s2c: futu_proto::qot_get_corporate_actions_buybacks::S2c,
197}
198
199pub async fn run_corporate_actions_buybacks(
200 gateway: &str,
201 symbol: &str,
202 next_key: Option<&str>,
203 num: Option<i32>,
204 format: OutputFormat,
205) -> Result<()> {
206 let sec = parse_symbol(symbol)?;
207 let (client, _rx) = connect_gateway(gateway, "futucli-corporate-actions-buybacks").await?;
208 let req = futu_proto::qot_get_corporate_actions_buybacks::Request {
209 c2s: futu_proto::qot_get_corporate_actions_buybacks::C2s {
210 security: futu_proto::qot_common::Security {
211 market: sec.market as i32,
212 code: sec.code.clone(),
213 },
214 next_key: next_key.map(str::to_string),
215 num,
216 },
217 };
218 let frame = client
219 .request(
220 futu_core::proto_id::QOT_GET_CORPORATE_ACTIONS_BUYBACKS,
221 req.encode_to_vec(),
222 )
223 .await?;
224 let resp =
225 futu_proto::qot_get_corporate_actions_buybacks::Response::decode(frame.body.as_ref())
226 .map_err(|e| anyhow!("decode corporate_actions_buybacks: {e}"))?;
227 if resp.ret_type != 0 {
228 bail!(
229 "corporate_actions_buybacks ret_type={} msg={:?}",
230 resp.ret_type,
231 resp.ret_msg
232 );
233 }
234 let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
235 let next_key_text = display_opt(&s2c.next_key);
236 let mut rows = Vec::new();
237 rows.extend(s2c.hk_buy_back_list.iter().map(|item| {
238 let range = match (item.low_price, item.high_price) {
239 (Some(low), Some(high)) => format!("{low}~{high}"),
240 (Some(low), None) => low.to_string(),
241 (None, Some(high)) => high.to_string(),
242 (None, None) => String::new(),
243 };
244 CorporateActionsBuybacksRow {
245 market: "HK".to_string(),
246 date: display_opt(&item.publ_date_str),
247 shares: display_i64(item.buy_back_sum),
248 amount: display_f64(item.buy_back_money),
249 percent: display_f64(item.percentage),
250 range,
251 share_type: display_opt(&item.share_type),
252 next_key: next_key_text.clone(),
253 }
254 }));
255 rows.extend(s2c.a_buy_back_list.iter().map(|item| {
256 let range = match (item.value_floor, item.value_ceiling) {
257 (Some(floor), Some(ceiling)) => format!("{floor}~{ceiling}"),
258 (Some(floor), None) => floor.to_string(),
259 (None, Some(ceiling)) => ceiling.to_string(),
260 (None, None) => match (item.price_floor, item.price_ceiling) {
261 (Some(floor), Some(ceiling)) => format!("{floor}~{ceiling}"),
262 (Some(floor), None) => floor.to_string(),
263 (None, Some(ceiling)) => ceiling.to_string(),
264 (None, None) => String::new(),
265 },
266 };
267 CorporateActionsBuybacksRow {
268 market: "A".to_string(),
269 date: display_opt(&item.advance_date_str),
270 shares: display_i64(item.buy_back_sum),
271 amount: display_f64(item.buy_back_money),
272 percent: display_f64(item.percentage),
273 range,
274 share_type: display_opt(&item.share_type),
275 next_key: next_key_text.clone(),
276 }
277 }));
278 let json = CorporateActionsBuybacksJson {
279 symbol: symbol.to_string(),
280 s2c,
281 };
282 format.print_rows(&rows, &[json])?;
283 Ok(())
284}