1use dashmap::DashMap;
7
8pub type SecurityKey = String;
10
11pub fn make_key(market: i32, code: &str) -> SecurityKey {
13 format!("{market}_{code}")
14}
15
16#[derive(Debug, Clone)]
18pub struct CachedBasicQot {
19 pub cur_price: f64,
20 pub open_price: f64,
21 pub high_price: f64,
22 pub low_price: f64,
23 pub last_close_price: f64,
24 pub volume: i64,
25 pub turnover: f64,
26 pub turnover_rate: f64,
27 pub amplitude: f64,
28 pub is_suspended: bool,
29 pub update_time: String,
30 pub update_timestamp: f64,
31}
32
33#[derive(Debug, Clone)]
35pub struct CachedKLine {
36 pub time: String,
37 pub open_price: f64,
38 pub high_price: f64,
39 pub low_price: f64,
40 pub close_price: f64,
41 pub volume: i64,
42 pub turnover: f64,
43}
44
45#[derive(Debug, Clone, Default)]
47pub struct CachedOrderBook {
48 pub ask_list: Vec<CachedOrderBookLevel>,
49 pub bid_list: Vec<CachedOrderBookLevel>,
50 pub svr_recv_time_bid: Option<String>,
51 pub svr_recv_time_bid_timestamp: Option<f64>,
52 pub svr_recv_time_ask: Option<String>,
53 pub svr_recv_time_ask_timestamp: Option<f64>,
54}
55
56#[derive(Debug, Clone)]
58pub struct CachedOrderBookLevel {
59 pub price: f64,
60 pub volume: i64,
61 pub order_count: i32,
62}
63
64#[derive(Debug, Clone)]
66pub struct CachedTicker {
67 pub time: String,
68 pub sequence: i64, pub dir: i32, pub price: f64,
71 pub volume: i64,
72 pub turnover: f64, pub recv_time: Option<f64>,
74 pub ticker_type: Option<i32>,
75 pub push_data_type: Option<i32>,
76 pub timestamp: Option<f64>,
77}
78
79#[derive(Debug, Clone)]
81pub struct CachedTimeShare {
82 pub time: String,
83 pub minute: i32,
84 pub price: f64,
85 pub last_close_price: f64,
86 pub avg_price: f64,
87 pub volume: i64,
88 pub turnover: f64,
89 pub timestamp: f64,
90}
91
92#[derive(Debug, Clone, Default)]
94pub struct CachedBroker {
95 pub bid_list: Vec<CachedBrokerItem>,
96 pub ask_list: Vec<CachedBrokerItem>,
97}
98
99#[derive(Debug, Clone)]
101pub struct CachedBrokerItem {
102 pub id: i64,
103 pub name: String,
104 pub pos: i32,
105}
106
107pub struct QotCache {
109 pub basic_qot: DashMap<SecurityKey, CachedBasicQot>,
111 pub klines: DashMap<String, Vec<CachedKLine>>,
113 pub order_books: DashMap<SecurityKey, CachedOrderBook>,
115 pub tickers: DashMap<SecurityKey, Vec<CachedTicker>>,
117 pub rt_data: DashMap<SecurityKey, Vec<CachedTimeShare>>,
119 pub brokers: DashMap<SecurityKey, CachedBroker>,
121}
122
123impl QotCache {
124 pub fn new() -> Self {
125 Self {
126 basic_qot: DashMap::new(),
127 klines: DashMap::new(),
128 order_books: DashMap::new(),
129 tickers: DashMap::new(),
130 rt_data: DashMap::new(),
131 brokers: DashMap::new(),
132 }
133 }
134
135 pub fn update_basic_qot(&self, key: &str, qot: CachedBasicQot) {
137 self.basic_qot.insert(key.to_string(), qot);
138 }
139
140 pub fn get_basic_qot(&self, key: &str) -> Option<CachedBasicQot> {
142 self.basic_qot.get(key).map(|v| v.clone())
143 }
144
145 pub fn update_klines(&self, key: &str, kl_type: i32, klines: Vec<CachedKLine>) {
147 let cache_key = format!("{key}:{kl_type}");
148 self.klines.insert(cache_key, klines);
149 }
150
151 pub fn get_klines(&self, key: &str, kl_type: i32) -> Option<Vec<CachedKLine>> {
153 let cache_key = format!("{key}:{kl_type}");
154 self.klines.get(&cache_key).map(|v| v.clone())
155 }
156
157 pub fn update_order_book(&self, key: &str, ob: CachedOrderBook) {
159 self.order_books.insert(key.to_string(), ob);
160 }
161
162 pub fn append_tickers(&self, key: &str, new_tickers: Vec<CachedTicker>) {
164 let mut entry = self.tickers.entry(key.to_string()).or_default();
165 entry.extend(new_tickers);
166 if entry.len() > 1000 {
167 let drain_count = entry.len() - 1000;
168 entry.drain(..drain_count);
169 }
170 }
171
172 pub fn update_broker(&self, key: &str, broker: CachedBroker) {
174 self.brokers.insert(key.to_string(), broker);
175 }
176
177 pub fn get_broker(&self, key: &str) -> Option<CachedBroker> {
179 self.brokers.get(key).map(|v| v.clone())
180 }
181
182 pub fn clear_security(&self, key: &str) {
184 self.basic_qot.remove(key);
185 self.order_books.remove(key);
186 self.tickers.remove(key);
187 self.brokers.remove(key);
188 let prefix = format!("{key}:");
190 self.klines.retain(|k, _| !k.starts_with(&prefix));
191 }
192}
193
194impl Default for QotCache {
195 fn default() -> Self {
196 Self::new()
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 #[test]
205 fn test_basic_qot_cache() {
206 let cache = QotCache::new();
207 let key = make_key(1, "00700");
208
209 assert!(cache.get_basic_qot(&key).is_none());
210
211 cache.update_basic_qot(
212 &key,
213 CachedBasicQot {
214 cur_price: 500.0,
215 open_price: 495.0,
216 high_price: 510.0,
217 low_price: 490.0,
218 last_close_price: 498.0,
219 volume: 10_000_000,
220 turnover: 5_000_000_000.0,
221 turnover_rate: 0.12,
222 amplitude: 4.0,
223 is_suspended: false,
224 update_time: "2026-04-09".to_string(),
225 update_timestamp: 0.0,
226 },
227 );
228
229 let cached = cache.get_basic_qot(&key).unwrap();
230 assert_eq!(cached.cur_price, 500.0);
231 }
232
233 #[test]
234 fn test_ticker_cap() {
235 let cache = QotCache::new();
236 let key = make_key(1, "00700");
237
238 let tickers: Vec<_> = (0..1100)
240 .map(|i| CachedTicker {
241 time: format!("t{i}"),
242 sequence: i,
243 price: i as f64,
244 volume: 100,
245 turnover: i as f64 * 100.0,
246 dir: 1,
247 recv_time: None,
248 ticker_type: None,
249 push_data_type: None,
250 timestamp: None,
251 })
252 .collect();
253
254 cache.append_tickers(&key, tickers);
255 let cached = cache.tickers.get(&key).unwrap();
256 assert_eq!(cached.len(), 1000);
257 assert_eq!(cached[0].time, "t100"); }
259}