1use crate::currency::Currency;
2use crate::node_utils::NodeWrapper;
3use crate::statement_section::StatementSection;
4use crate::time_utils;
5use anyhow::Result;
6
7#[derive(Debug, PartialEq)]
8pub struct CashReport {
9 pub account_id: String,
10 pub currency: Currency,
11 pub start_timestamp_ms: i64,
12 pub end_timestamp_ms: i64,
13
14 pub starting_cash: f64,
15 pub ending_cash: f64,
16 pub ending_settled_cash: f64,
17
18 pub net_trade_purchases: f64,
19 pub net_trade_sales: f64,
20
21 pub commissions: f64,
22 pub commissions_mtd: Option<f64>,
23 pub commissions_ytd: Option<f64>,
24
25 pub other_fees: f64,
26 pub other_fees_mtd: Option<f64>,
27 pub other_fees_ytd: Option<f64>,
28
29 pub dividends: f64,
30 pub dividends_mtd: Option<f64>,
31 pub dividends_ytd: Option<f64>,
32
33 pub interest: f64,
34 pub interest_mtd: Option<f64>,
35 pub interest_ytd: Option<f64>,
36
37 pub deposits: f64,
38 pub deposits_mtd: Option<f64>,
39 pub deposits_ytd: Option<f64>,
40
41 pub withdrawals: f64,
42 pub withdrawals_mtd: Option<f64>,
43 pub withdrawals_ytd: Option<f64>,
44}
45
46impl StatementSection for CashReport {
47 fn from_node(node: &NodeWrapper) -> Result<CashReport> {
48 let start_date_eod_ms_plus_one = time_utils::trading_eod_after_hours_timestamp_ms(
49 node.node.attribute("fromDate").unwrap(),
50 )?;
51 let start_timestamp_ms = start_date_eod_ms_plus_one - (60 * 60 * 24 * 1000) + 1;
52
53 Ok(CashReport {
54 account_id: node.get_attribute("accountId")?,
55 currency: Currency::try_from(node.node.attribute("currency").unwrap())?,
56
57 starting_cash: node.parse_attribute("startingCash")?,
58 ending_cash: node.parse_attribute("endingCash")?,
59 ending_settled_cash: node.parse_attribute("endingSettledCash")?,
60
61 net_trade_purchases: node.parse_attribute("netTradesPurchases")?,
62 net_trade_sales: node.parse_attribute("netTradesSales")?,
63
64 commissions: node.parse_attribute("commissions")?,
65 commissions_mtd: node.parse_attribute_opt("commissionsMTD")?,
66 commissions_ytd: node.parse_attribute_opt("commissionsYTD")?,
67
68 other_fees: node.parse_attribute("otherFees")?,
69 other_fees_mtd: node.parse_attribute_opt("otherFeesMTD")?,
70 other_fees_ytd: node.parse_attribute_opt("otherFeesYTD")?,
71
72 dividends: node.parse_attribute("dividends")?,
73 dividends_mtd: node.parse_attribute_opt("dividendsMTD")?,
74 dividends_ytd: node.parse_attribute_opt("dividendsYTD")?,
75
76 interest: node.parse_attribute("brokerInterest")?,
77 interest_mtd: node.parse_attribute_opt("brokerInterestMTD")?,
78 interest_ytd: node.parse_attribute_opt("brokerInterestYTD")?,
79
80 deposits: node.parse_attribute("deposits")?,
81 deposits_mtd: node.parse_attribute_opt("depositsMTD")?,
82 deposits_ytd: node.parse_attribute_opt("depositsYTD")?,
83
84 withdrawals: node.parse_attribute("withdrawals")?,
85 withdrawals_mtd: node.parse_attribute_opt("withdrawalsMTD")?,
86 withdrawals_ytd: node.parse_attribute_opt("withdrawalsYTD")?,
87
88 start_timestamp_ms,
89 end_timestamp_ms: time_utils::trading_eod_after_hours_timestamp_ms(
90 node.node.attribute("toDate").unwrap(),
91 )?,
92 })
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use crate::Parser;
100 use crate::currency::Currency;
101 use anyhow::Result;
102
103 const PARTIAL_STATEMENT_EXAMPLE: &str = r##"
104 <FlexQueryResponse queryName="example-query" type="AF">
105 <FlexStatements count="1">
106 <FlexStatement accountId="U1234567" fromDate="2025-04-25" toDate="2025-04-25" period="LastBusinessDay" whenGenerated="2025-04-26;13:34:28 EDT">
107 <AccountInformation accountId="U1234567" accountType="Individual" customerType="Individual" accountCapabilities="Portfolio Margin" tradingPermissions="Stocks,Options,Warrants,Forex,Futures,Crypto Currencies,Mutual Funds,Fully Paid Stock Loan" />
108 <CashReport>
109 <CashReportCurrency
110 accountId="U1234567"
111 currency="USD"
112 fromDate="2025-04-25"
113 toDate="2025-04-25"
114 startingCash="-1755658.754517244"
115 startingCashSec="-1755658.754517244"
116 startingCashCom="0"
117 commissions="-56.26956551"
118 commissionsSec="-56.26956551"
119 commissionsCom="0"
120 commissionsMTD="-11167.4772929"
121 commissionsYTD="-25339.56064716"
122 depositWithdrawals="0"
123 depositWithdrawalsSec="0"
124 depositWithdrawalsCom="0"
125 depositWithdrawalsMTD="0"
126 depositWithdrawalsYTD="1650000"
127 debitCardActivity="0"
128 debitCardActivitySec="0"
129 debitCardActivityCom="0"
130 debitCardActivityMTD="0"
131 debitCardActivityYTD="0"
132 dividends="0"
133 dividendsSec="0"
134 dividendsCom="0"
135 dividendsMTD="0"
136 dividendsYTD="110.7"
137 otherFees="-19.77"
138 otherFeesSec="-19.77"
139 otherFeesCom="0"
140 otherFeesMTD="-121.27"
141 otherFeesYTD="-486.9"
142 otherIncome="0"
143 otherIncomeSec="0"
144 otherIncomeCom="0"
145 otherIncomeMTD="0"
146 otherIncomeYTD="0"
147 netTradesSales="0"
148 netTradesSalesSec="0"
149 netTradesSalesCom="0"
150 netTradesPurchases="0"
151 netTradesPurchasesSec="0"
152 netTradesPurchasesCom="0"
153 endingCash="-1856140.999082752"
154 endingCashSec="-1856140.999082752"
155 endingCashCom="0"
156 endingSettledCash="-1755734.794082752"
157 endingSettledCashSec="-1755734.794082752"
158 endingSettledCashCom="0"
159 brokerInterest="0"
160 brokerInterestSec="0"
161 brokerInterestCom="0"
162 brokerInterestMTD="-545.49"
163 brokerInterestYTD="-1341.59"
164 brokerFees="0"
165 brokerFeesSec="0"
166 brokerFeesCom="0"
167 brokerFeesMTD="0"
168 brokerFeesYTD="0"
169 deposits="0"
170 depositsSec="0"
171 depositsCom="0"
172 depositsMTD="0"
173 depositsYTD="1650000"
174 withdrawals="0"
175 withdrawalsSec="0"
176 withdrawalsCom="0"
177 withdrawalsMTD="0"
178 withdrawalsYTD="0" />
179 </CashReport>
180
181 </FlexStatement>
182 </FlexStatements>
183 </FlexQueryResponse>
184 "##;
185
186 const PARTIAL_STATEMENT_EXAMPLE_NO_MTD_YTD: &str = r##"
187 <FlexQueryResponse queryName="some-query" type="AF">
188 <FlexStatements count="1">
189 <FlexStatement accountId="U1234567" fromDate="2024-01-01" toDate="2024-01-01" period="" whenGenerated="2025-05-04;01:26:45 EDT">
190 <AccountInformation accountId="U1234567" accountType="Individual" customerType="Individual" accountCapabilities="Portfolio Margin" tradingPermissions="Stocks,Options,Warrants,Forex,Futures,Crypto Currencies,Mutual Funds,Fully Paid Stock Loan" />
191 <CashReport>
192 <CashReportCurrency accountId="U1234567" currency="BASE_SUMMARY" fromDate="2024-01-01" toDate="2024-01-01" startingCash="1308.407281684" startingCashSec="1308.407281684" startingCashCom="0" commissions="0" commissionsSec="0" commissionsCom="0" depositWithdrawals="0" depositWithdrawalsSec="0" depositWithdrawalsCom="0" debitCardActivity="0" debitCardActivitySec="0" debitCardActivityCom="0" dividends="0" dividendsSec="0" dividendsCom="0" otherFees="0" otherFeesSec="0" otherFeesCom="0" otherIncome="0" otherIncomeSec="0" otherIncomeCom="0" endingCash="1308.407282342" endingCashSec="1308.407282342" endingCashCom="0" endingSettledCash="1308.407282342" endingSettledCashSec="1308.407282342" endingSettledCashCom="0" brokerInterest="0" brokerInterestSec="0" brokerInterestCom="0" brokerFees="0" brokerFeesSec="0" brokerFeesCom="0" deposits="0" depositsSec="0" depositsCom="0" withdrawals="0" withdrawalsSec="0" withdrawalsCom="0" netTradesSales="0" netTradesSalesSec="0" netTradesSalesCom="0" netTradesPurchases="0" netTradesPurchasesSec="0" netTradesPurchasesCom="0" transactionTax="0" transactionTaxSec="0" transactionTaxCom="0" withholdingTax="0" withholdingTaxSec="0" withholdingTaxCom="0" withholdingCollectedTax="0" withholdingCollectedTaxSec="0" withholdingCollectedTaxCom="0" slbNetSecuritiesLentActivity="0" slbNetSecuritiesLentActivitySec="0" slbNetSecuritiesLentActivityCom="0" />
193 <CashReportCurrency accountId="U1234567" currency="CAD" fromDate="2024-01-01" toDate="2024-01-01" startingCash="0.001153" startingCashSec="0.001153" startingCashCom="0" commissions="0" commissionsSec="0" commissionsCom="0" depositWithdrawals="0" depositWithdrawalsSec="0" depositWithdrawalsCom="0" debitCardActivity="0" debitCardActivitySec="0" debitCardActivityCom="0" dividends="0" dividendsSec="0" dividendsCom="0" otherFees="0" otherFeesSec="0" otherFeesCom="0" otherIncome="0" otherIncomeSec="0" otherIncomeCom="0" endingCash="0.001153" endingCashSec="0.001153" endingCashCom="0" endingSettledCash="0.001153" endingSettledCashSec="0.001153" endingSettledCashCom="0" brokerInterest="0" brokerInterestSec="0" brokerInterestCom="0" brokerFees="0" brokerFeesSec="0" brokerFeesCom="0" deposits="0" depositsSec="0" depositsCom="0" withdrawals="0" withdrawalsSec="0" withdrawalsCom="0" netTradesSales="0" netTradesSalesSec="0" netTradesSalesCom="0" netTradesPurchases="0" netTradesPurchasesSec="0" netTradesPurchasesCom="0" transactionTax="0" transactionTaxSec="0" transactionTaxCom="0" withholdingTax="0" withholdingTaxSec="0" withholdingTaxCom="0" withholdingCollectedTax="0" withholdingCollectedTaxSec="0" withholdingCollectedTaxCom="0" slbNetSecuritiesLentActivity="0" slbNetSecuritiesLentActivitySec="0" slbNetSecuritiesLentActivityCom="0" />
194 <CashReportCurrency accountId="U1234567" currency="USD" fromDate="2024-01-01" toDate="2024-01-01" startingCash="1308.406411423" startingCashSec="1308.406411423" startingCashCom="0" commissions="0" commissionsSec="0" commissionsCom="0" depositWithdrawals="0" depositWithdrawalsSec="0" depositWithdrawalsCom="0" debitCardActivity="0" debitCardActivitySec="0" debitCardActivityCom="0" dividends="0" dividendsSec="0" dividendsCom="0" otherFees="0" otherFeesSec="0" otherFeesCom="0" otherIncome="0" otherIncomeSec="0" otherIncomeCom="0" endingCash="1308.406411423" endingCashSec="1308.406411423" endingCashCom="0" endingSettledCash="1308.406411423" endingSettledCashSec="1308.406411423" endingSettledCashCom="0" brokerInterest="0" brokerInterestSec="0" brokerInterestCom="0" brokerFees="0" brokerFeesSec="0" brokerFeesCom="0" deposits="0" depositsSec="0" depositsCom="0" withdrawals="0" withdrawalsSec="0" withdrawalsCom="0" netTradesSales="0" netTradesSalesSec="0" netTradesSalesCom="0" netTradesPurchases="0" netTradesPurchasesSec="0" netTradesPurchasesCom="0" transactionTax="0" transactionTaxSec="0" transactionTaxCom="0" withholdingTax="0" withholdingTaxSec="0" withholdingTaxCom="0" withholdingCollectedTax="0" withholdingCollectedTaxSec="0" withholdingCollectedTaxCom="0" slbNetSecuritiesLentActivity="0" slbNetSecuritiesLentActivitySec="0" slbNetSecuritiesLentActivityCom="0" />
195 </CashReport>
196 </FlexStatement>
197 </FlexStatements>
198 </FlexQueryResponse>
199 "##;
200
201 #[test]
202 fn cash_reports_parse() -> Result<()> {
203 let statements = Parser::new()?.parse_flex_query_response(PARTIAL_STATEMENT_EXAMPLE)?;
204 assert_eq!(statements.len(), 1);
205 let result = &statements[0];
206
207 assert_eq!(result.cash_reports.len(), 1);
209
210 assert_eq!(
211 result.cash_reports[0],
212 CashReport {
213 account_id: "U1234567".to_string(),
214 currency: Currency::USD,
215
216 starting_cash: -1755658.754517244,
217 ending_cash: -1856140.999082752,
218 ending_settled_cash: -1755734.794082752,
219
220 commissions: -56.26956551,
221 commissions_mtd: Some(-11167.4772929),
222 commissions_ytd: Some(-25339.56064716),
223
224 dividends: 0.0,
225 dividends_mtd: Some(0.0),
226 dividends_ytd: Some(110.7),
227
228 other_fees: -19.77,
229 other_fees_mtd: Some(-121.27),
230 other_fees_ytd: Some(-486.9),
231
232 net_trade_purchases: 0.0,
233 net_trade_sales: 0.0,
234
235 interest: 0.0,
236 interest_mtd: Some(-545.49),
237 interest_ytd: Some(-1341.59),
238
239 deposits: 0.0,
240 deposits_mtd: Some(0.0),
241 deposits_ytd: Some(1650000.0),
242
243 withdrawals: 0.0,
244 withdrawals_mtd: Some(0.0),
245 withdrawals_ytd: Some(0.0),
246
247 start_timestamp_ms: result.cash_reports[0].start_timestamp_ms,
248 end_timestamp_ms: result.cash_reports[0].end_timestamp_ms,
249 }
250 );
251
252 Ok(())
253 }
254
255 #[test]
256 fn cash_reports_parse_no_mtd_ytd() -> Result<()> {
257 let statements =
258 Parser::new()?.parse_flex_query_response(PARTIAL_STATEMENT_EXAMPLE_NO_MTD_YTD)?;
259 assert_eq!(statements.len(), 1);
260 let result = &statements[0];
261
262 assert_eq!(result.cash_reports.len(), 3);
264
265 assert_eq!(
266 result.cash_reports[2],
267 CashReport {
268 account_id: "U1234567".to_string(),
269 currency: Currency::USD,
270
271 starting_cash: 1308.406411423,
272 ending_cash: 1308.406411423,
273 ending_settled_cash: 1308.406411423,
274
275 commissions: 0.0,
276 commissions_mtd: None,
277 commissions_ytd: None,
278
279 dividends: 0.0,
280 dividends_mtd: None,
281 dividends_ytd: None,
282
283 other_fees: 0.0,
284 other_fees_mtd: None,
285 other_fees_ytd: None,
286
287 net_trade_purchases: 0.0,
288 net_trade_sales: 0.0,
289
290 interest: 0.0,
291 interest_mtd: None,
292 interest_ytd: None,
293
294 deposits: 0.0,
295 deposits_mtd: None,
296 deposits_ytd: None,
297
298 withdrawals: 0.0,
299 withdrawals_mtd: None,
300 withdrawals_ytd: None,
301
302 start_timestamp_ms: result.cash_reports[2].start_timestamp_ms,
303 end_timestamp_ms: result.cash_reports[2].end_timestamp_ms,
304 }
305 );
306
307 Ok(())
308 }
309}