Skip to main content

futucli/cmd/
plate.rs

1//! `futucli plate-list` / `plate-stocks` — 板块相关
2
3use anyhow::{Result, bail};
4use serde::Serialize;
5use tabled::Tabled;
6
7use crate::common::{connect_gateway, format_symbol, parse_symbol};
8use crate::output::OutputFormat;
9use futu_qot::types::QotMarket;
10
11/// 解析市场字符串(仅 market 名称,不含 code)
12fn parse_market(s: &str) -> Result<QotMarket> {
13    let m = match s.trim().to_ascii_uppercase().as_str() {
14        "HK" => QotMarket::HkSecurity,
15        "HK_FUTURE" => QotMarket::HkFuture,
16        "US" => QotMarket::UsSecurity,
17        "SH" => QotMarket::CnshSecurity,
18        "SZ" => QotMarket::CnszSecurity,
19        other => bail!("unknown market {other:?} (HK|HK_FUTURE|US|SH|SZ)"),
20    };
21    Ok(m)
22}
23
24/// 解析板块集合类型
25fn parse_plate_set(s: &str) -> Result<i32> {
26    let v = match s.trim().to_ascii_lowercase().as_str() {
27        "all" => 0,
28        "industry" => 1,
29        "region" => 2,
30        "concept" => 3,
31        other => bail!("unknown plate-set {other:?} (all|industry|region|concept)"),
32    };
33    Ok(v)
34}
35
36#[derive(Tabled)]
37struct PlateRow {
38    #[tabled(rename = "Plate")]
39    plate: String,
40    #[tabled(rename = "Name")]
41    name: String,
42    #[tabled(rename = "Type")]
43    plate_type: String,
44}
45
46#[derive(Serialize)]
47struct PlateJson {
48    plate: String,
49    name: String,
50    plate_type: Option<i32>,
51}
52
53pub async fn list(gateway: &str, market: &str, set: &str, format: OutputFormat) -> Result<()> {
54    let m = parse_market(market)?;
55    let set_type = parse_plate_set(set)?;
56
57    let (client, _push_rx) = connect_gateway(gateway, "futucli-plate-list").await?;
58    let plates = futu_qot::market_misc::get_plate_set(&client, m, set_type).await?;
59
60    let rows: Vec<PlateRow> = plates
61        .iter()
62        .map(|p| PlateRow {
63            plate: format_symbol(&p.security),
64            name: p.name.clone(),
65            plate_type: p.plate_type.map(|v| v.to_string()).unwrap_or_default(),
66        })
67        .collect();
68
69    let jsons: Vec<PlateJson> = plates
70        .iter()
71        .map(|p| PlateJson {
72            plate: format_symbol(&p.security),
73            name: p.name.clone(),
74            plate_type: p.plate_type,
75        })
76        .collect();
77
78    format.print_rows(&rows, &jsons)?;
79    Ok(())
80}
81
82#[derive(Tabled)]
83struct StockRow {
84    #[tabled(rename = "Symbol")]
85    symbol: String,
86    #[tabled(rename = "Name")]
87    name: String,
88    #[tabled(rename = "ID")]
89    id: String,
90    #[tabled(rename = "Type")]
91    sec_type: i32,
92    #[tabled(rename = "Lot")]
93    lot_size: i32,
94}
95
96#[derive(Serialize)]
97struct StockJson {
98    symbol: String,
99    id: i64,
100    name: String,
101    sec_type: i32,
102    lot_size: i32,
103    list_time: String,
104    delisting: bool,
105}
106
107pub async fn stocks(gateway: &str, plate_symbol: &str, format: OutputFormat) -> Result<()> {
108    let plate = parse_symbol(plate_symbol)?;
109    let (client, _push_rx) = connect_gateway(gateway, "futucli-plate-stocks").await?;
110    let list = futu_qot::market_misc::get_plate_security(&client, &plate).await?;
111
112    let rows: Vec<StockRow> = list
113        .iter()
114        .map(|i| StockRow {
115            symbol: format_symbol(&i.security),
116            name: i.name.clone(),
117            id: i.id.to_string(),
118            sec_type: i.sec_type,
119            lot_size: i.lot_size,
120        })
121        .collect();
122
123    let jsons: Vec<StockJson> = list
124        .iter()
125        .map(|i| StockJson {
126            symbol: format_symbol(&i.security),
127            id: i.id,
128            name: i.name.clone(),
129            sec_type: i.sec_type,
130            lot_size: i.lot_size,
131            list_time: i.list_time.clone(),
132            delisting: i.delisting,
133        })
134        .collect();
135
136    format.print_rows(&rows, &jsons)?;
137    Ok(())
138}