1use super::*;
2use futu_domain_trade_account::{
3 TradeFundsSnapshotKeyPlan, TradePositionsSnapshotKeyPlan, plan_combo_position_lookup_like_cpp,
4 plan_funds_lookup_keys_like_cpp, plan_funds_write_key_like_cpp,
5 plan_funds_write_key_with_returned_currency_like_cpp, plan_positions_currency_key_like_cpp,
6 plan_positions_key_like_cpp,
7};
8
9use super::freshness::insert_newer_snapshot;
10
11impl TrdCache {
12 pub fn update_funds(&self, acc_id: u64, funds: CachedFunds) {
20 let key = funds_cache_key(plan_funds_write_key_like_cpp(acc_id, 0, None));
21 let snapshot = self.snapshot_freshness.stamp(funds);
22 insert_newer_snapshot(&self.funds, key, snapshot);
23 }
24
25 pub fn update_funds_per_currency(
31 &self,
32 acc_id: u64,
33 currency: Option<i32>,
34 funds: CachedFunds,
35 ) {
36 let key = funds_cache_key(plan_funds_write_key_like_cpp(acc_id, 0, currency));
37 let snapshot = self.snapshot_freshness.stamp(funds);
38 insert_newer_snapshot(&self.funds, key, snapshot);
39 }
40
41 pub fn update_funds_scoped(
47 &self,
48 acc_id: u64,
49 asset_category: i32,
50 currency: Option<i32>,
51 funds: CachedFunds,
52 ) {
53 let key = funds_cache_key(plan_funds_write_key_like_cpp(
54 acc_id,
55 asset_category,
56 currency,
57 ));
58 let snapshot = self.snapshot_freshness.stamp(funds);
59 insert_newer_snapshot(&self.funds, key, snapshot);
60 }
61
62 pub fn update_funds_scoped_with_returned_currency(
72 &self,
73 acc_id: u64,
74 asset_category: i32,
75 requested_currency: Option<i32>,
76 funds: CachedFunds,
77 ) {
78 let write_plan = plan_funds_write_key_with_returned_currency_like_cpp(
79 acc_id,
80 asset_category,
81 requested_currency,
82 funds.currency,
83 );
84 let requested_snapshot = self.snapshot_freshness.stamp(funds.clone());
85 insert_newer_snapshot(
86 &self.funds,
87 funds_cache_key(write_plan.requested),
88 requested_snapshot,
89 );
90
91 if let Some(mirror_key) = write_plan.returned_currency_mirror {
92 let mirror_snapshot = self.snapshot_freshness.stamp(funds);
93 insert_newer_snapshot(&self.funds, funds_cache_key(mirror_key), mirror_snapshot);
94 }
95 }
96
97 #[must_use]
114 pub fn get_funds(&self, acc_id: u64, currency: Option<i32>) -> (Option<CachedFunds>, bool) {
115 self.get_funds_scoped(acc_id, 0, currency)
116 }
117
118 #[must_use]
125 pub fn get_funds_scoped(
126 &self,
127 acc_id: u64,
128 asset_category: i32,
129 currency: Option<i32>,
130 ) -> (Option<CachedFunds>, bool) {
131 let lookup = self.get_funds_scoped_with_freshness(acc_id, asset_category, currency);
132 (lookup.funds, lookup.currency_match)
133 }
134
135 #[must_use]
136 pub fn get_funds_scoped_with_freshness(
137 &self,
138 acc_id: u64,
139 asset_category: i32,
140 currency: Option<i32>,
141 ) -> FundsSnapshotLookup {
142 let lookup_plan = plan_funds_lookup_keys_like_cpp(acc_id, asset_category, currency);
143 let exact_key = funds_cache_key(lookup_plan.exact);
144 if let Some(snapshot) = self.funds.get(&exact_key) {
145 return funds_lookup(&self.snapshot_freshness, exact_key, snapshot.value(), true);
146 }
147 if let Some(legacy_fallback) = lookup_plan.legacy_fallback
150 && let legacy_key = funds_cache_key(legacy_fallback)
151 && let Some(snapshot) = self.funds.get(&legacy_key)
152 {
153 return funds_lookup(
154 &self.snapshot_freshness,
155 legacy_key,
156 snapshot.value(),
157 false,
158 );
159 }
160 if let Some(scan_acc_id) = lookup_plan.scan_acc_id {
163 for entry in self.funds.iter() {
164 if entry.key().acc_id == scan_acc_id {
165 return funds_lookup(
166 &self.snapshot_freshness,
167 *entry.key(),
168 entry.value(),
169 false,
170 );
171 }
172 }
173 }
174 FundsSnapshotLookup {
175 funds: None,
176 currency_match: false,
177 freshness: self.snapshot_freshness.freshness(
178 PositionsCacheKey::scoped(acc_id, asset_category),
179 None::<&StampedTradeSnapshot<CachedFunds>>,
180 ),
181 }
182 }
183
184 pub fn update_positions(&self, acc_id: u64, positions: Vec<CachedPosition>) {
185 self.update_positions_scoped(acc_id, 0, positions);
186 }
187
188 pub fn update_positions_scoped(
189 &self,
190 acc_id: u64,
191 asset_category: i32,
192 positions: Vec<CachedPosition>,
193 ) {
194 let key = positions_cache_key(plan_positions_key_like_cpp(acc_id, asset_category));
195 let snapshot = self.snapshot_freshness.stamp(positions);
196 insert_newer_snapshot(&self.positions, key, snapshot);
197 }
198
199 pub fn update_positions_currency_scoped(
200 &self,
201 acc_id: u64,
202 asset_category: i32,
203 currency: i32,
204 positions: Vec<CachedPosition>,
205 ) {
206 let key = positions_cache_key(plan_positions_currency_key_like_cpp(
207 acc_id,
208 asset_category,
209 currency,
210 ));
211 let snapshot = self.snapshot_freshness.stamp(positions);
212 insert_newer_snapshot(&self.positions, key, snapshot);
213 }
214
215 pub fn update_combo_positions_scoped(
216 &self,
217 acc_id: u64,
218 asset_category: i32,
219 positions: Vec<CachedPosition>,
220 ) {
221 let key = positions_cache_key(plan_positions_key_like_cpp(acc_id, asset_category));
222 let snapshot = self.snapshot_freshness.stamp(positions);
223 insert_newer_snapshot(&self.combo_positions, key, snapshot);
224 }
225
226 #[must_use]
227 pub fn get_positions_scoped(
228 &self,
229 acc_id: u64,
230 asset_category: i32,
231 ) -> Option<Vec<CachedPosition>> {
232 self.get_positions_scoped_with_freshness(acc_id, asset_category)
233 .positions
234 }
235
236 #[must_use]
237 pub fn get_positions_currency_scoped(
238 &self,
239 acc_id: u64,
240 asset_category: i32,
241 currency: i32,
242 ) -> Option<Vec<CachedPosition>> {
243 self.get_positions_currency_scoped_with_freshness(acc_id, asset_category, currency)
244 .positions
245 }
246
247 #[must_use]
248 pub fn get_combo_positions_scoped(
249 &self,
250 acc_id: u64,
251 asset_category: i32,
252 ) -> Option<Vec<CachedPosition>> {
253 self.get_combo_positions_scoped_with_freshness(acc_id, asset_category)
254 .positions
255 }
256
257 #[must_use]
258 pub fn get_positions_scoped_with_freshness(
259 &self,
260 acc_id: u64,
261 asset_category: i32,
262 ) -> PositionsSnapshotLookup {
263 let key = positions_cache_key(plan_positions_key_like_cpp(acc_id, asset_category));
264 positions_lookup(&self.snapshot_freshness, key, self.positions.get(&key))
265 }
266
267 #[must_use]
268 pub fn get_positions_currency_scoped_with_freshness(
269 &self,
270 acc_id: u64,
271 asset_category: i32,
272 currency: i32,
273 ) -> PositionsSnapshotLookup {
274 let key = positions_cache_key(plan_positions_currency_key_like_cpp(
275 acc_id,
276 asset_category,
277 currency,
278 ));
279 positions_lookup(&self.snapshot_freshness, key, self.positions.get(&key))
280 }
281
282 #[must_use]
283 pub fn get_combo_positions_scoped_with_freshness(
284 &self,
285 acc_id: u64,
286 asset_category: i32,
287 ) -> PositionsSnapshotLookup {
288 let key = positions_cache_key(plan_positions_key_like_cpp(acc_id, asset_category));
289 positions_lookup(
290 &self.snapshot_freshness,
291 key,
292 self.combo_positions.get(&key),
293 )
294 }
295
296 #[must_use]
297 pub fn has_positions_scoped(&self, acc_id: u64, asset_category: i32) -> bool {
298 let key = positions_cache_key(plan_positions_key_like_cpp(acc_id, asset_category));
299 self.positions.contains_key(&key)
300 }
301
302 #[must_use]
309 pub fn find_combo_position_item(
310 &self,
311 acc_id: u64,
312 position_id: u64,
313 ) -> Option<CachedPosition> {
314 let lookup_plan = plan_combo_position_lookup_like_cpp(acc_id, position_id);
315 let legacy_plan = lookup_plan.legacy_key?;
316 let legacy_key = positions_cache_key(legacy_plan);
317 if let Some(positions) = self.combo_positions.get(&legacy_key)
318 && let Some(position) = positions
319 .value()
320 .value()
321 .iter()
322 .find(|position| position.position_id == position_id)
323 {
324 return Some(position.clone());
325 }
326 if let Some(scan_acc_id) = lookup_plan.scan_remaining_account_buckets {
327 for entry in self.combo_positions.iter() {
328 if entry.key().acc_id != scan_acc_id || *entry.key() == legacy_key {
329 continue;
330 }
331 if let Some(position) = entry
332 .value()
333 .value()
334 .iter()
335 .find(|position| position.position_id == position_id)
336 {
337 return Some(position.clone());
338 }
339 }
340 }
341 None
342 }
343}
344
345fn funds_lookup(
346 freshness_store: &super::freshness::TradeSnapshotFreshnessStore,
347 key: FundsCacheKey,
348 snapshot: &StampedTradeSnapshot<CachedFunds>,
349 currency_match: bool,
350) -> FundsSnapshotLookup {
351 FundsSnapshotLookup {
352 funds: Some(snapshot.value().clone()),
353 currency_match,
354 freshness: freshness_store.freshness(
355 PositionsCacheKey::scoped(key.acc_id, key.asset_category),
356 Some(snapshot),
357 ),
358 }
359}
360
361fn positions_lookup(
362 freshness_store: &super::freshness::TradeSnapshotFreshnessStore,
363 key: PositionsCacheKey,
364 snapshot: Option<
365 dashmap::mapref::one::Ref<'_, PositionsCacheKey, StampedTradeSnapshot<Vec<CachedPosition>>>,
366 >,
367) -> PositionsSnapshotLookup {
368 let freshness = freshness_store.freshness(key.asset_scope(), snapshot.as_deref());
369 PositionsSnapshotLookup {
370 positions: snapshot.map(|snapshot| snapshot.value().value().clone()),
371 freshness,
372 }
373}
374
375fn funds_cache_key(plan: TradeFundsSnapshotKeyPlan) -> FundsCacheKey {
376 if plan.asset_category != 0 {
377 FundsCacheKey::full(plan.acc_id, plan.asset_category, plan.currency)
378 } else {
379 match plan.currency {
380 Some(currency) => FundsCacheKey::per_currency(plan.acc_id, currency),
381 None => FundsCacheKey::legacy(plan.acc_id),
382 }
383 }
384}
385
386fn positions_cache_key(plan: TradePositionsSnapshotKeyPlan) -> PositionsCacheKey {
387 PositionsCacheKey::full(plan.acc_id, plan.asset_category, plan.currency)
388}