1use std::collections::BTreeMap;
4
5use crate::account::ClearinghouseState;
6use crate::account::FrontendOrder;
7use crate::account::positions::Position;
8use crate::account::{Abstraction, Beside, Collateral, Prices, Standing, Surface, Usable, Worth, combined};
9use crate::client::{Network, USDC_INDEX};
10use crate::live::store::account::{Account, Keep};
11use crate::live::store::convert;
12use crate::live::store::interest::{Changes, Handle, Interest, Interests};
13use crate::live::store::market::{BARS, Context, Markets, fold_bar, fold_prints};
14use crate::live::store::stamp::Stamped;
15use crate::live::{Book, Candle, Frame, Interval, LedgerUpdate, Level, Limits, Refused, Session, Subscription, Trade};
16use crate::markets::perps::{MetaAndAssetCtxs, PerpCtx, UserFunding};
17use crate::markets::split_coin;
18use crate::markets::spot::{SpotCtx, SpotMeta, SpotMetaAndAssetCtxs};
19use crate::markets::{L2Book, MarketTrade, Mids};
20use crate::utils::{Address, Dec};
21
22#[derive(Debug, Clone, Copy)]
24pub enum Read<'a> {
25 Book(&'a L2Book),
27 Trades(&'a [MarketTrade]),
29 Candles(&'a [crate::markets::Candle]),
31 Mids { dex: &'a str, mids: &'a Mids },
33 Perps(&'a MetaAndAssetCtxs),
35 Dex { dex: &'a str, read: &'a crate::markets::dexes::DexMetaAndAssetCtxs },
37 Dexes(&'a crate::markets::dexes::Dexes),
39 Spot(&'a SpotMetaAndAssetCtxs),
41 SpotMeta(&'a SpotMeta),
43 Clearinghouse { user: Address, dex: &'a str, state: &'a ClearinghouseState },
45 SpotState { user: Address, state: &'a crate::account::SpotState },
47 Mode { user: Address, mode: &'a Abstraction },
49 Orders { user: Address, dex: &'a str, orders: &'a [FrontendOrder] },
51 Fills { user: Address, fills: &'a [crate::account::fills::Fill] },
53 MoreFills { user: Address, fills: &'a [crate::account::fills::Fill] },
55 Fundings { user: Address, fundings: &'a [UserFunding] },
57 Ledger { user: Address, rows: &'a [LedgerUpdate] },
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
64pub enum Folded {
65 Changed,
67 Same,
69 Dropped,
71}
72
73#[derive(Debug, Clone, Default, PartialEq, Eq)]
75pub struct Synced {
76 pub frames: Vec<String>,
78 pub refused: Vec<(Subscription, Refused)>,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
84#[error("this store is on {store} and the session on {session}; one store is one network")]
85pub struct OtherNetwork {
86 pub store: Network,
87 pub session: Network,
88}
89
90#[derive(Debug)]
92pub struct Store {
93 network: Network,
94 interests: Interests,
95 markets: Markets,
96 accounts: BTreeMap<Address, Account>,
97}
98
99impl Store {
100 #[must_use]
102 pub fn new(network: Network) -> Store {
103 Store {
104 network,
105 interests: Interests::new(Limits::VENUE),
106 markets: Markets::default(),
107 accounts: BTreeMap::new(),
108 }
109 }
110
111 #[must_use]
113 pub fn with_limits(mut self, limits: Limits) -> Store {
114 self.interests.set_limits(limits);
115 self
116 }
117
118 #[must_use]
119 pub const fn network(&self) -> Network {
120 self.network
121 }
122
123 pub fn register(&mut self, interest: Interest) -> Result<Handle, Refused> {
126 self.settle();
127 self.interests.register(interest)
128 }
129
130 pub fn release(&mut self, handle: Handle) {
133 drop(handle);
134 self.settle();
135 }
136
137 #[must_use]
139 pub fn wanted(&self) -> &[Subscription] {
140 self.interests.wanted()
141 }
142
143 #[must_use]
145 pub fn followed(&self) -> &[Address] {
146 self.interests.users()
147 }
148
149 pub fn changes(&mut self) -> Changes {
152 self.settle();
153 self.interests.changes()
154 }
155
156 pub fn sync(&mut self, session: &mut Session) -> Result<Synced, OtherNetwork> {
159 if session.network() != self.network {
160 return Err(OtherNetwork { store: self.network, session: session.network() });
161 }
162 let changes = self.changes();
163 let mut synced = Synced::default();
164 for subscription in &changes.unsubscribe {
165 synced.frames.extend(session.unsubscribe(subscription));
166 }
167 for subscription in changes.subscribe {
168 match session.subscribe(subscription.clone()) {
169 Ok(frame) => synced.frames.extend(frame),
170 Err(refused) => {
171 self.interests.refused(&subscription);
172 synced.refused.push((subscription, refused));
173 }
174 }
175 }
176 Ok(synced)
177 }
178
179 pub fn apply(&mut self, now: u64, frame: &Frame) -> Folded {
183 self.settle();
184 match frame {
185 Frame::AllMids(mids) => self.fold_mids(now, &mids.dex, &mids.mids),
186 Frame::Book(book) => self.fold_book(now, book.clone()),
187 Frame::Trades(prints) => self.fold_prints(now, prints.iter().rev().cloned()),
188 Frame::Candle(candle) => self.fold_candles(now, [candle.clone()]),
189 Frame::AssetCtx { coin, ctx } => self.fold_perp_ctxs(now, [(coin.clone(), ctx.clone())]),
190 Frame::SpotAssetCtx { coin, ctx } => self.fold_spot_ctxs(now, [(coin.clone(), ctx.clone())]),
191 Frame::AllDexsAssetCtxs(dexes) => dexes
192 .iter()
193 .map(|(dex, ctxs)| self.fold_dex_ctxs(now, dex, ctxs.iter().cloned()))
194 .fold(Folded::Dropped, Folded::or),
195 Frame::AssetCtxs { dex, ctxs } => self.fold_dex_ctxs(now, dex, ctxs.iter().cloned()),
196 Frame::SpotAssetCtxs(ctxs) => {
197 self.fold_spot_ctxs(now, ctxs.iter().map(|ctx| (ctx.coin.clone(), ctx.clone())))
198 }
199 Frame::ClearinghouseState(one) => {
200 let state = one.state.clone();
201 self.with_state(one.user, |account| account.set_dex(now, &one.dex, state))
202 }
203 Frame::AllDexsClearinghouseState(all) => {
204 let states = all.states.clone();
205 self.with_state(all.user, |account| account.set_dexes(now, states))
206 }
207 Frame::SpotState(spot) => {
208 let wanted = Subscription::SpotState { user: spot.user, ignore_portfolio_margin: false };
209 self.with(spot.user, &wanted, |account| {
210 let spot = convert::spot_state(spot, account.spot().map(|held| &held.value));
211 account.set_spot(now, spot)
212 })
213 }
214 Frame::WebData3(web) => match web.user_state.abstraction.as_deref() {
215 Some(mode) => self.fold_mode(now, web.user_state.user, Abstraction::parse(mode)),
216 None => Folded::Dropped,
217 },
218 Frame::OpenOrders(open) => {
219 let orders = open.orders.clone();
220 self.fold_orders(now, open.user, &open.dex, orders)
221 }
222 Frame::UserFills(fills) => {
223 let newest = fills.fills.iter().rev().cloned().collect();
224 self.fold_fills(now, fills.user, newest, fills.is_snapshot)
225 }
226 Frame::UserFundings(fundings) => {
227 let wanted = Subscription::UserFundings { user: fundings.user };
228 let newest = fundings.fundings.iter().rev().cloned().collect();
229 let whole = fundings.is_snapshot;
230 self.with(fundings.user, &wanted, |account| account.fold_fundings(now, newest, whole))
231 }
232 Frame::Ledger(ledger) => {
233 let newest = ledger.updates.iter().rev().cloned().collect();
234 self.fold_ledger(now, ledger.user, newest, ledger.is_snapshot)
235 }
236 _ => Folded::Dropped,
237 }
238 }
239
240 pub fn read(&mut self, now: u64, read: Read<'_>) -> Folded {
244 self.settle();
245 match read {
246 Read::Book(book) => self.fold_book(now, (*book).clone()),
247 Read::Trades(prints) => self.fold_prints(now, prints.iter().cloned()),
248 Read::Candles(candles) => self.fold_history(now, candles.to_vec()),
249 Read::Mids { dex, mids } => self.fold_mids(now, dex, mids),
250 Read::Perps(read) => {
251 let coins: Vec<String> = read.meta.universe.iter().map(|perp| perp.name.clone()).collect();
252 let listed = self.list("", coins, read.meta.collateral_token);
253 listed.or(self.fold_dex_ctxs(now, "", read.ctxs.iter().cloned()))
254 }
255 Read::Dex { dex, read } => {
256 let coins: Vec<String> = read.meta.universe.iter().map(|perp| perp.name.clone()).collect();
257 let listed = self.list(dex, coins, read.meta.collateral_token);
258 listed.or(self.fold_dex_ctxs(now, dex, read.ctxs.iter().cloned()))
259 }
260 Read::Dexes(dexes) => dexes
261 .list
262 .0
263 .iter()
264 .zip(&dexes.metas)
265 .map(|(dex, meta)| {
266 let name = dex.as_ref().map_or("", |dex| dex.name.as_str());
267 let coins = meta.universe.iter().map(|perp| perp.name.clone()).collect();
268 self.list(name, coins, meta.collateral_token)
269 })
270 .fold(Folded::Dropped, Folded::or),
271 Read::Spot(read) => {
272 let listed = self.fold_spot_meta(&read.meta);
273 listed.or(self.fold_spot_ctxs(now, read.ctxs.iter().map(|ctx| (ctx.coin.clone(), ctx.clone()))))
274 }
275 Read::SpotMeta(meta) => self.fold_spot_meta(meta),
276 Read::Clearinghouse { user, dex, state } => {
277 self.with_state(user, |account| account.set_dex(now, dex, state.clone()))
278 }
279 Read::SpotState { user, state } => {
280 let wanted = Subscription::SpotState { user, ignore_portfolio_margin: false };
281 self.with(user, &wanted, |account| account.set_spot(now, state.clone()))
282 }
283 Read::Mode { user, mode } => self.fold_mode(now, user, mode.clone()),
284 Read::Orders { user, dex, orders } => self.fold_orders(now, user, dex, orders.to_vec()),
285 Read::Fills { user, fills } => self.fold_fills(now, user, fills.to_vec(), true),
286 Read::MoreFills { user, fills } => self.fold_fills(now, user, fills.iter().rev().cloned().collect(), false),
287 Read::Fundings { user, fundings } => {
288 let wanted = Subscription::UserFundings { user };
289 let newest = fundings.iter().rev().map(convert::funding).collect();
290 self.with(user, &wanted, |account| account.fold_fundings(now, newest, false))
291 }
292 Read::Ledger { user, rows } => self.fold_ledger(now, user, rows.iter().rev().cloned().collect(), false),
293 }
294 }
295
296 fn settle(&mut self) {
298 if !self.interests.drain() {
299 return;
300 }
301 let interests = &self.interests;
302 self.markets.books.retain(|coin, _| interests.wants(&book(coin)));
303 self.markets.tapes.retain(|coin, _| interests.wants(&Subscription::Trades { coin: coin.clone() }));
304 self.markets.candles.retain(|(coin, interval), _| {
305 interests.wants(&Subscription::Candle { coin: coin.clone(), interval: *interval })
306 });
307 let users = interests.users();
308 self.accounts.retain(|user, _| users.contains(user));
309 for (user, account) in &mut self.accounts {
310 let user = *user;
311 let orders = |dex: &str| interests.wants(&Subscription::OpenOrders { user, dex: dex.to_owned() });
312 account.keep(&Keep {
313 state: interests.wants(&Subscription::AllDexsClearinghouseState { user }),
314 orders: &orders,
315 fills: interests.wants(&Subscription::UserFills { user, aggregate_by_time: false }),
316 fundings: interests.wants(&Subscription::UserFundings { user }),
317 ledger: interests.wants(&Subscription::UserNonFundingLedgerUpdates { user }),
318 });
319 }
320 }
321
322 fn fold_mids(&mut self, now: u64, dex: &str, mids: &Mids) -> Folded {
323 let mids: BTreeMap<String, Dec> = mids.iter().map(|(coin, mid)| (coin.to_owned(), mid)).collect();
324 match self.markets.mids.get_mut(dex) {
325 Some(held) => Folded::of(held.set(now, mids)),
326 None => {
327 self.markets.mids.insert(dex.to_owned(), Stamped::new(now, mids));
328 Folded::Changed
329 }
330 }
331 }
332
333 fn fold_book(&mut self, now: u64, sent: Book) -> Folded {
336 if sent.spread.is_some() || !self.interests.wants(&book(&sent.coin)) {
337 return Folded::Dropped;
338 }
339 match self.markets.books.get_mut(&sent.coin) {
340 Some(held) if held.time > sent.time => Folded::Dropped,
341 Some(held) => Folded::of(held.set(now, sent)),
342 None => {
343 self.markets.books.insert(sent.coin.clone(), Stamped::new(now, sent));
344 Folded::Changed
345 }
346 }
347 }
348
349 fn fold_prints(&mut self, now: u64, prints: impl IntoIterator<Item = Trade>) -> Folded {
351 let mut by_coin: BTreeMap<String, Vec<Trade>> = BTreeMap::new();
352 for print in prints {
353 by_coin.entry(print.coin.clone()).or_default().push(print);
354 }
355 let mut folded = Folded::Dropped;
356 for (coin, prints) in by_coin {
357 if !self.interests.wants(&Subscription::Trades { coin: coin.clone() }) {
358 continue;
359 }
360 let tape = self.markets.tapes.entry(coin).or_insert_with(|| Stamped::new(now, Vec::new()));
361 let changed = fold_prints(&mut tape.value, prints);
362 tape.touch(now, changed);
363 folded = folded.or(Folded::of(changed));
364 }
365 folded
366 }
367
368 fn fold_candles(&mut self, now: u64, candles: impl IntoIterator<Item = Candle>) -> Folded {
370 let mut folded = Folded::Dropped;
371 for candle in candles {
372 let key = (candle.coin.clone(), candle.interval);
373 if !self.interests.wants(&Subscription::Candle { coin: key.0.clone(), interval: key.1 }) {
374 continue;
375 }
376 let bars = self.markets.candles.entry(key).or_insert_with(|| Stamped::new(now, Vec::new()));
377 let changed = fold_bar(&mut bars.value, candle);
378 bars.touch(now, changed);
379 folded = folded.or(Folded::of(changed));
380 }
381 folded
382 }
383
384 fn fold_history(&mut self, now: u64, history: Vec<Candle>) -> Folded {
387 let mut by_market: BTreeMap<(String, Interval), Vec<Candle>> = BTreeMap::new();
388 for candle in history {
389 by_market.entry((candle.coin.clone(), candle.interval)).or_default().push(candle);
390 }
391 let mut folded = Folded::Dropped;
392 for (key, mut history) in by_market {
393 if !self.interests.wants(&Subscription::Candle { coin: key.0.clone(), interval: key.1 }) {
394 continue;
395 }
396 let bars = self.markets.candles.entry(key).or_insert_with(|| Stamped::new(now, Vec::new()));
397 history.sort_by_key(|candle| candle.open_time);
398 let live = std::mem::take(&mut bars.value);
399 let mut merged = history;
400 for bar in live.iter().cloned() {
401 let _ = fold_bar(&mut merged, bar);
402 }
403 if merged.len() > BARS {
404 let extra = merged.len() - BARS;
405 merged.drain(..extra);
406 }
407 let changed = merged != live;
408 bars.value = merged;
409 bars.touch(now, changed);
410 folded = folded.or(Folded::of(changed));
411 }
412 folded
413 }
414
415 fn fold_perp_ctxs(&mut self, now: u64, ctxs: impl IntoIterator<Item = (String, PerpCtx)>) -> Folded {
416 let mut folded = Folded::Dropped;
417 for (coin, ctx) in ctxs {
418 let changed = match self.markets.perps.get_mut(&coin) {
419 Some(held) => held.set(now, ctx),
420 None => {
421 self.markets.perps.insert(coin, Stamped::new(now, ctx));
422 true
423 }
424 };
425 folded = folded.or(Folded::of(changed));
426 }
427 folded
428 }
429
430 fn fold_spot_ctxs(&mut self, now: u64, ctxs: impl IntoIterator<Item = (String, SpotCtx)>) -> Folded {
431 let mut folded = Folded::Dropped;
432 for (coin, ctx) in ctxs {
433 let changed = match self.markets.spots.get_mut(&coin) {
434 Some(held) => held.set(now, ctx),
435 None => {
436 self.markets.spots.insert(coin, Stamped::new(now, ctx));
437 true
438 }
439 };
440 folded = folded.or(Folded::of(changed));
441 }
442 folded
443 }
444
445 fn fold_dex_ctxs(&mut self, now: u64, dex: &str, ctxs: impl IntoIterator<Item = PerpCtx>) -> Folded {
448 let Some(coins) = self.markets.universes.get(dex) else { return Folded::Dropped };
449 let placed: Vec<(String, PerpCtx)> = coins.iter().cloned().zip(ctxs).collect();
450 self.fold_perp_ctxs(now, placed)
451 }
452
453 fn list(&mut self, dex: &str, coins: Vec<String>, collateral: Option<u32>) -> Folded {
455 let token = collateral.unwrap_or(USDC_INDEX);
456 let changed = self.markets.universes.get(dex) != Some(&coins) || self.markets.collateral.of(dex) != token;
457 self.markets.universes.insert(dex.to_owned(), coins);
458 self.markets.collateral = std::mem::take(&mut self.markets.collateral).with(dex, token);
459 Folded::of(changed)
460 }
461
462 fn fold_spot_meta(&mut self, meta: &SpotMeta) -> Folded {
463 let changed = self.markets.spot_meta.as_ref() != Some(meta);
464 self.markets.spot_meta = Some(meta.clone());
465 Folded::of(changed)
466 }
467
468 fn fold_mode(&mut self, now: u64, user: Address, mode: Abstraction) -> Folded {
469 self.with(user, &Subscription::WebData3 { user }, |account| account.set_mode(now, mode))
470 }
471
472 fn fold_orders(&mut self, now: u64, user: Address, dex: &str, orders: Vec<FrontendOrder>) -> Folded {
473 let wanted = Subscription::OpenOrders { user, dex: dex.to_owned() };
474 self.with(user, &wanted, |account| account.set_orders(now, dex, orders))
475 }
476
477 fn fold_fills(&mut self, now: u64, user: Address, fills: Vec<crate::live::Fill>, whole: bool) -> Folded {
478 let wanted = Subscription::UserFills { user, aggregate_by_time: false };
479 self.with(user, &wanted, |account| account.fold_fills(now, fills, whole))
480 }
481
482 fn fold_ledger(&mut self, now: u64, user: Address, rows: Vec<LedgerUpdate>, whole: bool) -> Folded {
483 let wanted = Subscription::UserNonFundingLedgerUpdates { user };
484 self.with(user, &wanted, |account| account.fold_ledger(now, rows, whole))
485 }
486
487 fn with_state(&mut self, user: Address, change: impl FnOnce(&mut Account) -> bool) -> Folded {
489 self.with(user, &Subscription::AllDexsClearinghouseState { user }, change)
490 }
491
492 fn with(&mut self, user: Address, wanted: &Subscription, change: impl FnOnce(&mut Account) -> bool) -> Folded {
494 if !self.interests.wants(wanted) {
495 return Folded::Dropped;
496 }
497 Folded::of(change(self.accounts.entry(user).or_default()))
498 }
499
500 #[must_use]
504 pub fn book(&self, coin: &str) -> Option<&Stamped<Book>> {
505 self.markets.books.get(coin)
506 }
507
508 #[must_use]
510 pub fn best(&self, coin: &str) -> (Option<Level>, Option<Level>) {
511 self.book(coin).map_or((None, None), |book| (book.bids.first().copied(), book.asks.first().copied()))
512 }
513
514 #[must_use]
517 pub fn mid(&self, coin: &str) -> Option<Dec> {
518 if let Some(mid) = self.book(coin).and_then(|book| book.mid()) {
519 return Some(mid);
520 }
521 let dex = split_coin(coin).map_or("", |(dex, _)| dex);
522 if let Some(mid) = self.markets.mids.get(dex).and_then(|mids| mids.get(coin)) {
523 return Some(*mid);
524 }
525 self.context(coin).and_then(|ctx| ctx.mid())
526 }
527
528 #[must_use]
530 pub fn mids(&self, dex: &str) -> Option<&Stamped<BTreeMap<String, Dec>>> {
531 self.markets.mids.get(dex)
532 }
533
534 #[must_use]
536 pub fn context(&self, coin: &str) -> Option<Stamped<Context<'_>>> {
537 if let Some(ctx) = self.markets.perps.get(coin) {
538 return Some(Stamped { value: Context::Perp(&ctx.value), heard: ctx.heard, changed: ctx.changed });
539 }
540 let ctx = self.markets.spots.get(coin)?;
541 Some(Stamped { value: Context::Spot(&ctx.value), heard: ctx.heard, changed: ctx.changed })
542 }
543
544 #[must_use]
546 pub fn trades(&self, coin: &str) -> Option<&Stamped<Vec<Trade>>> {
547 self.markets.tapes.get(coin)
548 }
549
550 #[must_use]
552 pub fn candles(&self, coin: &str, interval: Interval) -> Option<&Stamped<Vec<Candle>>> {
553 self.markets.candles.get(&(coin.to_owned(), interval))
554 }
555
556 #[must_use]
558 pub const fn collateral(&self) -> &Collateral {
559 &self.markets.collateral
560 }
561
562 #[must_use]
563 pub fn account(&self, user: Address) -> Option<&Account> {
564 self.accounts.get(&user)
565 }
566
567 #[must_use]
569 pub fn positions(&self, user: Address) -> Vec<&Position> {
570 self.account(user).map_or_else(Vec::new, |account| account.positions().collect())
571 }
572
573 #[must_use]
575 pub fn orders(&self, user: Address) -> Vec<&FrontendOrder> {
576 self.account(user).map_or_else(Vec::new, |account| account.all_orders().collect())
577 }
578
579 #[must_use]
583 pub fn surface<'a>(&self, coin: &'a str) -> Option<Surface<'a>> {
584 if coin.starts_with(['#', '+']) {
585 return Some(Surface::Outcomes);
586 }
587 if coin.starts_with('@') || coin.contains('/') {
588 let pair = self.markets.spot_meta.as_ref()?.pair_named(coin)?;
589 return Some(Surface::Spot { quote: pair.quote() });
590 }
591 Some(Surface::Perps { dex: split_coin(coin).map_or("", |(dex, _)| dex) })
592 }
593
594 #[must_use]
597 pub fn usable(&self, user: Address, coin: &str) -> Option<Usable> {
598 let surface = self.surface(coin)?;
599 let account = self.account(user).filter(|account| account.spot.is_some() || !account.dexes.is_empty())?;
600 Some(account.standing().usable(surface, &self.markets.collateral, |name| self.price(name)))
601 }
602
603 #[must_use]
607 pub fn price(&self, name: &str) -> Option<Dec> {
608 if name == "USDC" {
609 return Some(Dec::ONE);
610 }
611 let mids = self.markets.mids.get("")?;
612 let mid = |coin: &str| mids.get(coin).copied().filter(|mid| mid.is_positive());
613 if let Some(side) = name.strip_prefix('+') {
614 return mid(&format!("#{side}"));
615 }
616 let meta = self.markets.spot_meta.as_ref()?;
617 mid(&meta.pair_of(meta.token_named(name)?.index, USDC_INDEX)?.name)
618 }
619
620 #[must_use]
623 pub fn prices(&self) -> Prices {
624 let Some(mids) = self.markets.mids.get("") else { return Prices::new() };
625 let mut pairs: BTreeMap<String, String> = BTreeMap::new();
626 if let Some(meta) = self.markets.spot_meta.as_ref() {
627 let names: BTreeMap<u32, &str> =
628 meta.tokens.iter().map(|token| (token.index, token.name.as_str())).collect();
629 for pair in meta.universe.iter().filter(|pair| pair.quote() == USDC_INDEX) {
630 if let Some(name) = names.get(&pair.base()) {
631 pairs.entry((*name).to_owned()).or_insert_with(|| pair.name.clone());
632 }
633 }
634 }
635 Prices::of(&pairs, mids)
636 }
637
638 #[must_use]
641 pub fn worth(&self, user: Address, beside: &Beside) -> Option<Worth> {
642 self.account(user)?.standing().worth(&self.prices(), beside)
643 }
644
645 #[must_use]
648 pub fn combined(&self, accounts: &[(Address, Beside)]) -> Option<Worth> {
649 let held: Vec<(Address, &Standing, &Beside)> = accounts
650 .iter()
651 .filter_map(|(user, beside)| Some((*user, self.account(*user)?.standing(), beside)))
652 .collect();
653 combined(&held, &self.prices())
654 }
655}
656
657impl Folded {
658 const fn of(changed: bool) -> Folded {
659 match changed {
660 true => Folded::Changed,
661 false => Folded::Same,
662 }
663 }
664
665 const fn or(self, other: Folded) -> Folded {
667 match (self, other) {
668 (Folded::Changed, _) | (_, Folded::Changed) => Folded::Changed,
669 (Folded::Same, _) | (_, Folded::Same) => Folded::Same,
670 _ => Folded::Dropped,
671 }
672 }
673}
674
675fn book(coin: &str) -> Subscription {
677 Subscription::L2Book { coin: coin.to_owned(), n_sig_figs: None, mantissa: None, fast: false }
678}