ib-flex 0.2.3

Type-safe parser for Interactive Brokers (IBKR) FLEX XML statements: trades, positions, cash flows, corporate actions, and P&L.
Documentation
# ib-flex

[![crates.io](https://img.shields.io/crates/v/ib-flex.svg)](https://crates.io/crates/ib-flex)
[![docs.rs](https://docs.rs/ib-flex/badge.svg)](https://docs.rs/ib-flex)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

Type-safe Rust parser for Interactive Brokers FLEX XML statements with comprehensive coverage and financial precision.

## Features

- **Complete attribute coverage** - Maps the full set of IB FLEX attributes (modeled against the mature [ibflex]https://github.com/csingley/ibflex reference), so you don't silently lose data
- **Robust on real data** - Tolerates IB's real-world quirks: thousands separators, `-`/`--`/`N/A` sentinels, `MULTI` dates, `Yes`/`No` booleans, legacy date/time separators
- **Financial precision** - `rust_decimal` for all monetary values (no floating-point error)
- **Type-safe** - 19 enums covering asset classes, trade/cash/corporate-action types, transfer types, and more
- **Comprehensive sections** - Trades, positions, cash, corporate actions, transfers, performance summaries, accruals, and 40+ FLEX sections in total
- **Edge cases covered** - Warrants, T-Bills, CFDs, crypto, fractional shares, cancelled/corrected trades, cost-basis lots
- **Optional API client** - Fetch FLEX statements programmatically from IB (`api-client` feature)

## Installation

Add this to your `Cargo.toml`:

```toml
[dependencies]
ib-flex = "0.2"
```

## Quick Start

Pull a NAV time series out of a multi-period Activity FLEX export (e.g. "Last 180
Calendar Days") and compute an annualized Sharpe ratio:

```rust
use ib_flex::parse_activity_flex_all;
use rust_decimal::prelude::ToPrimitive; // for Decimal::to_f64

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let xml = std::fs::read_to_string("flex_statement.xml")?;

    // NAV over time: the `total` of each daily equity-summary row, across all
    // statements, sorted by date. Every attribute is `Option`, so `?`-filter.
    let mut nav: Vec<(chrono::NaiveDate, f64)> = parse_activity_flex_all(&xml)?
        .iter()
        .flat_map(|stmt| stmt.equity_summary.items.iter())
        .filter_map(|e| Some((e.report_date?, e.total?.to_f64()?)))
        .collect();
    nav.sort_by_key(|(date, _)| *date);

    // Daily returns -> annualized Sharpe (risk-free = 0, ~252 trading days).
    let returns: Vec<f64> = nav.windows(2).map(|w| w[1].1 / w[0].1 - 1.0).collect();
    let mean = returns.iter().sum::<f64>() / returns.len() as f64;
    let variance = returns.iter().map(|r| (r - mean).powi(2)).sum::<f64>() / returns.len() as f64;
    let sharpe = mean / variance.sqrt() * 252f64.sqrt();

    println!("{} NAV points, annualized Sharpe: {sharpe:.2}", nav.len());
    Ok(())
}
```

Run it on your own export with `cargo run --example nav_sharpe -- statement.xml`.

Want trades or other sections instead? Parse a single statement and read any
section directly โ€” every attribute-mapped field is an `Option<T>` (real
statements may omit any attribute):

```rust
use ib_flex::{detect_statement_type, parse_activity_flex, StatementType};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let xml = std::fs::read_to_string("flex_statement.xml")?;
    if let StatementType::Activity = detect_statement_type(&xml)? {
        let statement = parse_activity_flex(&xml)?;
        for trade in &statement.trades.items {
            println!(
                "{} {} @ {}",
                trade.symbol.as_deref().unwrap_or("?"),
                trade.quantity.unwrap_or_default(),
                trade.price.unwrap_or_default(),
            );
        }
    }
    Ok(())
}
```

> **Upgrading from 0.1.x?** 0.2.0 makes all attribute-derived fields `Option<T>`
> so a statement that omits any attribute no longer fails to parse. Update call
> sites accordingly (e.g. `statement.account_id` โ†’ `statement.account_id.as_deref()`).
> See the [CHANGELOG]CHANGELOG.md.

## FLEX Query Setup

Interactive Brokers FLEX queries must be configured in the IB Client Portal:

1. Navigate to: Reports โ†’ Flex Queries โ†’ Create Activity Flex Query
2. Select required sections (Trades, Positions, Cash Transactions, etc.)
3. Choose date format: ISO-8601 (`yyyy-MM-dd`) or compact (`yyyyMMdd`)
4. Set output format to XML
5. Save query and note the Query ID

**Important**: European date formats (`dd/MM/yyyy`) are NOT supported by the IB FLEX API.

๐Ÿ“˜ **For comprehensive setup instructions**, see [FLEX_SETUP.md](FLEX_SETUP.md) which covers all 21 recommended sections, field selections, and configuration options.

## FLEX Web Service API Client (Optional)

The `api-client` feature provides programmatic access to fetch FLEX statements directly from Interactive Brokers without manual downloads.

### Installation with API Client

```toml
[dependencies]
ib-flex = { version = "0.2", features = ["api-client"] }
```

### API Setup

1. Log in to IB Account Management
2. Navigate to: Reports โ†’ Settings โ†’ FlexWeb Service
3. Generate a FLEX Web Service token (keep it secure!)
4. Note your FLEX Query ID from the Flex Queries page

### Usage Example

```rust
use ib_flex::api::FlexApiClient;
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create client with your token
    let client = FlexApiClient::new("YOUR_TOKEN");

    // Step 1: Send request with your query ID
    let reference_code = client.send_request("123456").await?;

    // Step 2: Get statement with automatic retry
    let xml = client.get_statement_with_retry(
        &reference_code,
        10,                           // max retries
        Duration::from_secs(2)        // delay between retries
    ).await?;

    // Step 3: Parse the statement
    let statement = ib_flex::parse_activity_flex(&xml)?;
    println!("Trades: {}", statement.trades.items.len());

    Ok(())
}
```

### API Examples

Run the API examples (requires IB credentials):

```bash
export IB_FLEX_TOKEN="your_token"
export IB_FLEX_QUERY_ID="your_query_id"
cargo run --example fetch_flex_statement --features api-client
cargo run --example api_simple_usage --features api-client
cargo run --example api_with_retry --features api-client
```

## Supported FLEX Sections

### Activity FLEX - Core Types
- โœ… **Trades** - Executions with 90+ fields including P&L, commissions, dates, IDs, security details
- โœ… **Trade sub-rows** - `<Lot>` cost-basis detail and `<Order>`/`<SymbolSummary>`/`<AssetSummary>` roll-ups captured from inside `<Trades>`
- โœ… **Open Positions** - Current holdings
- โœ… **Cash Transactions** - Deposits, withdrawals, interest, fees, dividends
- โœ… **Corporate Actions** - Splits, mergers, spinoffs, dividends
- โœ… **Securities Info** - Reference data for all traded instruments
- โœ… **FX Conversion Rates** - Currency conversion rates for multi-currency accounts

### Extended FLEX Sections
- โœ… **Account Information**, **Change in NAV**, **Equity Summary**, **Cash Report**
- โœ… **Trade Confirmations** - Real-time trade execution confirmations
- โœ… **Option EAE** - Option exercises, assignments, and expirations
- โœ… **FX Transactions** & **FX Lots** - Foreign exchange conversions and lots
- โœ… **Dividend Accruals** (open + change) and **Interest Accruals** (incl. tier detail)
- โœ… **Transfers** - Transfers, trade transfers, unsettled transfers (ACATS, ATON, FOP, internal, OTC, ...)
- โœ… **Performance summaries** - MTM, FIFO, and MTD/YTD
- โœ… **Securities lending** - SLB activity, fees, hard-to-borrow, and open contracts
- โœ… **Fees & commissions** - Client fees, unbundled commission detail, transaction taxes, sales tax
- โœ… **Other** - Net stock positions, prior-period positions, statement of funds, change-in-position value, debit-card activity, CFD charges, stock-grant activity

### Asset Classes Supported
- โœ… **Stocks (STK)** - Including fractional shares
- โœ… **Options (OPT)** - Calls, puts, assignments, expirations
- โœ… **Futures (FUT)** - All major contracts (ES, NQ, CL, GC, etc.)
- โœ… **Forex (CASH)** - FX trades and positions
- โœ… **Bonds (BOND)** - Treasuries, corporate, municipal
- โœ… **Treasury Bills (BILL)** - With maturity handling
- โœ… **Warrants (WAR)** - Equity warrants
- โœ… **CFDs (CFD / FXCFD)** - Contract for difference
- โœ… **Crypto (CRYPTO)** - Cryptocurrency
- โœ… **Funds, Commodities, and more** - 22 asset categories total

### Trade Confirmation FLEX
- โœ… **Trade Confirmations** - Real-time trade execution data
- โœ… **All trade fields** - Full support for all trade attributes
- โœ… **Automatic detection** - Detect statement type from XML

## Performance

Benchmarked on M1 MacBook Pro:

| Statement Type | Transactions | Parse Time |
|---------------|--------------|------------|
| Minimal | 1 trade | ~6.5 ยตs |
| Options | 4 trades | ~65 ยตs |
| Cash | 15 transactions | ~71 ยตs |

The full multi-statement test corpus (142 statements, ~2,600 trades) parses in a few milliseconds.

## Type Safety

All financial values use `rust_decimal::Decimal` for exact arithmetic without floating-point error. The public enums include:

- **AssetCategory** (22 variants) - STK, OPT, FUT, FOP, CASH, BOND, BILL, CFD, FXCFD, WAR, FUND, CMDTY, CRYPTO, FSFOP, FSOPT, ...
- **BuySell, OpenClose, PutCall, LongShort** - trade direction and option basics
- **TradeType** - ExchTrade, BookTrade, TradeCancel, TradeCorrect, FracShare, ...
- **OrderType** - market, limit, stop, stop-limit, trailing, ...
- **CashTransactionType** - dividends, interest, fees, deposits/withdrawals, ...
- **SecurityIdType** - CUSIP, ISIN, FIGI, SEDOL
- **SubCategory** - ETF, ADR, REIT, Preferred, Common, ...
- **LevelOfDetail** - Summary, Detail, Execution, Lot
- **DerivativeInfo** - consolidated option/future/warrant metadata via `Trade::derivative()`

## Examples

The repository includes several complete example programs:

### Parsing Examples
1. **nav_sharpe.rs** - NAV time series + annualized Sharpe ratio (the Quick Start example)
2. **parse_activity_statement.rs** - Basic Activity FLEX parsing and display
3. **filter_trades.rs** - Filter by asset class, side, symbol, quantity, P&L, date range
4. **calculate_commissions.rs** - Analyze commission costs by category
5. **parse_trade_confirmation.rs** - Trade Confirmation FLEX parsing

Run parsing examples:
```bash
cargo run --example parse_activity_statement
cargo run --example parse_trade_confirmation
cargo run --example filter_trades
cargo run --example calculate_commissions
```

### Historical Backfill Example
6. **backfill_summary.rs** - Parse multi-statement FLEX XML and display comprehensive summary

To use this example:
1. Create a FLEX query with **Period: Last 180 Calendar Days** (or similar) in [IBKR Client Portal]https://portal.interactivebrokers.com - see [FLEX_SETUP.md]FLEX_SETUP.md
2. Download the XML file manually or via API
3. Run the summary:

```bash
cargo run --example backfill_summary -- path/to/your/backfill.xml
```

Output includes:
- NAV over time with returns, high/low, drawdown
- Trading activity by symbol with P&L attribution
- Cash transaction breakdown (dividends, interest, fees)
- Current positions from latest statement
- Monthly returns table

### API Client Examples (requires `api-client` feature)
7. **fetch_flex_statement.rs** - Complete API workflow with detailed output
8. **api_simple_usage.rs** - Minimal API client usage
9. **api_with_retry.rs** - API client with automatic retry logic

Run API examples:
```bash
export IB_FLEX_TOKEN="your_token"
export IB_FLEX_QUERY_ID="your_query_id"
cargo run --example fetch_flex_statement --features api-client
```

## Development

### Build

```bash
cargo build
```

### Test

```bash
cargo test
```

### Benchmark

```bash
cargo bench
```

### Format

```bash
cargo fmt
```

### Lint

```bash
cargo clippy --all-targets -- -D warnings
```

## Testing

The library has comprehensive test coverage including:

- **Integration tests** covering all asset classes and edge cases
- **Extended types tests** for FLEX sections
- **Error tests** for malformed XML and invalid data
- **Unit tests** for custom deserializers
- **Doc tests** in inline documentation
- **XML fixtures** including extended types, warrants, T-Bills, CFDs, fractional shares, cancelled trades

### Reliability Testing

The library includes comprehensive reliability tests using:
- **Property-based testing** with proptest for random inputs
- **Stress tests** for large XML files
- **Concurrency tests** for thread safety
- **Memory efficiency tests** for repeated parsing
- **Edge case fuzzing** for malformed inputs

Run tests with:
```bash
cargo test           # All tests
cargo test --doc     # Documentation tests only
```

## Contributing

Contributions welcome! This is an open-source project designed to benefit the Rust trading community.

**Before submitting a PR:**
1. Ensure all tests pass: `cargo test`
2. Run clippy: `cargo clippy --all-targets -- -D warnings`
3. Format code: `cargo fmt`
4. Add tests for new features
5. Update CHANGELOG.md

Bug reports and feature requests are appreciated. When reporting bugs, please include:
- Anonymized XML sample demonstrating the issue
- Expected vs actual behavior
- Rust version and platform

## Documentation

- [API Documentation]https://docs.rs/ib-flex
- [FLEX Query Setup Guide]FLEX_SETUP.md - How to configure your IBKR FLEX query
- [Changelog]CHANGELOG.md - Release history
- [Project Guide for Claude Code]CLAUDE.md - Development guide

## Resources

- [IB FLEX Queries Guide]https://www.ibkrguides.com/orgportal/performanceandstatements/flex.htm
- [Activity FLEX Reference]https://www.ibkrguides.com/reportingreference/reportguide/activity%20flex%20query%20reference.htm
- [FLEX Web Service API]https://www.interactivebrokers.com/campus/ibkr-api-page/flex-web-service/

## License

Licensed under the [MIT License](LICENSE).

## Acknowledgments

- Inspired by [csingley/ibflex]https://github.com/csingley/ibflex (Python) - Comprehensive enum research
- Built with [quick-xml]https://github.com/tafia/quick-xml - Fast XML parsing with serde
- [rust_decimal]https://github.com/paupino/rust-decimal - Financial precision
- [chrono]https://github.com/chronotope/chrono - Date/time handling