Skip to main content

futu_qot/
types.rs

1// 行情域通用类型
2// 从 proto 结构体转换为 Rust 友好的类型。
3
4/// 股票标识
5#[derive(Debug, Clone, PartialEq, Eq, Hash)]
6pub struct Security {
7    /// 所属市场(HK / US / SH / SZ / SG / JP / ...)
8    pub market: QotMarket,
9    /// 市场内的原始代码(如 HK `"00700"` / US `"NVDA"` / 期权 `"NVDA261219C150000"`)
10    pub code: String,
11}
12
13impl Security {
14    /// 构造 Security,`code` 支持 `&str` / `String` / 任何 `Into<String>`。
15    pub fn new(market: QotMarket, code: impl Into<String>) -> Self {
16        Self {
17            market,
18            code: code.into(),
19        }
20    }
21
22    /// 从 proto Security 转换
23    pub fn from_proto(s: &futu_proto::qot_common::Security) -> Self {
24        Self {
25            market: QotMarket::from_i32(s.market),
26            code: s.code.clone(),
27        }
28    }
29
30    /// 转换为 proto Security
31    pub fn to_proto(&self) -> futu_proto::qot_common::Security {
32        futu_proto::qot_common::Security {
33            market: self.market as i32,
34            code: self.code.clone(),
35        }
36    }
37}
38
39/// 市场类型
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41#[repr(i32)]
42#[non_exhaustive]
43pub enum QotMarket {
44    Unknown = 0,
45    HkSecurity = 1,
46    HkFuture = 2,
47    UsSecurity = 11,
48    CnshSecurity = 21,
49    CnszSecurity = 22,
50    SgSecurity = 31,
51    JpSecurity = 41,
52    AuSecurity = 51,
53    MySecurity = 61,
54    CaSecurity = 71,
55    FxSecurity = 81,
56    // Ref: proto/Qot_Common.proto:22 `QotMarket_CC_Security = 91`.
57    // Public QOT crypto market; crypto trade still requires account/broker
58    // context and static-cache metadata before write-path routing.
59    Crypto = 91,
60}
61
62impl QotMarket {
63    /// 从 proto i32 值还原;未知值返 [`Self::Unknown`]。
64    pub fn from_i32(v: i32) -> Self {
65        match v {
66            1 => Self::HkSecurity,
67            2 => Self::HkFuture,
68            11 => Self::UsSecurity,
69            21 => Self::CnshSecurity,
70            22 => Self::CnszSecurity,
71            31 => Self::SgSecurity,
72            41 => Self::JpSecurity,
73            51 => Self::AuSecurity,
74            61 => Self::MySecurity,
75            71 => Self::CaSecurity,
76            81 => Self::FxSecurity,
77            91 => Self::Crypto,
78            _ => Self::Unknown,
79        }
80    }
81}
82
83/// 订阅类型
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
85#[repr(i32)]
86#[non_exhaustive]
87pub enum SubType {
88    None = 0,
89    Basic = 1,
90    OrderBook = 2,
91    Ticker = 4,
92    RT = 5,
93    KLDay = 6,
94    KL5Min = 7,
95    KL15Min = 8,
96    KL30Min = 9,
97    KL60Min = 10,
98    KL1Min = 11,
99    KLWeek = 12,
100    KLMonth = 13,
101    Broker = 14,
102    KLQuarter = 15,
103    KLYear = 16,
104    KL3Min = 17,
105    OrderDetail = 18,
106}
107
108/// K 线类型
109#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
110#[repr(i32)]
111#[non_exhaustive]
112pub enum KLType {
113    Unknown = 0,
114    Min1 = 1,
115    Day = 2,
116    Week = 3,
117    Month = 4,
118    Year = 5,
119    Min5 = 6,
120    Min15 = 7,
121    Min30 = 8,
122    Min60 = 9,
123    Min3 = 10,
124    Quarter = 11,
125}
126
127/// 复权类型
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129#[repr(i32)]
130#[non_exhaustive]
131pub enum RehabType {
132    None = 0,
133    Forward = 1,
134    Backward = 2,
135}
136
137/// K 线数据点
138#[derive(Debug, Clone)]
139pub struct KLine {
140    /// 所属 K 线时间(字符串表示,如 `"2026-04-21 09:30:00"`)
141    pub time: String,
142    /// 是否为空白 K 线(该时间点无成交)
143    pub is_blank: bool,
144    /// 最高价
145    pub high_price: f64,
146    /// 开盘价
147    pub open_price: f64,
148    /// 最低价
149    pub low_price: f64,
150    /// 收盘价
151    pub close_price: f64,
152    /// 昨收价
153    pub last_close_price: f64,
154    /// 成交量(股 / 手 / 合约数,视产品而定)
155    pub volume: i64,
156    /// 成交额
157    pub turnover: f64,
158    /// 换手率(百分比)
159    pub turnover_rate: f64,
160    /// 市盈率
161    pub pe: f64,
162    /// 涨跌幅(百分比,含正负)
163    pub change_rate: f64,
164    /// Unix 秒时间戳(用于排序 / 对齐 tz)
165    pub timestamp: f64,
166}
167
168impl KLine {
169    pub fn from_proto(k: &futu_proto::qot_common::KLine) -> Self {
170        Self {
171            time: k.time.clone(),
172            is_blank: k.is_blank,
173            high_price: k.high_price.unwrap_or(0.0),
174            open_price: k.open_price.unwrap_or(0.0),
175            low_price: k.low_price.unwrap_or(0.0),
176            close_price: k.close_price.unwrap_or(0.0),
177            last_close_price: k.last_close_price.unwrap_or(0.0),
178            volume: k.volume.unwrap_or(0),
179            turnover: k.turnover.unwrap_or(0.0),
180            turnover_rate: k.turnover_rate.unwrap_or(0.0),
181            pe: k.pe.unwrap_or(0.0),
182            change_rate: k.change_rate.unwrap_or(0.0),
183            timestamp: k.timestamp.unwrap_or(0.0),
184        }
185    }
186}
187
188/// 基本行情数据
189#[derive(Debug, Clone)]
190pub struct BasicQot {
191    /// 对应证券
192    pub security: Security,
193    /// 是否停牌
194    pub is_suspended: bool,
195    /// 上市日期
196    pub list_time: String,
197    /// 价位(最小变动价,spread)
198    pub price_spread: f64,
199    /// 最新报价更新时间(`"YYYY-MM-DD HH:MM:SS"`)
200    pub update_time: String,
201    /// 今日最高价
202    pub high_price: f64,
203    /// 今日开盘价
204    pub open_price: f64,
205    /// 今日最低价
206    pub low_price: f64,
207    /// 现价(最新成交价)
208    pub cur_price: f64,
209    /// 昨收价
210    pub last_close_price: f64,
211    /// 成交量
212    pub volume: i64,
213    /// 成交额
214    pub turnover: f64,
215    /// 换手率(百分比)
216    pub turnover_rate: f64,
217    /// 振幅(`(high - low) / last_close`,百分比)
218    pub amplitude: f64,
219}
220
221impl BasicQot {
222    pub fn from_proto(q: &futu_proto::qot_common::BasicQot) -> Self {
223        Self {
224            security: Security::from_proto(&q.security),
225            is_suspended: q.is_suspended,
226            list_time: q.list_time.clone(),
227            price_spread: q.price_spread,
228            update_time: q.update_time.clone(),
229            high_price: q.high_price,
230            open_price: q.open_price,
231            low_price: q.low_price,
232            cur_price: q.cur_price,
233            last_close_price: q.last_close_price,
234            volume: q.volume,
235            turnover: q.turnover,
236            turnover_rate: q.turnover_rate,
237            amplitude: q.amplitude,
238        }
239    }
240}
241
242/// 摆盘数据项
243#[derive(Debug, Clone)]
244pub struct OrderBookEntry {
245    /// 档位价格
246    pub price: f64,
247    /// 该档位合计挂单量
248    pub volume: i64,
249    /// 该档位挂单笔数
250    pub order_count: i32,
251}
252
253impl OrderBookEntry {
254    pub fn from_proto(ob: &futu_proto::qot_common::OrderBook) -> Self {
255        Self {
256            price: ob.price,
257            volume: ob.volume,
258            order_count: ob.oreder_count, // proto 中拼写为 oreder_count
259        }
260    }
261}
262
263/// 摆盘数据
264#[derive(Debug, Clone)]
265pub struct OrderBookData {
266    /// 对应证券
267    pub security: Security,
268    /// 卖档列表(升序,index 0 为卖一)
269    pub ask_list: Vec<OrderBookEntry>,
270    /// 买档列表(降序,index 0 为买一)
271    pub bid_list: Vec<OrderBookEntry>,
272}
273
274/// `Qot_Common.QotMarketState` enum → 人读 label。
275///
276/// 严格对齐 `proto/Qot_Common.proto:83-124` 的 QotMarketState 枚举(含夜市 /
277/// 期货日市 / HkCas 港股收盘竞价 / 美股夜盘等)。
278///
279/// 历史:v1.4.25 加 `market-state` 命令时把 label 写错了(`Closed=6` 被叫
280/// 成 `ClosedToday` 等),v1.4.30 严格按 proto 重写。v1.4.31 从 futucli 抽
281/// 到这里统一维护,避免两处拷贝再次漂移。
282pub fn market_state_label(s: i32) -> &'static str {
283    match s {
284        0 => "None",                  // 无交易
285        1 => "Auction",               // 竞价
286        2 => "WaitingOpen",           // 早盘前等待开盘
287        3 => "Morning",               // 早盘
288        4 => "Rest",                  // 午间休市
289        5 => "Afternoon",             // 午盘
290        6 => "Closed",                // 收盘
291        8 => "PreMarketBegin",        // 盘前
292        9 => "PreMarketEnd",          // 盘前结束
293        10 => "AfterHoursBegin",      // 盘后
294        11 => "AfterHoursEnd",        // 盘后结束
295        12 => "FutuSwitchDate",       // 切换日
296        13 => "NightOpen",            // 夜市开盘
297        14 => "NightEnd",             // 夜市收盘
298        15 => "FutureDayOpen",        // 期货日市开盘
299        16 => "FutureDayBreak",       // 期货日市休市
300        17 => "FutureDayClose",       // 期货日市收盘
301        18 => "FutureDayWaitForOpen", // 期货日市等待开盘
302        19 => "HkCas",                // 港股收盘竞价
303        20 => "FutureNightWait",      // 夜市等待开盘(已废弃)
304        21 => "FutureAfternoon",      // 期货下午开盘(已废弃)
305        22 => "FutureSwitchDate",     // 期货切交易日(已废弃)
306        23 => "FutureOpen",           // 期货开盘
307        24 => "FutureBreak",          // 期货中盘休息
308        25 => "FutureBreakOver",      // 期货休息后开盘
309        26 => "FutureClose",          // 期货收盘
310        27 => "StibAfterHoursWait",   // 科创板盘后撮合等待(已废弃)
311        28 => "StibAfterHoursBegin",  // 科创板盘后交易开始(已废弃)
312        29 => "StibAfterHoursEnd",    // 科创板盘后交易结束(已废弃)
313        30 => "CloseAuction",         // 收市竞价
314        31 => "AfternoonEnd",         // 已收盘
315        32 => "Night",                // 交易中
316        33 => "OvernightBegin",       // 夜盘开始
317        34 => "OvernightEnd",         // 夜盘结束
318        35 => "TradeAtLast",          // 收盘前成交
319        36 => "TradeAuction",         // 收盘前竞价
320        37 => "Overnight",            // 美股夜盘交易时段
321        _ => "Unknown",
322    }
323}
324
325#[cfg(test)]
326mod tests;