use ib_flex::parse_activity_flex_all;
use ib_flex::types::common::{CashTransactionType, LevelOfDetail};
use rust_decimal::Decimal;
#[test]
fn real_statement_parses_and_populates() {
let Ok(path) = std::env::var("IB_FLEX_REAL_XML") else {
eprintln!("skipping: set IB_FLEX_REAL_XML to a real Activity FLEX file to run");
return;
};
let xml = std::fs::read_to_string(&path).expect("read real XML file");
let statements = parse_activity_flex_all(&xml)
.unwrap_or_else(|e| panic!("real statement failed to parse: {e}"));
assert!(
!statements.is_empty(),
"expected at least one FlexStatement"
);
let mut trades = 0usize;
let mut lots = 0usize;
let mut orders = 0usize;
let mut symbol_summaries = 0usize;
let mut trades_with_price = 0usize;
let mut trades_with_ib_order_id = 0usize;
let mut trades_with_net_cash_in_base = 0usize;
let mut positions = 0usize;
let mut conversion_rates = 0usize;
let mut corporate_actions = 0usize;
for s in &statements {
trades += s.trades.items.len();
lots += s.trades.lots.len();
orders += s.trades.orders.len();
symbol_summaries += s.trades.symbol_summaries.len();
positions += s.positions.items.len();
conversion_rates += s.conversion_rates.items.len();
corporate_actions += s.corporate_actions.items.len();
for t in &s.trades.items {
if t.price.is_some() {
trades_with_price += 1;
}
if t.ib_order_id.is_some() {
trades_with_ib_order_id += 1;
}
if t.net_cash_in_base.is_some() {
trades_with_net_cash_in_base += 1;
}
}
}
eprintln!(
"real-data smoke: {} statements | {trades} trades ({trades_with_price} w/price, \
{trades_with_ib_order_id} w/ibOrderID, {trades_with_net_cash_in_base} w/netCashInBase) | \
{lots} lots | {orders} orders | {symbol_summaries} symbolSummaries | \
{positions} positions | {conversion_rates} convRates | {corporate_actions} corpActions",
statements.len()
);
if trades > 0 {
assert!(
trades_with_price * 100 >= trades * 90,
"expected >=90% of trades to carry a tradePrice, got {trades_with_price}/{trades}"
);
assert!(
trades_with_ib_order_id > 0,
"expected ibOrderID to populate on at least some real trades"
);
}
}
#[test]
fn real_lending_and_income_fields_populate() {
let Ok(path) = std::env::var("IB_FLEX_REAL_XML") else {
eprintln!("skipping: set IB_FLEX_REAL_XML to a real Activity FLEX file to run");
return;
};
let xml = std::fs::read_to_string(&path).expect("read real XML file");
let statements = parse_activity_flex_all(&xml).expect("parse");
let mut slb_fee_rows = 0usize;
let mut sum_net_lend_fee = Decimal::ZERO;
let mut sum_gross_lend_fee = Decimal::ZERO;
let mut sum_lending_income = Decimal::ZERO;
let mut managed_loans = 0usize;
let mut lod_resolved = 0usize; let mut lod_unknown = 0usize;
let mut pil_count = 0usize; let mut withholding_count = 0usize;
let mut div_accruals = 0usize;
let mut div_accruals_with_action_id = 0usize;
for s in &statements {
for f in &s.slb_fees.items {
slb_fee_rows += 1;
sum_net_lend_fee += f.net_lend_fee.unwrap_or_default();
sum_gross_lend_fee += f.gross_lend_fee.unwrap_or_default();
sum_lending_income += f.lending_income().unwrap_or_default();
if f.slb_type.as_deref() == Some("ManagedLoan") {
managed_loans += 1;
}
}
for t in &s.trades.items {
match t.level_of_detail {
Some(LevelOfDetail::Unknown) => lod_unknown += 1,
Some(_) => lod_resolved += 1,
None => {}
}
}
for c in &s.cash_transactions.items {
match c.transaction_type_kind() {
Some(CashTransactionType::PaymentInLieuOfDividends) => pil_count += 1,
Some(CashTransactionType::WithholdingTax) => withholding_count += 1,
_ => {}
}
}
for d in &s.open_dividend_accruals.items {
div_accruals += 1;
if d.action_id.is_some() {
div_accruals_with_action_id += 1;
}
}
}
eprintln!(
"lending/income smoke: {slb_fee_rows} SLBFee rows ({managed_loans} ManagedLoan) | \
Σnet_lend_fee={sum_net_lend_fee} Σgross_lend_fee={sum_gross_lend_fee} \
Σlending_income={sum_lending_income} | LOD resolved={lod_resolved} unknown={lod_unknown} | \
PIL={pil_count} withholding={withholding_count} | \
divAccruals={div_accruals} w/actionID={div_accruals_with_action_id}"
);
if slb_fee_rows > 0 {
assert!(
sum_net_lend_fee != Decimal::ZERO,
"expected non-zero Σnet_lend_fee once lending economics are captured"
);
assert!(
sum_lending_income != Decimal::ZERO,
"SLBFee::lending_income() should surface managed-loan income"
);
}
if lod_resolved + lod_unknown > 0 {
assert!(
lod_resolved > lod_unknown,
"expected most trade level_of_detail values to resolve, got \
{lod_resolved} resolved vs {lod_unknown} unknown"
);
}
if div_accruals > 0 {
assert!(
div_accruals_with_action_id > 0,
"expected OpenDividendAccrual.action_id to populate on real data"
);
}
}