Skip to main content

futucli/cmd/analysis/short_info/
corporate_actions.rs

1use 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}