Skip to main content

futucli/cmd/
plate.rs

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