Skip to main content

futu_cache/qot_cache/
kline.rs

1use futu_core::qot_stock_key::QotSecurityKey;
2
3use super::QotCache;
4
5/// K 线缓存
6#[derive(Debug, Clone)]
7pub struct CachedKLine {
8    pub time: String,
9    pub open_price: f64,
10    pub high_price: f64,
11    pub low_price: f64,
12    pub close_price: f64,
13    pub volume: i64,
14    pub turnover: f64,
15}
16
17/// K-line cache dimensions that must travel together.
18///
19/// Keeps the C++ cache dimensions `(rehab, kl_type, session)` as one typed value
20/// so call sites cannot accidentally swap positional `i32` arguments.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub struct KlineDims {
23    pub rehab: i32,
24    pub kl_type: i32,
25    pub session: i32,
26}
27
28impl KlineDims {
29    #[must_use]
30    pub const fn new(rehab: i32, kl_type: i32, session: i32) -> Self {
31        Self {
32            rehab,
33            kl_type,
34            session,
35        }
36    }
37}
38
39impl QotCache {
40    /// 构造 K 线 cache key (v1.4.106 codex 1140 F3 4-tuple).
41    ///
42    /// 之前 key 仅 `(sec_key, kl_type)` 2-tuple, 同股票同 KLType 但前复权 vs
43    /// 后复权 / RTH vs ETH 数据互相覆盖. 对齐 C++ APIServer_Qot_KL.cpp:
44    /// `GetNewestKLByCount(stock_id, enRehabType, enKLType, num, session, ...)`
45    /// 用 4 维 key.
46    ///
47    /// - `rehab`: proto Qot_Common.RehabType (0=None, 1=Forward, 2=Backward),
48    ///   对齐 backend `FTCmdKline.ExrightType`. 同一股票同一 kl_type 不同 rehab
49    ///   走独立 cache, 不互相覆盖.
50    /// - `kl_type`: proto Qot_Common.KLType (1=1Min, 2=Day, ..., 11=Quarter).
51    /// - `session`: proto FTCmdKline.RequestSection (0=NORMAL, 1=FULL,
52    ///   2=PREMARKET, 3=AFTERHOURS). RTH/ETH 隔离, push 来自 backend 的
53    ///   `point.section_type[0]` 决定写入桶, read 由 client subscription
54    ///   session 决定 (尚无, 默认 NORMAL).
55    pub fn make_kline_key_by_dims(sec_key: &str, dims: KlineDims) -> String {
56        format!(
57            "{sec_key}:r{}:k{}:s{}",
58            dims.rehab, dims.kl_type, dims.session
59        )
60    }
61
62    pub fn make_kline_key(sec_key: &str, rehab: i32, kl_type: i32, session: i32) -> String {
63        Self::make_kline_key_by_dims(sec_key, KlineDims::new(rehab, kl_type, session))
64    }
65
66    /// 更新 K 线 (v1.4.106 codex 1140 F3: 4-tuple key, rehab + session 隔离).
67    pub fn update_klines_by_dims(&self, sec_key: &str, dims: KlineDims, klines: Vec<CachedKLine>) {
68        let cache_key = Self::make_kline_key_by_dims(sec_key, dims);
69        self.klines.insert(cache_key, klines);
70    }
71
72    pub fn update_klines(
73        &self,
74        sec_key: &str,
75        rehab: i32,
76        kl_type: i32,
77        session: i32,
78        klines: Vec<CachedKLine>,
79    ) {
80        self.update_klines_by_dims(sec_key, KlineDims::new(rehab, kl_type, session), klines);
81    }
82
83    /// 获取 K 线 (v1.4.106 codex 1140 F3: 4-tuple key, rehab + session 隔离).
84    pub fn get_klines_by_dims(&self, sec_key: &str, dims: KlineDims) -> Option<Vec<CachedKLine>> {
85        let cache_key = Self::make_kline_key_by_dims(sec_key, dims);
86        self.klines.get(&cache_key).map(|v| v.clone())
87    }
88
89    pub fn get_klines(
90        &self,
91        sec_key: &str,
92        rehab: i32,
93        kl_type: i32,
94        session: i32,
95    ) -> Option<Vec<CachedKLine>> {
96        self.get_klines_by_dims(sec_key, KlineDims::new(rehab, kl_type, session))
97    }
98
99    /// **v1.4.110 Phase 2 Slice 5**: 更新 K 线 (broker-aware).
100    ///
101    /// 用 `QotSecurityKey::cache_key()` 作 prefix, broker_id=None 时退化到原行为.
102    /// composite 维度仍是 4-tuple `(rehab, kl_type, session)`, broker_id 是第 5
103    /// 维通过 `QotSecurityKey` 注入到 prefix.
104    pub fn update_klines_broker_by_dims(
105        &self,
106        key: &QotSecurityKey,
107        dims: KlineDims,
108        klines: Vec<CachedKLine>,
109    ) {
110        let cache_key = Self::make_kline_key_by_dims(&key.cache_key(), dims);
111        self.klines.insert(cache_key, klines);
112    }
113
114    pub fn update_klines_broker(
115        &self,
116        key: &QotSecurityKey,
117        rehab: i32,
118        kl_type: i32,
119        session: i32,
120        klines: Vec<CachedKLine>,
121    ) {
122        self.update_klines_broker_by_dims(key, KlineDims::new(rehab, kl_type, session), klines);
123    }
124
125    /// **v1.4.110 Phase 2 Slice 5**: 获取 K 线 (broker-aware).
126    pub fn get_klines_broker_by_dims(
127        &self,
128        key: &QotSecurityKey,
129        dims: KlineDims,
130    ) -> Option<Vec<CachedKLine>> {
131        let cache_key = Self::make_kline_key_by_dims(&key.cache_key(), dims);
132        self.klines.get(&cache_key).map(|v| v.clone())
133    }
134
135    pub fn get_klines_broker(
136        &self,
137        key: &QotSecurityKey,
138        rehab: i32,
139        kl_type: i32,
140        session: i32,
141    ) -> Option<Vec<CachedKLine>> {
142        self.get_klines_broker_by_dims(key, KlineDims::new(rehab, kl_type, session))
143    }
144}