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_trades: Option<f64>,
19 pub net_trade_purchases: Option<f64>,
20 pub net_trade_sales: Option<f64>,
21
22 pub commissions: f64,
23 pub commissions_mtd: f64,
24 pub commissions_ytd: f64,
25
26 pub other_fees: f64,
27 pub other_fees_mtd: f64,
28 pub other_fees_ytd: f64,
29
30 pub dividends: f64,
31 pub dividends_mtd: f64,
32 pub dividends_ytd: f64,
33
34 pub interest: f64,
35 pub interest_mtd: f64,
36 pub interest_ytd: f64,
37
38 pub deposits: f64,
39 pub deposits_mtd: f64,
40 pub deposits_ytd: f64,
41
42 pub withdrawals: f64,
43 pub withdrawals_mtd: f64,
44 pub withdrawals_ytd: f64,
45}
46
47impl StatementSection for CashReport {
48 fn from_node(node: &NodeWrapper) -> Result<CashReport> {
49 let start_date_eod_ms_plus_one = time_utils::trading_eod_after_hours_timestamp_ms(
50 node.node.attribute("fromDate").unwrap(),
51 )?;
52 let start_timestamp_ms = start_date_eod_ms_plus_one - (60 * 60 * 24 * 1000) + 1;
53
54 Ok(CashReport {
55 account_id: node.get_attribute("accountId")?,
56 currency: Currency::try_from(node.node.attribute("currency").unwrap())?,
57
58 starting_cash: node.parse_attribute("startingCash")?,
59 ending_cash: node.parse_attribute("endingCash")?,
60 ending_settled_cash: node.parse_attribute("endingSettledCash")?,
61
62 net_trades: node.parse_attribute_opt("netTrades")?,
63 net_trade_purchases: node.parse_attribute_opt("netTradesPurchases")?,
64 net_trade_sales: node.parse_attribute_opt("netTradesSales")?,
65
66 commissions: node.parse_attribute("commissions")?,
67 commissions_mtd: node.parse_attribute("commissionsMTD")?,
68 commissions_ytd: node.parse_attribute("commissionsYTD")?,
69
70 other_fees: node.parse_attribute("otherFees")?,
71 other_fees_mtd: node.parse_attribute("otherFeesMTD")?,
72 other_fees_ytd: node.parse_attribute("otherFeesYTD")?,
73
74 dividends: node.parse_attribute("dividends")?,
75 dividends_mtd: node.parse_attribute("dividendsMTD")?,
76 dividends_ytd: node.parse_attribute("dividendsYTD")?,
77
78 interest: node.parse_attribute("brokerInterest")?,
79 interest_mtd: node.parse_attribute("brokerInterestMTD")?,
80 interest_ytd: node.parse_attribute("brokerInterestYTD")?,
81
82 deposits: node.parse_attribute("deposits")?,
83 deposits_mtd: node.parse_attribute("depositsMTD")?,
84 deposits_ytd: node.parse_attribute("depositsYTD")?,
85
86 withdrawals: node.parse_attribute("withdrawals")?,
87 withdrawals_mtd: node.parse_attribute("withdrawalsMTD")?,
88 withdrawals_ytd: node.parse_attribute("withdrawalsYTD")?,
89
90 start_timestamp_ms,
91 end_timestamp_ms: time_utils::trading_eod_after_hours_timestamp_ms(
92 node.node.attribute("toDate").unwrap(),
93 )?,
94 })
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use crate::Parser;
102 use crate::currency::Currency;
103 use anyhow::Result;
104
105 const FULL_STATEMENT_EXAMPLE: &str = r##"
106 <FlexQueryResponse queryName="example-query" type="AF">
107 <FlexStatements count="1">
108 <FlexStatement accountId="U1234567" fromDate="2025-04-25" toDate="2025-04-25" period="LastBusinessDay" whenGenerated="2025-04-26;13:34:28 EDT">
109 <AccountInformation accountId="U1234567" accountType="Individual" customerType="Individual" accountCapabilities="Portfolio Margin" tradingPermissions="Stocks,Options,Warrants,Forex,Futures,Crypto Currencies,Mutual Funds,Fully Paid Stock Loan" />
110 <CashReport>
111 <CashReportCurrency accountId="U1234567" currency="BASE_SUMMARY" fromDate="2025-04-25" toDate="2025-04-25" startingCash="-1755658.753685008" startingCashSec="-1755658.753685008" startingCashCom="0" commissions="-56.26956551" commissionsSec="-56.26956551" commissionsCom="0" commissionsMTD="-11167.4772929" commissionsYTD="-25339.56064716" depositWithdrawals="0" depositWithdrawalsSec="0" depositWithdrawalsCom="0" depositWithdrawalsMTD="0" depositWithdrawalsYTD="1650000" debitCardActivity="0" debitCardActivitySec="0" debitCardActivityCom="0" debitCardActivityMTD="0" debitCardActivityYTD="0" dividends="0" dividendsSec="0" dividendsCom="0" dividendsMTD="0" dividendsYTD="110.7" otherFees="-19.77" otherFeesSec="-19.77" otherFeesCom="0" otherFeesMTD="-121.27" otherFeesYTD="-486.9" otherIncome="0" otherIncomeSec="0" otherIncomeCom="0" otherIncomeMTD="0" otherIncomeYTD="0" endingCash="-1856140.99825062" endingCashSec="-1856140.99825062" endingCashCom="0" endingSettledCash="-1755734.79325062" endingSettledCashSec="-1755734.79325062" endingSettledCashCom="0" brokerInterest="0" brokerInterestSec="0" brokerInterestCom="0" brokerInterestMTD="-545.49" brokerInterestYTD="-1341.59" brokerFees="0" brokerFeesSec="0" brokerFeesCom="0" brokerFeesMTD="0" brokerFeesYTD="0" deposits="0" depositsSec="0" depositsCom="0" depositsMTD="0" depositsYTD="1650000" withdrawals="0" withdrawalsSec="0" withdrawalsCom="0" withdrawalsMTD="0" withdrawalsYTD="0" />
112 <CashReportCurrency accountId="U1234567" currency="CAD" fromDate="2025-04-25" toDate="2025-04-25" startingCash="0.001153" startingCashSec="0.001153" startingCashCom="0" commissions="0" commissionsSec="0" commissionsCom="0" commissionsMTD="0" commissionsYTD="0" depositWithdrawals="0" depositWithdrawalsSec="0" depositWithdrawalsCom="0" depositWithdrawalsMTD="0" depositWithdrawalsYTD="0" debitCardActivity="0" debitCardActivitySec="0" debitCardActivityCom="0" debitCardActivityMTD="0" debitCardActivityYTD="0" dividends="0" dividendsSec="0" dividendsCom="0" dividendsMTD="0" dividendsYTD="0" otherFees="0" otherFeesSec="0" otherFeesCom="0" otherFeesMTD="0" otherFeesYTD="0" otherIncome="0" otherIncomeSec="0" otherIncomeCom="0" otherIncomeMTD="0" otherIncomeYTD="0" endingCash="0.001153" endingCashSec="0.001153" endingCashCom="0" endingSettledCash="0.001153" endingSettledCashSec="0.001153" endingSettledCashCom="0" brokerInterest="0" brokerInterestSec="0" brokerInterestCom="0" brokerInterestMTD="0" brokerInterestYTD="0" brokerFees="0" brokerFeesSec="0" brokerFeesCom="0" brokerFeesMTD="0" brokerFeesYTD="0" deposits="0" depositsSec="0" depositsCom="0" depositsMTD="0" depositsYTD="0" withdrawals="0" withdrawalsSec="0" withdrawalsCom="0" withdrawalsMTD="0" withdrawalsYTD="0" />
113 <CashReportCurrency
114 accountId="U1234567"
115 currency="USD"
116 fromDate="2025-04-25"
117 toDate="2025-04-25"
118 startingCash="-1755658.754517244"
119 startingCashSec="-1755658.754517244"
120 startingCashCom="0"
121 commissions="-56.26956551"
122 commissionsSec="-56.26956551"
123 commissionsCom="0"
124 commissionsMTD="-11167.4772929"
125 commissionsYTD="-25339.56064716"
126 depositWithdrawals="0"
127 depositWithdrawalsSec="0"
128 depositWithdrawalsCom="0"
129 depositWithdrawalsMTD="0"
130 depositWithdrawalsYTD="1650000"
131 debitCardActivity="0"
132 debitCardActivitySec="0"
133 debitCardActivityCom="0"
134 debitCardActivityMTD="0"
135 debitCardActivityYTD="0"
136 dividends="0"
137 dividendsSec="0"
138 dividendsCom="0"
139 dividendsMTD="0"
140 dividendsYTD="110.7"
141 otherFees="-19.77"
142 otherFeesSec="-19.77"
143 otherFeesCom="0"
144 otherFeesMTD="-121.27"
145 otherFeesYTD="-486.9"
146 otherIncome="0"
147 otherIncomeSec="0"
148 otherIncomeCom="0"
149 otherIncomeMTD="0"
150 otherIncomeYTD="0"
151 endingCash="-1856140.999082752"
152 endingCashSec="-1856140.999082752"
153 endingCashCom="0"
154 endingSettledCash="-1755734.794082752"
155 endingSettledCashSec="-1755734.794082752"
156 endingSettledCashCom="0"
157 brokerInterest="0"
158 brokerInterestSec="0"
159 brokerInterestCom="0"
160 brokerInterestMTD="-545.49"
161 brokerInterestYTD="-1341.59"
162 brokerFees="0"
163 brokerFeesSec="0"
164 brokerFeesCom="0"
165 brokerFeesMTD="0"
166 brokerFeesYTD="0"
167 deposits="0"
168 depositsSec="0"
169 depositsCom="0"
170 depositsMTD="0"
171 depositsYTD="1650000"
172 withdrawals="0"
173 withdrawalsSec="0"
174 withdrawalsCom="0"
175 withdrawalsMTD="0"
176 withdrawalsYTD="0" />
177 </CashReport>
178
179 </FlexStatement>
180 </FlexStatements>
181 </FlexQueryResponse>
182 "##;
183
184 #[test]
185 fn cash_reports_parse() -> Result<()> {
186 let result = Parser::new()?.parse_statement_contents(FULL_STATEMENT_EXAMPLE)?;
187
188 assert_eq!(result.cash_reports.len(), 3);
190
191 assert_eq!(
192 result.cash_reports[2],
193 CashReport {
194 account_id: "U1234567".to_string(),
195 currency: Currency::USD,
196
197 starting_cash: -1755658.754517244,
198 ending_cash: -1856140.999082752,
199 ending_settled_cash: -1755734.794082752,
200
201 commissions: -56.26956551,
202 commissions_mtd: -11167.4772929,
203 commissions_ytd: -25339.56064716,
204
205 dividends: 0.0,
206 dividends_mtd: 0.0,
207 dividends_ytd: 110.7,
208
209 other_fees: -19.77,
210 other_fees_mtd: -121.27,
211 other_fees_ytd: -486.9,
212
213 net_trades: None,
214 net_trade_purchases: None,
215 net_trade_sales: None,
216
217 interest: 0.0,
218 interest_mtd: -545.49,
219 interest_ytd: -1341.59,
220
221 deposits: 0.0,
222 deposits_mtd: 0.0,
223 deposits_ytd: 1650000.0,
224
225 withdrawals: 0.0,
226 withdrawals_mtd: 0.0,
227 withdrawals_ytd: 0.0,
228
229 start_timestamp_ms: result.cash_reports[2].start_timestamp_ms,
230 end_timestamp_ms: result.cash_reports[2].end_timestamp_ms,
231 }
232 );
233
234 Ok(())
235 }
236}