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