Skip to main content

df_hyperliquid_core/live/store/
account.rs

1//! What the store holds of one account, read at its own address (a subaccount is an account of
2//! its own, with its own mode). A snapshot (`isSnapshot`, or a read of the latest) replaces a
3//! history; anything else is added to it, and a row already held is not added twice.
4
5use std::{
6    cmp::Reverse,
7    collections::{BTreeMap, HashSet},
8    hash::Hash,
9};
10
11use crate::account::history::{Fill, LedgerUpdate};
12use crate::account::{Abstraction, ClearinghouseState, DexState, FrontendOrder, Position, SpotState, Standing};
13use crate::live::Funding;
14use crate::live::store::stamp::{Stamped, put};
15
16/// Rows of each history kept, newest first; the whole of it is a read away.
17pub const KEEP: usize = 4_000;
18
19/// One account as the venue last told it.
20#[derive(Debug, Clone, Default)]
21pub struct Account {
22    pub(crate) dexes: BTreeMap<String, Stamped<ClearinghouseState>>,
23    pub(crate) spot: Option<Stamped<SpotState>>,
24    pub(crate) mode: Option<Stamped<Abstraction>>,
25    pub(crate) orders: BTreeMap<String, Stamped<Vec<FrontendOrder>>>,
26    pub(crate) fills: Option<Stamped<Vec<Fill>>>,
27    pub(crate) fundings: Option<Stamped<Vec<Funding>>>,
28    pub(crate) ledger: Option<Stamped<Vec<LedgerUpdate>>>,
29    /// The dexes, the spot balances and the mode as one [`Standing`], kept in step with them.
30    standing: Standing,
31}
32
33impl Account {
34    /// The mode, the dexes and spot together, which usable balances and worth are read from.
35    #[must_use]
36    pub const fn standing(&self) -> &Standing {
37        &self.standing
38    }
39
40    /// `None` until the socket or a read says.
41    #[must_use]
42    pub const fn mode(&self) -> Option<&Stamped<Abstraction>> {
43        self.mode.as_ref()
44    }
45
46    #[must_use]
47    pub const fn spot(&self) -> Option<&Stamped<SpotState>> {
48        self.spot.as_ref()
49    }
50
51    /// One dex's clearinghouse, `""` the first.
52    #[must_use]
53    pub fn dex(&self, dex: &str) -> Option<&Stamped<ClearinghouseState>> {
54        self.dexes.get(dex)
55    }
56
57    /// Every dex told of, the first first.
58    pub fn dexes(&self) -> impl Iterator<Item = (&str, &Stamped<ClearinghouseState>)> {
59        self.dexes.iter().map(|(dex, state)| (dex.as_str(), state))
60    }
61
62    /// Every open position, dex by dex.
63    pub fn positions(&self) -> impl Iterator<Item = &Position> {
64        self.dexes.values().flat_map(|state| state.positions())
65    }
66
67    /// The resting orders on one dex.
68    #[must_use]
69    pub fn orders(&self, dex: &str) -> Option<&Stamped<Vec<FrontendOrder>>> {
70        self.orders.get(dex)
71    }
72
73    /// Every resting order, dex by dex, each dex's in the venue's order.
74    pub fn all_orders(&self) -> impl Iterator<Item = &FrontendOrder> {
75        self.orders.values().flat_map(|orders| orders.iter())
76    }
77
78    /// Newest first.
79    #[must_use]
80    pub const fn fills(&self) -> Option<&Stamped<Vec<Fill>>> {
81        self.fills.as_ref()
82    }
83
84    /// Newest first.
85    #[must_use]
86    pub const fn fundings(&self) -> Option<&Stamped<Vec<Funding>>> {
87        self.fundings.as_ref()
88    }
89
90    /// Newest first.
91    #[must_use]
92    pub const fn ledger(&self) -> Option<&Stamped<Vec<LedgerUpdate>>> {
93        self.ledger.as_ref()
94    }
95
96    /// When anything about the account was last told.
97    #[must_use]
98    pub fn heard(&self) -> Option<u64> {
99        let rows = [
100            self.fills.as_ref().map(|fills| fills.heard),
101            self.fundings.as_ref().map(|fundings| fundings.heard),
102            self.ledger.as_ref().map(|ledger| ledger.heard),
103        ];
104        let orders = self.orders.values().map(|orders| Some(orders.heard));
105        rows.into_iter().chain(orders).chain([self.state_heard()]).flatten().max()
106    }
107
108    /// When the dexes, the spot balances or the mode were last told.
109    fn state_heard(&self) -> Option<u64> {
110        let dexes = self.dexes.values().map(|state| state.heard);
111        let rest = [self.spot.as_ref().map(|spot| spot.heard), self.mode.as_ref().map(|mode| mode.heard)];
112        dexes.chain(rest.into_iter().flatten()).max()
113    }
114
115    /// One dex's clearinghouse, told at `now`.
116    pub(crate) fn set_dex(&mut self, now: u64, dex: &str, state: ClearinghouseState) -> bool {
117        let changed = match self.dexes.get_mut(dex) {
118            Some(held) => held.set(now, state),
119            None => {
120                self.dexes.insert(dex.to_owned(), Stamped::new(now, state));
121                true
122            }
123        };
124        self.state_told(changed)
125    }
126
127    /// Every dex's clearinghouse, told at `now`: a dex not listed is gone.
128    pub(crate) fn set_dexes(&mut self, now: u64, states: Vec<(String, ClearinghouseState)>) -> bool {
129        let before = self.dexes.len();
130        self.dexes.retain(|dex, _| states.iter().any(|(listed, _)| listed == dex));
131        let mut changed = self.dexes.len() != before;
132        for (dex, state) in states {
133            changed |= match self.dexes.get_mut(&dex) {
134                Some(held) => held.set(now, state),
135                None => {
136                    self.dexes.insert(dex, Stamped::new(now, state));
137                    true
138                }
139            };
140        }
141        self.state_told(changed)
142    }
143
144    pub(crate) fn set_spot(&mut self, now: u64, spot: SpotState) -> bool {
145        let changed = put(&mut self.spot, now, spot);
146        self.state_told(changed)
147    }
148
149    pub(crate) fn set_mode(&mut self, now: u64, mode: Abstraction) -> bool {
150        let changed = put(&mut self.mode, now, mode);
151        self.state_told(changed)
152    }
153
154    pub(crate) fn set_orders(&mut self, now: u64, dex: &str, orders: Vec<FrontendOrder>) -> bool {
155        match self.orders.get_mut(dex) {
156            Some(held) => held.set(now, orders),
157            None => {
158                self.orders.insert(dex.to_owned(), Stamped::new(now, orders));
159                true
160            }
161        }
162    }
163
164    /// Fills told at `now`, newest first; `whole` replaces what is held. A fill is the same fill
165    /// by its `tid` and `oid`: the two sides of one trade share a `tid` (and can both be this
166    /// account's), every fill of one transaction shares a hash, and a TWAP slice's hash is all
167    /// zeros.
168    pub(crate) fn fold_fills(&mut self, now: u64, fills: Vec<Fill>, whole: bool) -> bool {
169        fold_rows(&mut self.fills, now, fills, whole, |fill| (fill.tid, fill.oid), |fill| fill.time)
170    }
171
172    /// Fundings told at `now`, newest first: one a position an hour, so the same by time and
173    /// coin.
174    pub(crate) fn fold_fundings(&mut self, now: u64, fundings: Vec<Funding>, whole: bool) -> bool {
175        fold_rows(&mut self.fundings, now, fundings, whole, |row| (row.time, row.coin.clone()), |row| row.time)
176    }
177
178    /// Ledger rows told at `now`, newest first: the same only when every field and digit is (a
179    /// delta's debug form keeps each figure's digits, where its equality would not).
180    pub(crate) fn fold_ledger(&mut self, now: u64, rows: Vec<LedgerUpdate>, whole: bool) -> bool {
181        fold_rows(
182            &mut self.ledger,
183            now,
184            rows,
185            whole,
186            |row| (row.time, row.hash.clone(), format!("{:?}", row.delta)),
187            |row| row.time,
188        )
189    }
190
191    /// Lets go of the pieces nothing wants any more: the state (dexes, spot and mode together),
192    /// a dex's orders, the fills, the fundings, the ledger.
193    pub(crate) fn keep(&mut self, keep: &Keep<'_>) {
194        if !keep.state && (self.spot.is_some() || self.mode.is_some() || !self.dexes.is_empty()) {
195            self.dexes.clear();
196            self.spot = None;
197            self.mode = None;
198            self.restand();
199        }
200        self.orders.retain(|dex, _| (keep.orders)(dex));
201        if !keep.fills {
202            self.fills = None;
203        }
204        if !keep.fundings {
205            self.fundings = None;
206        }
207        if !keep.ledger {
208            self.ledger = None;
209        }
210    }
211
212    /// Keeps the standing in step: rebuilt when the state changed, its time moved when it was
213    /// only told again.
214    fn state_told(&mut self, changed: bool) -> bool {
215        match changed {
216            true => self.restand(),
217            false => self.standing.heard = self.state_heard(),
218        }
219        changed
220    }
221
222    fn restand(&mut self) {
223        self.standing = Standing {
224            mode: self.mode.as_ref().map(|mode| mode.value.clone()),
225            dexes: self
226                .dexes
227                .iter()
228                .map(|(dex, state)| DexState { dex: dex.clone(), state: state.value.clone() })
229                .collect(),
230            spot: self.spot.as_ref().map(|spot| spot.value.clone()),
231            heard: self.state_heard(),
232        };
233    }
234}
235
236/// Rows told at `now`, newest first, into a history kept newest first and at most [`KEEP`]
237/// long. `whole` replaces what is held; otherwise a row whose `key` is held already is not added
238/// again, and among rows of one time the new go before the held. Says whether anything changed.
239fn fold_rows<T: PartialEq, K: Hash + Eq>(
240    slot: &mut Option<Stamped<Vec<T>>>,
241    now: u64,
242    rows: Vec<T>,
243    whole: bool,
244    key: impl Fn(&T) -> K,
245    time: impl Fn(&T) -> u64,
246) -> bool {
247    let mut seen: HashSet<K> = HashSet::new();
248    let held = match (whole, slot.as_mut()) {
249        (false, Some(held)) => {
250            seen.extend(held.iter().map(&key));
251            Some(held)
252        }
253        _ => None,
254    };
255    let mut fresh: Vec<T> = rows.into_iter().filter(|row| seen.insert(key(row))).collect();
256    let Some(held) = held else {
257        fresh.sort_by_key(|row| Reverse(time(row)));
258        fresh.truncate(KEEP);
259        return put(slot, now, fresh);
260    };
261    if fresh.is_empty() {
262        held.touch(now, false);
263        return false;
264    }
265    fresh.append(&mut held.value);
266    fresh.sort_by_key(|row| Reverse(time(row)));
267    fresh.truncate(KEEP);
268    held.value = fresh;
269    held.touch(now, true);
270    true
271}
272
273/// Which pieces of an account something still wants.
274#[derive(Clone, Copy)]
275pub(crate) struct Keep<'a> {
276    pub(crate) state: bool,
277    pub(crate) orders: &'a dyn Fn(&str) -> bool,
278    pub(crate) fills: bool,
279    pub(crate) fundings: bool,
280    pub(crate) ledger: bool,
281}
282
283#[cfg(test)]
284#[path = "tests/account.rs"]
285mod tests;