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