futucli/cmd/analysis/
market_plate.rs1use anyhow::{Result, anyhow, bail};
6use prost::Message;
7use serde::Serialize;
8use tabled::Tabled;
9
10use crate::common::{connect_gateway, format_symbol, parse_symbol};
11use crate::output::OutputFormat;
12
13#[derive(Tabled)]
18struct MarketStateRow {
19 #[tabled(rename = "Symbol")]
20 symbol: String,
21 #[tabled(rename = "Name")]
22 name: String,
23 #[tabled(rename = "State")]
24 state: String,
25}
26
27#[derive(Serialize)]
28struct MarketStateJson {
29 symbol: String,
30 name: String,
31 market_state: i32,
32}
33
34use futu_qot::types::market_state_label;
36
37fn market_state_symbol(sec: &futu_proto::qot_common::Security) -> String {
38 let sec = futu_qot::types::Security::from_proto(sec);
39 format_symbol(&sec)
40}
41
42pub async fn run_market_state(
43 gateway: &str,
44 symbols: &[String],
45 format: OutputFormat,
46) -> Result<()> {
47 if symbols.is_empty() {
48 bail!("no symbols");
49 }
50 let (client, _rx) = connect_gateway(gateway, "futucli-market-state").await?;
51
52 let secs: Vec<_> = symbols
53 .iter()
54 .map(|s| parse_symbol(s))
55 .collect::<Result<Vec<_>>>()?;
56 let sec_protos: Vec<_> = secs
57 .iter()
58 .map(|s| futu_proto::qot_common::Security {
59 market: s.market as i32,
60 code: s.code.clone(),
61 })
62 .collect();
63
64 let req = futu_proto::qot_get_market_state::Request {
65 c2s: futu_proto::qot_get_market_state::C2s {
66 security_list: sec_protos,
67 header: None,
68 },
69 };
70 let body = req.encode_to_vec();
71 let frame = client
72 .request(futu_core::proto_id::QOT_GET_MARKET_STATE, body)
73 .await?;
74 let resp = futu_proto::qot_get_market_state::Response::decode(frame.body.as_ref())?;
75 if resp.ret_type != 0 {
76 bail!("market_state ret_type={}", resp.ret_type);
77 }
78 let s = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
79
80 let rows: Vec<MarketStateRow> = s
81 .market_info_list
82 .iter()
83 .map(|m| MarketStateRow {
84 symbol: market_state_symbol(&m.security),
85 name: m.name.clone(),
86 state: format!(
87 "{} ({})",
88 market_state_label(m.market_state),
89 m.market_state
90 ),
91 })
92 .collect();
93
94 let jsons: Vec<MarketStateJson> = s
95 .market_info_list
96 .iter()
97 .map(|m| MarketStateJson {
98 symbol: market_state_symbol(&m.security),
99 name: m.name.clone(),
100 market_state: m.market_state,
101 })
102 .collect();
103
104 format.print_rows(&rows, &jsons)?;
105 Ok(())
106}
107
108#[cfg(test)]
109mod tests;
110
111#[derive(Tabled)]
116struct OwnerPlateRow {
117 #[tabled(rename = "Symbol")]
118 symbol: String,
119 #[tabled(rename = "Plate")]
120 plate: String,
121 #[tabled(rename = "Name")]
122 name: String,
123 #[tabled(rename = "Type")]
124 plate_type: String,
125}
126
127#[derive(Serialize)]
128struct OwnerPlateJson {
129 symbol: String,
130 plate_code: String,
131 plate_name: String,
132 plate_type: i32,
133}
134
135fn plate_type_label(t: i32) -> &'static str {
136 match t {
137 1 => "Industry",
138 2 => "Region",
139 3 => "Concept",
140 4 => "Other",
141 _ => "Unknown",
142 }
143}
144
145pub async fn run_owner_plate(
146 gateway: &str,
147 symbols: &[String],
148 format: OutputFormat,
149) -> Result<()> {
150 if symbols.is_empty() {
151 bail!("no symbols");
152 }
153 let (client, _rx) = connect_gateway(gateway, "futucli-owner-plate").await?;
154 let secs: Vec<_> = symbols
155 .iter()
156 .map(|s| parse_symbol(s))
157 .collect::<Result<Vec<_>>>()?;
158
159 let s2c = futu_qot::market_misc::get_owner_plate(&client, &secs).await?;
160
161 let mut rows: Vec<OwnerPlateRow> = Vec::new();
162 let mut jsons: Vec<OwnerPlateJson> = Vec::new();
163 for entry in &s2c.owner_plate_list {
164 let sym = format!("{}.{}", entry.security.market, entry.security.code);
165 for p in &entry.plate_info_list {
166 rows.push(OwnerPlateRow {
167 symbol: sym.clone(),
168 plate: p.plate.code.clone(),
169 name: p.name.clone(),
170 plate_type: format!(
171 "{} ({})",
172 plate_type_label(p.plate_type.unwrap_or(0)),
173 p.plate_type.unwrap_or(0)
174 ),
175 });
176 jsons.push(OwnerPlateJson {
177 symbol: sym.clone(),
178 plate_code: p.plate.code.clone(),
179 plate_name: p.name.clone(),
180 plate_type: p.plate_type.unwrap_or(0),
181 });
182 }
183 }
184
185 format.print_rows(&rows, &jsons)?;
186 Ok(())
187}