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