Skip to main content

rustrade_execution/
cash_flow.rs

1//! Cash that moves into or out of an account other than by a trade: perpetual funding, margin
2//! interest, stock borrow fees and rebates.
3//!
4//! An account stream reports each such movement the venue posts as
5//! [`AccountEventKind::CashFlow`](crate::AccountEventKind::CashFlow), carrying a [`CashFlow`]. The
6//! library delivers the venue's own figures; rates, day counts and what to do with the flows stay
7//! the consumer's.
8
9use chrono::{DateTime, Utc};
10use derive_more::{Constructor, From};
11use rust_decimal::Decimal;
12use serde::{Deserialize, Serialize};
13use smol_str::SmolStr;
14use std::fmt::{Display, Formatter};
15
16/// One movement of cash into or out of an account that is not a trade, as the venue posted it.
17///
18/// Sent as [`AccountEventKind::CashFlow`](crate::AccountEventKind::CashFlow). A flow is a delta:
19/// apply each one once. The engine adds a flow attributed to an instrument to the `carry` of that
20/// instrument's open position (see `Position::carry` in `rustrade`), and applies none to
21/// balances. A venue's balances already include the flows it has posted, so adding a flow to a
22/// balance read after it counts the flow twice.
23///
24/// # Delivery
25///
26/// Each producer says how it avoids sending one flow twice, and what identifies a flow when the
27/// venue gives it no [`id`](Self::id). Known producers, as of writing:
28/// - Hyperliquid perpetuals: funding, as [`CashFlowKind::Funding`]. See the client's rustdoc.
29/// - Alpaca: margin interest, fees and pass-through charges and rebates, read on request by
30///   `AlpacaClient::fetch_cash_flows` rather than streamed, with Alpaca's activity id. The caller
31///   drops what it has already read.
32/// - Binance margin: interest, as [`CashFlowKind::MarginInterest`], read on request by
33///   `BinanceMargin::fetch_margin_interest` rather than streamed, with Binance's transaction id.
34///   The caller drops what it has already read.
35/// - The simulated venue: whatever its configured
36///   [`HoldingCostModel`](crate::holding_cost::HoldingCostModel)s charge, each flow once, with an
37///   id from its own sequence.
38#[non_exhaustive]
39#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, Constructor)]
40pub struct CashFlow<AssetKey, InstrumentKey> {
41    /// What the flow is for.
42    pub kind: CashFlowKind,
43    /// The asset the cash moved in.
44    pub asset: AssetKey,
45    /// How much moved, signed: positive when the account received it, negative when it paid.
46    pub amount: Decimal,
47    /// The instrument whose position caused the flow, when the venue attributes it to one, as it
48    /// does funding. `None` for a flow on the account as a whole.
49    pub instrument: Option<InstrumentKey>,
50    /// When the venue posted the flow.
51    pub time_exchange: DateTime<Utc>,
52    /// The venue's identifier for the flow, when it gives one. See [`CashFlowId`].
53    pub id: Option<CashFlowId>,
54}
55
56/// What a [`CashFlow`] is for.
57///
58/// Each variant carries the venue's own figures where it reports them, and `None` where it does
59/// not.
60#[non_exhaustive]
61#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
62pub enum CashFlowKind {
63    /// A perpetual's funding payment, paid or received on a position held at a funding time.
64    Funding {
65        /// The funding rate the payment was charged at, per funding interval, in the venue's
66        /// sign convention.
67        rate: Option<Decimal>,
68        /// The signed position the payment was charged on: positive long, negative short.
69        position_quantity: Option<Decimal>,
70    },
71    /// A fee for borrowing a security to sell it short.
72    BorrowFee {
73        /// The fee rate charged, as the venue quotes it: usually annualised.
74        rate: Option<Decimal>,
75        /// The quantity borrowed that the fee was charged on.
76        quantity: Option<Decimal>,
77    },
78    /// Interest on borrowed cash or assets.
79    MarginInterest {
80        /// The interest rate charged, per the venue's own period.
81        rate: Option<Decimal>,
82        /// The amount borrowed that the interest was charged on.
83        principal: Option<Decimal>,
84    },
85    /// The financing of a leveraged position held past a venue's daily cutoff, such as a CFD's
86    /// overnight financing.
87    ///
88    /// Charged on the position rather than on a loan: a long and a short in one instrument are
89    /// charged at different rates, and a short may receive rather than pay. Interest on a loan of
90    /// cash or assets is [`MarginInterest`](Self::MarginInterest).
91    Financing {
92        /// The financing rate charged, as the venue quotes it: usually annualised.
93        rate: Option<Decimal>,
94        /// The notional value of the position the financing was charged on.
95        notional: Option<Decimal>,
96    },
97    /// A rebate paid to the account, such as on cash collateral for a short sale.
98    Rebate,
99    /// A flow of a kind no other variant names, with the venue's own name for it.
100    Other(String),
101}
102
103/// A venue's own identifier for one [`CashFlow`], carried through opaquely.
104///
105/// Like [`TradeId`](crate::trade::TradeId), its uniqueness is the venue's to define and may be
106/// scoped to an asset or instrument rather than global. A consumer that needs a key should
107/// qualify it by exchange and asset or instrument. `Ord` is byte order, not time order.
108#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, From)]
109pub struct CashFlowId(pub SmolStr);
110
111impl Display for CashFlowId {
112    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
113        Display::fmt(&self.0, f)
114    }
115}
116
117impl CashFlowId {
118    pub fn new<S: AsRef<str>>(id: S) -> Self {
119        Self(SmolStr::new(id))
120    }
121}
122
123/// What one read of a venue's cash flows over a span returned, for a venue that serves them in
124/// bounded calls.
125///
126/// `resume` says whether the read stopped before the span's end:
127/// - `None`: every flow in the span the venue still holds, and the client reports, is in `flows`.
128///   Each client documents the records it leaves out.
129/// - `Some(t)`: the read stopped at its bound, and flows from `t` on may be missing from `flows`.
130///   Read on from `start = t` with the same end. That read can return again flows this one
131///   returned at or after `t`, so match them by [`CashFlowId`].
132#[non_exhaustive]
133#[must_use = "`resume` says whether the span was read to its end"]
134#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Constructor)]
135pub struct CashFlowsRead<AssetKey, InstrumentKey> {
136    /// The flows read, in the span.
137    pub flows: Vec<CashFlow<AssetKey, InstrumentKey>>,
138    /// Where to read on from, or `None` when the read reached the span's end.
139    pub resume: Option<DateTime<Utc>>,
140}
141
142impl<AssetKey, InstrumentKey> CashFlowsRead<AssetKey, InstrumentKey> {
143    /// A read that reached the span's end.
144    pub fn complete(flows: Vec<CashFlow<AssetKey, InstrumentKey>>) -> Self {
145        Self {
146            flows,
147            resume: None,
148        }
149    }
150}