Skip to main content

futu_qot/
static_info.rs

1use futu_core::error::{FutuError, Result};
2use futu_core::proto_id;
3use futu_net::client::FutuClient;
4
5use crate::types::{QotMarket, Security};
6
7/// 股票静态信息
8#[derive(Debug, Clone)]
9pub struct SecurityStaticInfo {
10    pub security: Security,
11    pub id: i64,
12    pub lot_size: i32,
13    pub sec_type: i32,
14    pub name: String,
15    pub list_time: String,
16    pub delisting: bool,
17    /// v1.4.93 P1-3 (BUG-5318-003): 透传 proto `SecurityStaticBasic.exchType`
18    /// (field 9). daemon 端 `make_static_info` 已用
19    /// `derive_exch_type_with_fallback` 把 cache miss / 历史 SQLite 行的
20    /// `exch_type=0` 通过 `mkt_id` 派生(含 US 期货 60-109 range NYMEX/COMEX/
21    /// CBOT/CME/CBOE). 这里只做透传; user-facing `exchange_code` 字符串
22    /// 通过 `Self::exchange_code()` 派生.
23    pub exch_type: i32,
24}
25
26impl SecurityStaticInfo {
27    pub fn from_proto(info: &futu_proto::qot_common::SecurityStaticInfo) -> Self {
28        let basic = &info.basic;
29        Self {
30            security: Security::from_proto(&basic.security),
31            id: basic.id,
32            lot_size: basic.lot_size,
33            sec_type: basic.sec_type,
34            name: basic.name.clone(),
35            list_time: basic.list_time.clone(),
36            delisting: basic.delisting.unwrap_or(false),
37            // v1.4.93 P1-3: proto optional int32 → i32 (proto3 default 0 = Unknown)
38            exch_type: basic.exch_type.unwrap_or(0),
39        }
40    }
41
42    /// v1.4.93 P1-3 (BUG-5318-003): 派生 user-facing exchange code 字符串
43    /// (e.g. "CME"/"NYMEX"/"CBOT"/"CBOE"/"COMEX" for US 期货, "NYSE"/"Nasdaq"
44    /// for US 股, "HK_MainBoard"/"SH"/"SZ" 等). 见
45    /// `futu_core::exch_type::exch_type_to_string` 完整映射表.
46    ///
47    /// 返回 `None` 表示 `Unknown`(0) 或 ExchType 表外值, caller 一般
48    /// `.map(String::from)` 推送到 JSON / display.
49    pub fn exchange_code(&self) -> Option<&'static str> {
50        futu_core::exch_type::exch_type_to_string(self.exch_type)
51    }
52}
53
54/// 获取股票静态信息
55///
56/// 通过股票列表查询静态信息(名称、每手数量、类型等)。
57pub async fn get_static_info(
58    client: &FutuClient,
59    securities: &[Security],
60) -> Result<Vec<SecurityStaticInfo>> {
61    let req = futu_proto::qot_get_static_info::Request {
62        c2s: futu_proto::qot_get_static_info::C2s {
63            market: None,
64            sec_type: None,
65            security_list: securities.iter().map(|s| s.to_proto()).collect(),
66            header: None,
67        },
68    };
69
70    let body = prost::Message::encode_to_vec(&req);
71    let resp_frame = client.request(proto_id::QOT_GET_STATIC_INFO, body).await?;
72
73    let resp: futu_proto::qot_get_static_info::Response =
74        prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
75
76    if resp.ret_type != 0 {
77        return Err(FutuError::ServerError {
78            ret_type: resp.ret_type,
79            msg: resp.ret_msg.unwrap_or_default(),
80        });
81    }
82
83    let s2c = resp
84        .s2c
85        .ok_or(FutuError::Codec("missing s2c in GetStaticInfo".into()))?;
86
87    Ok(s2c
88        .static_info_list
89        .iter()
90        .map(SecurityStaticInfo::from_proto)
91        .collect())
92}
93
94/// 按市场和类型获取股票静态信息
95pub async fn get_static_info_by_market(
96    client: &FutuClient,
97    market: QotMarket,
98    sec_type: i32,
99) -> Result<Vec<SecurityStaticInfo>> {
100    let req = futu_proto::qot_get_static_info::Request {
101        c2s: futu_proto::qot_get_static_info::C2s {
102            market: Some(market as i32),
103            sec_type: Some(sec_type),
104            security_list: vec![],
105            header: None,
106        },
107    };
108
109    let body = prost::Message::encode_to_vec(&req);
110    let resp_frame = client.request(proto_id::QOT_GET_STATIC_INFO, body).await?;
111
112    let resp: futu_proto::qot_get_static_info::Response =
113        prost::Message::decode(resp_frame.body.as_ref()).map_err(FutuError::Proto)?;
114
115    if resp.ret_type != 0 {
116        return Err(FutuError::ServerError {
117            ret_type: resp.ret_type,
118            msg: resp.ret_msg.unwrap_or_default(),
119        });
120    }
121
122    let s2c = resp
123        .s2c
124        .ok_or(FutuError::Codec("missing s2c in GetStaticInfo".into()))?;
125
126    Ok(s2c
127        .static_info_list
128        .iter()
129        .map(SecurityStaticInfo::from_proto)
130        .collect())
131}
132
133#[cfg(test)]
134mod tests;