use std::{
cmp::Reverse,
collections::{BTreeMap, HashSet},
hash::Hash,
};
use crate::account::history::{Fill, LedgerUpdate};
use crate::account::{Abstraction, ClearinghouseState, DexState, FrontendOrder, Position, SpotState, Standing};
use crate::live::Funding;
use crate::live::store::stamp::{Stamped, put};
pub const KEEP: usize = 4_000;
#[derive(Debug, Clone, Default)]
pub struct Account {
pub(crate) dexes: BTreeMap<String, Stamped<ClearinghouseState>>,
pub(crate) spot: Option<Stamped<SpotState>>,
pub(crate) mode: Option<Stamped<Abstraction>>,
pub(crate) orders: BTreeMap<String, Stamped<Vec<FrontendOrder>>>,
pub(crate) fills: Option<Stamped<Vec<Fill>>>,
pub(crate) fundings: Option<Stamped<Vec<Funding>>>,
pub(crate) ledger: Option<Stamped<Vec<LedgerUpdate>>>,
standing: Standing,
}
impl Account {
#[must_use]
pub const fn standing(&self) -> &Standing {
&self.standing
}
#[must_use]
pub const fn mode(&self) -> Option<&Stamped<Abstraction>> {
self.mode.as_ref()
}
#[must_use]
pub const fn spot(&self) -> Option<&Stamped<SpotState>> {
self.spot.as_ref()
}
#[must_use]
pub fn dex(&self, dex: &str) -> Option<&Stamped<ClearinghouseState>> {
self.dexes.get(dex)
}
pub fn dexes(&self) -> impl Iterator<Item = (&str, &Stamped<ClearinghouseState>)> {
self.dexes.iter().map(|(dex, state)| (dex.as_str(), state))
}
pub fn positions(&self) -> impl Iterator<Item = &Position> {
self.dexes.values().flat_map(|state| state.positions())
}
#[must_use]
pub fn orders(&self, dex: &str) -> Option<&Stamped<Vec<FrontendOrder>>> {
self.orders.get(dex)
}
pub fn all_orders(&self) -> impl Iterator<Item = &FrontendOrder> {
self.orders.values().flat_map(|orders| orders.iter())
}
#[must_use]
pub const fn fills(&self) -> Option<&Stamped<Vec<Fill>>> {
self.fills.as_ref()
}
#[must_use]
pub const fn fundings(&self) -> Option<&Stamped<Vec<Funding>>> {
self.fundings.as_ref()
}
#[must_use]
pub const fn ledger(&self) -> Option<&Stamped<Vec<LedgerUpdate>>> {
self.ledger.as_ref()
}
#[must_use]
pub fn heard(&self) -> Option<u64> {
let rows = [
self.fills.as_ref().map(|fills| fills.heard),
self.fundings.as_ref().map(|fundings| fundings.heard),
self.ledger.as_ref().map(|ledger| ledger.heard),
];
let orders = self.orders.values().map(|orders| Some(orders.heard));
rows.into_iter().chain(orders).chain([self.state_heard()]).flatten().max()
}
fn state_heard(&self) -> Option<u64> {
let dexes = self.dexes.values().map(|state| state.heard);
let rest = [self.spot.as_ref().map(|spot| spot.heard), self.mode.as_ref().map(|mode| mode.heard)];
dexes.chain(rest.into_iter().flatten()).max()
}
pub(crate) fn set_dex(&mut self, now: u64, dex: &str, state: ClearinghouseState) -> bool {
let changed = match self.dexes.get_mut(dex) {
Some(held) => held.set(now, state),
None => {
self.dexes.insert(dex.to_owned(), Stamped::new(now, state));
true
}
};
self.state_told(changed)
}
pub(crate) fn set_dexes(&mut self, now: u64, states: Vec<(String, ClearinghouseState)>) -> bool {
let before = self.dexes.len();
self.dexes.retain(|dex, _| states.iter().any(|(listed, _)| listed == dex));
let mut changed = self.dexes.len() != before;
for (dex, state) in states {
changed |= match self.dexes.get_mut(&dex) {
Some(held) => held.set(now, state),
None => {
self.dexes.insert(dex, Stamped::new(now, state));
true
}
};
}
self.state_told(changed)
}
pub(crate) fn set_spot(&mut self, now: u64, spot: SpotState) -> bool {
let changed = put(&mut self.spot, now, spot);
self.state_told(changed)
}
pub(crate) fn set_mode(&mut self, now: u64, mode: Abstraction) -> bool {
let changed = put(&mut self.mode, now, mode);
self.state_told(changed)
}
pub(crate) fn set_orders(&mut self, now: u64, dex: &str, orders: Vec<FrontendOrder>) -> bool {
match self.orders.get_mut(dex) {
Some(held) => held.set(now, orders),
None => {
self.orders.insert(dex.to_owned(), Stamped::new(now, orders));
true
}
}
}
pub(crate) fn fold_fills(&mut self, now: u64, fills: Vec<Fill>, whole: bool) -> bool {
fold_rows(&mut self.fills, now, fills, whole, |fill| (fill.tid, fill.oid), |fill| fill.time)
}
pub(crate) fn fold_fundings(&mut self, now: u64, fundings: Vec<Funding>, whole: bool) -> bool {
fold_rows(&mut self.fundings, now, fundings, whole, |row| (row.time, row.coin.clone()), |row| row.time)
}
pub(crate) fn fold_ledger(&mut self, now: u64, rows: Vec<LedgerUpdate>, whole: bool) -> bool {
fold_rows(
&mut self.ledger,
now,
rows,
whole,
|row| (row.time, row.hash.clone(), format!("{:?}", row.delta)),
|row| row.time,
)
}
pub(crate) fn keep(&mut self, keep: &Keep<'_>) {
if !keep.state && (self.spot.is_some() || self.mode.is_some() || !self.dexes.is_empty()) {
self.dexes.clear();
self.spot = None;
self.mode = None;
self.restand();
}
self.orders.retain(|dex, _| (keep.orders)(dex));
if !keep.fills {
self.fills = None;
}
if !keep.fundings {
self.fundings = None;
}
if !keep.ledger {
self.ledger = None;
}
}
fn state_told(&mut self, changed: bool) -> bool {
match changed {
true => self.restand(),
false => self.standing.heard = self.state_heard(),
}
changed
}
fn restand(&mut self) {
self.standing = Standing {
mode: self.mode.as_ref().map(|mode| mode.value.clone()),
dexes: self
.dexes
.iter()
.map(|(dex, state)| DexState { dex: dex.clone(), state: state.value.clone() })
.collect(),
spot: self.spot.as_ref().map(|spot| spot.value.clone()),
heard: self.state_heard(),
};
}
}
fn fold_rows<T: PartialEq, K: Hash + Eq>(
slot: &mut Option<Stamped<Vec<T>>>,
now: u64,
rows: Vec<T>,
whole: bool,
key: impl Fn(&T) -> K,
time: impl Fn(&T) -> u64,
) -> bool {
let mut seen: HashSet<K> = HashSet::new();
let held = match (whole, slot.as_mut()) {
(false, Some(held)) => {
seen.extend(held.iter().map(&key));
Some(held)
}
_ => None,
};
let mut fresh: Vec<T> = rows.into_iter().filter(|row| seen.insert(key(row))).collect();
let Some(held) = held else {
fresh.sort_by_key(|row| Reverse(time(row)));
fresh.truncate(KEEP);
return put(slot, now, fresh);
};
if fresh.is_empty() {
held.touch(now, false);
return false;
}
fresh.append(&mut held.value);
fresh.sort_by_key(|row| Reverse(time(row)));
fresh.truncate(KEEP);
held.value = fresh;
held.touch(now, true);
true
}
#[derive(Clone, Copy)]
pub(crate) struct Keep<'a> {
pub(crate) state: bool,
pub(crate) orders: &'a dyn Fn(&str) -> bool,
pub(crate) fills: bool,
pub(crate) fundings: bool,
pub(crate) ledger: bool,
}
#[cfg(test)]
#[path = "tests/account.rs"]
mod tests;