Skip to main content

futucli/cmd/analysis/company/
valuation.rs

1use anyhow::{Result, anyhow, bail};
2use prost::Message;
3use tabled::Tabled;
4
5use crate::common::{connect_gateway, parse_symbol};
6use crate::output::OutputFormat;
7
8use super::{display_opt, display_opt_f64, display_opt_i32};
9
10#[derive(Tabled)]
11struct ValuationDetailRow {
12    #[tabled(rename = "Symbol")]
13    symbol: String,
14    #[tabled(rename = "Type")]
15    valuation_type: String,
16    #[tabled(rename = "Updated")]
17    updated: String,
18    #[tabled(rename = "Current")]
19    current: String,
20    #[tabled(rename = "Average")]
21    average: String,
22    #[tabled(rename = "Percentile")]
23    percentile: String,
24    #[tabled(rename = "Forward")]
25    forward: String,
26    #[tabled(rename = "Market Total")]
27    market_total: String,
28    #[tabled(rename = "Plate")]
29    plate: String,
30    #[tabled(rename = "Profit")]
31    profit: String,
32}
33
34pub async fn run_valuation_detail(
35    gateway: &str,
36    symbol: &str,
37    valuation_type: Option<i32>,
38    interval_type: Option<i32>,
39    format: OutputFormat,
40) -> Result<()> {
41    let sec = parse_symbol(symbol)?;
42    let (client, _rx) = connect_gateway(gateway, "futucli-valuation-detail").await?;
43
44    let req = futu_proto::qot_get_valuation_detail::Request {
45        c2s: futu_proto::qot_get_valuation_detail::C2s {
46            security: futu_proto::qot_common::Security {
47                market: sec.market as i32,
48                code: sec.code.clone(),
49            },
50            valuation_type,
51            interval_type,
52        },
53    };
54    let body = req.encode_to_vec();
55    let frame = client
56        .request(futu_core::proto_id::QOT_GET_VALUATION_DETAIL, body)
57        .await?;
58    let resp = futu_proto::qot_get_valuation_detail::Response::decode(frame.body.as_ref())
59        .map_err(|e| anyhow!("decode valuation_detail: {e}"))?;
60    if resp.ret_type != 0 {
61        bail!(
62            "valuation_detail ret_type={} msg={:?}",
63            resp.ret_type,
64            resp.ret_msg
65        );
66    }
67    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
68    let trend = s2c.trend.as_ref();
69    let market = s2c.market_distribution.as_ref();
70    let plate = s2c.plate_distribution.as_ref();
71    let profit = s2c.profit_growth_rate.as_ref();
72    let plate_name = plate.and_then(|v| v.plate_name.clone());
73    let profit_conclusion = profit.and_then(|v| v.conclusion_detailed.clone());
74    let rows = [ValuationDetailRow {
75        symbol: symbol.to_string(),
76        valuation_type: display_opt_i32(s2c.valuation_type),
77        updated: display_opt(&s2c.last_update_time_str),
78        current: display_opt_f64(trend.and_then(|v| v.current_value)),
79        average: display_opt_f64(trend.and_then(|v| v.average_value)),
80        percentile: display_opt_f64(trend.and_then(|v| v.valuation_percentile)),
81        forward: display_opt_f64(trend.and_then(|v| v.forward_value)),
82        market_total: display_opt_i32(market.and_then(|v| v.total)),
83        plate: display_opt(&plate_name),
84        profit: display_opt(&profit_conclusion),
85    }];
86    let json = serde_json::json!({
87        "symbol": symbol,
88        "s2c": s2c,
89    });
90    format.print_rows(&rows, &[json])?;
91    Ok(())
92}
93
94#[derive(Tabled)]
95struct ValuationPlateStockListRow {
96    #[tabled(rename = "Symbol")]
97    symbol: String,
98    #[tabled(rename = "Name")]
99    name: String,
100    #[tabled(rename = "Valuation")]
101    valuation: String,
102    #[tabled(rename = "Forward")]
103    forward: String,
104    #[tabled(rename = "Percentile")]
105    percentile: String,
106    #[tabled(rename = "Market Cap")]
107    market_cap: String,
108    #[tabled(rename = "Next Key")]
109    next_key: String,
110}
111
112pub async fn run_valuation_plate_stock_list(
113    gateway: &str,
114    symbol: &str,
115    valuation_type: Option<i32>,
116    next_key: Option<&str>,
117    num: Option<i32>,
118    sort_type: Option<i32>,
119    sort_id: Option<i32>,
120    filter_security: Option<&str>,
121    format: OutputFormat,
122) -> Result<()> {
123    let sec = parse_symbol(symbol)?;
124    let filter_security = match filter_security {
125        Some(symbol) if !symbol.trim().is_empty() => {
126            let sec = parse_symbol(symbol)?;
127            Some(futu_proto::qot_common::Security {
128                market: sec.market as i32,
129                code: sec.code,
130            })
131        }
132        _ => None,
133    };
134    let (client, _rx) = connect_gateway(gateway, "futucli-valuation-plate-stock-list").await?;
135
136    let req = futu_proto::qot_get_valuation_plate_stock_list::Request {
137        c2s: futu_proto::qot_get_valuation_plate_stock_list::C2s {
138            security: futu_proto::qot_common::Security {
139                market: sec.market as i32,
140                code: sec.code.clone(),
141            },
142            valuation_type,
143            next_key: next_key.map(str::to_string),
144            num,
145            sort_type,
146            sort_id,
147            filter_security,
148        },
149    };
150    let body = req.encode_to_vec();
151    let frame = client
152        .request(
153            futu_core::proto_id::QOT_GET_VALUATION_PLATE_STOCK_LIST,
154            body,
155        )
156        .await?;
157    let resp =
158        futu_proto::qot_get_valuation_plate_stock_list::Response::decode(frame.body.as_ref())
159            .map_err(|e| anyhow!("decode valuation_plate_stock_list: {e}"))?;
160    if resp.ret_type != 0 {
161        bail!(
162            "valuation_plate_stock_list ret_type={} msg={:?}",
163            resp.ret_type,
164            resp.ret_msg
165        );
166    }
167    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
168
169    let rows: Vec<ValuationPlateStockListRow> = s2c
170        .stock_list
171        .iter()
172        .map(|stock| {
173            let stock_symbol = stock
174                .security
175                .as_ref()
176                .map(|sec| format!("{}.{}", sec.market, sec.code))
177                .unwrap_or_default();
178            ValuationPlateStockListRow {
179                symbol: stock_symbol,
180                name: display_opt(&stock.name),
181                valuation: display_opt_f64(stock.valuation_val),
182                forward: display_opt_f64(stock.forward_value),
183                percentile: display_opt_f64(stock.valuation_percentile),
184                market_cap: display_opt_f64(stock.market_cap),
185                next_key: display_opt(&s2c.next_key),
186            }
187        })
188        .collect();
189    let json = serde_json::json!({
190        "symbol": symbol,
191        "count": s2c.count,
192        "stock_list": s2c.stock_list,
193        "next_key": s2c.next_key,
194        "plate_list": s2c.plate_list,
195    });
196    format.print_rows(&rows, &[json])?;
197    Ok(())
198}