ibkr-flex-statement 0.3.6

Parser for InteractiveBrokers (IBKR) Flex-based query response XML broker statements.
Documentation
use super::currency::Currency;
use super::time_utils;
use crate::node_utils::NodeWrapper;
use crate::statement_section::StatementSection;
use anyhow::Result;

#[derive(Debug, PartialEq)]
pub struct EquitySummary {
    pub account_id: String,
    pub cash_balance: f64,
    pub cash_balance_long: f64,
    pub cash_balance_short: f64,
    pub currency: Currency,
    pub interest_accrual_mtd: f64,
    pub interest_accrual_mtd_long: f64,
    pub interest_accrual_mtd_short: f64,
    pub stock_balance: f64,
    pub stock_balance_long: f64,
    pub stock_balance_short: f64,
    pub timestamp_eod_ms: i64,
}

impl StatementSection for EquitySummary {
    fn from_node(node: &NodeWrapper) -> Result<EquitySummary> {
        Ok(EquitySummary {
            account_id: node.get_attribute("accountId")?,
            cash_balance: node.parse_attribute("cash")?,
            cash_balance_long: node.parse_attribute("cashLong")?,
            cash_balance_short: node.parse_attribute("cashShort")?,
            currency: Currency::try_from(node.node.attribute("currency").unwrap())?,
            interest_accrual_mtd: node.parse_attribute("interestAccruals")?,
            interest_accrual_mtd_long: node.parse_attribute("interestAccrualsLong")?,
            interest_accrual_mtd_short: node.parse_attribute("interestAccrualsShort")?,
            stock_balance: node.parse_attribute("stock")?,
            stock_balance_long: node.parse_attribute("stockLong")?,
            stock_balance_short: node.parse_attribute("stockShort")?,
            timestamp_eod_ms: time_utils::trading_eod_after_hours_timestamp_ms(
                node.node.attribute("reportDate").unwrap(),
            )?,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::Parser;
    use crate::currency::Currency;
    use anyhow::Result;

    const PARTIAL_STATEMENT_EXAMPLE: &str = r##"
        <FlexQueryResponse queryName="example-query" type="AF">
            <FlexStatements count="1">
                <FlexStatement accountId="U1234567" fromDate="2025-04-25" toDate="2025-04-25" period="LastBusinessDay" whenGenerated="2025-04-26;13:34:28 EDT">
                    <AccountInformation accountId="U1234567" accountType="Individual" customerType="Individual" accountCapabilities="Portfolio Margin" tradingPermissions="Stocks,Options,Warrants,Forex,Futures,Crypto Currencies,Mutual Funds,Fully Paid Stock Loan" />
                    <EquitySummaryInBase>
                        <EquitySummaryByReportDateInBase accountId="U1234567" currency="USD" cash="-1755658.753685009" cashLong="0.000832235" cashShort="-1755658.754517244" commodities="0" commoditiesLong="0" commoditiesShort="0" dividendAccruals="0" dividendAccrualsLong="0" dividendAccrualsShort="0" interestAccruals="1292.18" interestAccrualsLong="1591.34" interestAccrualsShort="-299.16" stock="3441241" stockLong="3441241" stockShort="0" funds="0" fundsLong="0" fundsShort="0" brokerInterestAccrualsComponent="186.32" brokerInterestAccrualsComponentLong="485.48" brokerInterestAccrualsComponentShort="-299.16" brokerFeesAccrualsComponent="0" brokerFeesAccrualsComponentLong="0" brokerFeesAccrualsComponentShort="0" total="1686874.426314991" totalLong="3442832.340832235" totalShort="-1755957.914517244" reportDate="2025-04-24" />
                        <EquitySummaryByReportDateInBase
                            accountId="U1234567"
                            currency="USD"
                            cash="-1856140.99825062"
                            cashLong="0.000832132"
                            cashShort="-1856140.999082752"
                            commodities="0"
                            commoditiesLong="0"
                            commoditiesShort="0"
                            dividendAccruals="0"
                            dividendAccrualsLong="0"
                            dividendAccrualsShort="0"
                            interestAccruals="1051.42"
                            interestAccrualsLong="1591.34"
                            interestAccrualsShort="-539.92"
                            stock="3664457"
                            stockLong="3664457"
                            stockShort="0"
                            funds="0"
                            fundsLong="0"
                            fundsShort="0"
                            brokerInterestAccrualsComponent="-54.44"
                            brokerInterestAccrualsComponentLong="485.48"
                            brokerInterestAccrualsComponentShort="-539.92"
                            brokerFeesAccrualsComponent="0"
                            brokerFeesAccrualsComponentLong="0"
                            brokerFeesAccrualsComponentShort="0"
                            total="1809367.421749379"
                            totalLong="3666048.340832131"
                            totalShort="-1856680.919082752"
                            reportDate="2025-04-25" />
                    </EquitySummaryInBase>
                </FlexStatement>
            </FlexStatements>
         </FlexQueryResponse>
        "##;

    #[test]
    fn equity_summaries_parse() -> Result<()> {
        let statements = Parser::new()?.parse_flex_query_response(PARTIAL_STATEMENT_EXAMPLE)?;
        assert_eq!(statements.len(), 1);
        let result = &statements[0];

        // Ensure we got two equity summaries.
        assert_eq!(result.equity_summaries.len(), 2);

        assert_eq!(
            result.equity_summaries[1],
            EquitySummary {
                account_id: "U1234567".to_string(),
                cash_balance: -1856140.99825062,
                cash_balance_long: 0.000832132,
                cash_balance_short: -1856140.999082752,
                currency: Currency::USD,
                interest_accrual_mtd: 1051.42,
                interest_accrual_mtd_long: 1591.34,
                interest_accrual_mtd_short: -539.92,
                stock_balance: 3664457.0,
                stock_balance_long: 3664457.0,
                stock_balance_short: 0.0,
                timestamp_eod_ms: result.equity_summaries[1].timestamp_eod_ms,
            }
        );
        Ok(())
    }
}