1use std::fmt::{Debug, Display};
19
20use nautilus_core::correctness::{FAILED, check_predicate_true};
21use serde::{Deserialize, Serialize};
22
23use crate::{
24 identifiers::InstrumentId,
25 types::{Currency, Money},
26};
27
28#[derive(Copy, Clone, Serialize, Deserialize)]
30#[cfg_attr(
31 feature = "python",
32 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model", frozen, eq)
33)]
34pub struct AccountBalance {
35 pub currency: Currency,
37 pub total: Money,
39 pub locked: Money,
41 pub free: Money,
43}
44
45impl AccountBalance {
46 pub fn new_checked(total: Money, locked: Money, free: Money) -> anyhow::Result<Self> {
56 check_predicate_true(
57 total == locked + free,
58 &format!("`total` ({total}) - `locked` ({locked}) != `free` ({free})"),
59 )?;
60 Ok(Self {
61 currency: total.currency,
62 total,
63 locked,
64 free,
65 })
66 }
67
68 pub fn new(total: Money, locked: Money, free: Money) -> Self {
74 Self::new_checked(total, locked, free).expect(FAILED)
75 }
76}
77
78impl PartialEq for AccountBalance {
79 fn eq(&self, other: &Self) -> bool {
80 self.total == other.total && self.locked == other.locked && self.free == other.free
81 }
82}
83
84impl Debug for AccountBalance {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 write!(
87 f,
88 "{}(total={}, locked={}, free={})",
89 stringify!(AccountBalance),
90 self.total,
91 self.locked,
92 self.free,
93 )
94 }
95}
96
97impl Display for AccountBalance {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 write!(f, "{self:?}")
100 }
101}
102
103#[derive(Copy, Clone, Serialize, Deserialize)]
104#[cfg_attr(
105 feature = "python",
106 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model", frozen, eq)
107)]
108pub struct MarginBalance {
109 pub initial: Money,
110 pub maintenance: Money,
111 pub currency: Currency,
112 pub instrument_id: InstrumentId,
113}
114
115impl MarginBalance {
116 pub fn new(initial: Money, maintenance: Money, instrument_id: InstrumentId) -> Self {
118 Self {
119 initial,
120 maintenance,
121 currency: initial.currency,
122 instrument_id,
123 }
124 }
125}
126
127impl PartialEq for MarginBalance {
128 fn eq(&self, other: &Self) -> bool {
129 self.initial == other.initial
130 && self.maintenance == other.maintenance
131 && self.instrument_id == other.instrument_id
132 }
133}
134
135impl Debug for MarginBalance {
136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137 write!(
138 f,
139 "{}(initial={}, maintenance={}, instrument_id={})",
140 stringify!(MarginBalance),
141 self.initial,
142 self.maintenance,
143 self.instrument_id,
144 )
145 }
146}
147
148impl Display for MarginBalance {
149 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150 write!(f, "{self:?}")
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use rstest::rstest;
157
158 use crate::types::{
159 AccountBalance, MarginBalance,
160 stubs::{stub_account_balance, stub_margin_balance},
161 };
162
163 #[rstest]
164 fn test_account_balance_equality() {
165 let account_balance_1 = stub_account_balance();
166 let account_balance_2 = stub_account_balance();
167 assert_eq!(account_balance_1, account_balance_2);
168 }
169
170 #[rstest]
171 fn test_account_balance_debug(stub_account_balance: AccountBalance) {
172 let result = format!("{stub_account_balance:?}");
173 let expected =
174 "AccountBalance(total=1525000.00 USD, locked=25000.00 USD, free=1500000.00 USD)";
175 assert_eq!(result, expected);
176 }
177
178 #[rstest]
179 fn test_account_balance_display(stub_account_balance: AccountBalance) {
180 let result = format!("{stub_account_balance}");
181 let expected =
182 "AccountBalance(total=1525000.00 USD, locked=25000.00 USD, free=1500000.00 USD)";
183 assert_eq!(result, expected);
184 }
185
186 #[rstest]
187 fn test_margin_balance_equality() {
188 let margin_balance_1 = stub_margin_balance();
189 let margin_balance_2 = stub_margin_balance();
190 assert_eq!(margin_balance_1, margin_balance_2);
191 }
192
193 #[rstest]
194 fn test_margin_balance_debug(stub_margin_balance: MarginBalance) {
195 let display = format!("{stub_margin_balance:?}");
196 assert_eq!(
197 "MarginBalance(initial=5000.00 USD, maintenance=20000.00 USD, instrument_id=BTCUSDT.COINBASE)",
198 display
199 );
200 }
201
202 #[rstest]
203 fn test_margin_balance_display(stub_margin_balance: MarginBalance) {
204 let display = format!("{stub_margin_balance}");
205 assert_eq!(
206 "MarginBalance(initial=5000.00 USD, maintenance=20000.00 USD, instrument_id=BTCUSDT.COINBASE)",
207 display
208 );
209 }
210}