Skip to main content

futucli/cmd/
static_info.rs

1//! `futucli static` — 获取静态信息
2
3use anyhow::Result;
4use serde::Serialize;
5use tabled::Tabled;
6
7use crate::common::{connect_gateway, format_symbol, parse_symbol};
8use crate::output::OutputFormat;
9
10#[derive(Tabled)]
11struct StaticRow {
12    #[tabled(rename = "Symbol")]
13    symbol: String,
14    #[tabled(rename = "Name")]
15    name: String,
16    #[tabled(rename = "ID")]
17    id: String,
18    #[tabled(rename = "Type")]
19    sec_type: i32,
20    #[tabled(rename = "Lot")]
21    lot_size: i32,
22    #[tabled(rename = "Listed")]
23    list_time: String,
24    #[tabled(rename = "Delisting")]
25    delisting: String,
26    /// v1.4.93 P1-3 (BUG-5318-003): exchange_code (e.g. "CME"/"NYSE"/"NYMEX")
27    #[tabled(rename = "Exchange")]
28    exchange_code: String,
29}
30
31#[derive(Serialize)]
32struct StaticJson {
33    symbol: String,
34    id: i64,
35    market: i32,
36    code: String,
37    name: String,
38    sec_type: i32,
39    lot_size: i32,
40    list_time: String,
41    delisting: bool,
42    /// v1.4.93 P1-3 (BUG-5318-003): exchange_code (e.g. "CME"/"NYSE"/"NYMEX")
43    /// 派生自 daemon 的 `derive_exch_type_with_fallback` (cache-first +
44    /// mkt_id fallback). 见 `futu_core::exch_type::exch_type_to_string`.
45    /// `null` 表示 daemon 端 `exch_type=Unknown(0)` 且 mkt_id 未在表中.
46    #[serde(skip_serializing_if = "Option::is_none")]
47    exchange_code: Option<String>,
48}
49
50pub async fn run(gateway: &str, symbols: &[String], format: OutputFormat) -> Result<()> {
51    let secs: Vec<_> = symbols
52        .iter()
53        .map(|s| parse_symbol(s))
54        .collect::<Result<_>>()?;
55
56    let (client, _push_rx) = connect_gateway(gateway, "futucli-static").await?;
57    let infos = futu_qot::static_info::get_static_info(&client, &secs).await?;
58
59    let rows: Vec<StaticRow> = infos
60        .iter()
61        .map(|i| StaticRow {
62            symbol: format_symbol(&i.security),
63            name: i.name.clone(),
64            id: i.id.to_string(),
65            sec_type: i.sec_type,
66            lot_size: i.lot_size,
67            list_time: i.list_time.clone(),
68            delisting: if i.delisting { "YES" } else { "no" }.to_string(),
69            // v1.4.93 P1-3: tabled row 上 unknown 显示为 "-"(不留空; column 头永远在).
70            exchange_code: i.exchange_code().unwrap_or("-").to_string(),
71        })
72        .collect();
73
74    let jsons: Vec<StaticJson> = infos
75        .iter()
76        .map(|i| StaticJson {
77            symbol: format_symbol(&i.security),
78            id: i.id,
79            market: i.security.market as i32,
80            code: i.security.code.clone(),
81            name: i.name.clone(),
82            sec_type: i.sec_type,
83            lot_size: i.lot_size,
84            list_time: i.list_time.clone(),
85            delisting: i.delisting,
86            // v1.4.93 P1-3: JSON 输出 unknown → 字段省略(None + skip_serializing_if).
87            exchange_code: i.exchange_code().map(String::from),
88        })
89        .collect();
90
91    format.print_rows(&rows, &jsons)?;
92    Ok(())
93}