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, 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
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// ============================================================
104// owner-plate
105// ============================================================
106
107#[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}