ibkr_flex_statement/
cash_report.rs

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        // Ensure we got two equity summaries.
208        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        // Ensure we got two equity summaries.
263        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}