Skip to main content

futucli/cmd/analysis/
market_plate.rs

1//! v1.4.110+ split (from cmd/analysis.rs): market_plate domain.
2//!
3//! pub items: run_market_state,run_owner_plate.
4
5use 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// ============================================================
14// market-state
15// ============================================================
16
17#[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
34// v1.4.31: market_state_label 抽到 futu_qot::types::market_state_label 单份维护
35use 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// ============================================================
112// owner-plate
113// ============================================================
114
115#[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}