Summary
After the v0.4.5 fix for #142, req_pnl() no longer leaks notional values into daily_pnl, but introduces a silent regression: the pnl() callback never fires for positions opened intraday.
Repro
Probe script: connect to paper account (flat at start), req_account_updates(True) + req_pnl(), place a 1-share marketable BUY, observe callbacks for 180s, place SELL to flatten.
Probe results (paper, US pre-market 2026-05-08, account/IDs redacted):
[ 11.633s] req_account_updates(True) + req_pnl(...)
[ 11.655s] update_account_value DailyPnL=0.00 (one push, never updates again)
[ 11.984s] pnl() daily=+0.00 unrealized=+0.00 realized=+~$X
[ 41.967s] BUY 1 SPY @ 735.00 filled
-- 180s observation window --
-- ZERO pnl() callbacks fired during this window --
[222.199s] SELL @ 735.07 filled (flat)
Key data points:
pnl() fires once at startup (computed from prior-session state via the midnight-seed reconstruction)
- During 180s post-fill on a fresh intraday position,
pnl() never fires
update_account_value("DailyPnL", ...) also pushes only once with stale 0.00, despite NetLiquidation updating multiple times
Diagnosis
src/client_core.rs:898-956 (poll_pnl) iterates only over midnight seeds:
let seeds = shared.portfolio.midnight_seeds();
if seeds.is_empty() { return None; }
for seed in &seeds { ... }
Positions opened intraday have no entry in the midnight-seed map, so they're excluded from the computation entirely. For a flat-at-midnight account, the loop produces stale totals that never change → cached last_pnl suppresses any callback.
Bug class progression
| Version |
Symptom for intraday-opened position |
| v0.4.4 |
daily_pnl accumulates trade notional per fill (the original report) |
| v0.4.5+ |
pnl() callback never fires at all |
The v0.4.5 fix swapped a loud bug for a quiet one. Both stem from the same root cause: client-side reconstruction from midnight seeds cannot represent positions opened today.
Open architectural question
Whether the upstream client achieves this via:
- A different subscription that streams server-computed daily P&L live, or
- The same midnight-seed message but with intraday updates after fills
is unknown without protocol capture. A capture request has been filed at deepentropy/ib-agent.
A purely-local fix (extend poll_pnl to handle no-seed positions by synthesizing money_traded = qty_now × avg_cost, which collapses to unrealized P&L for fresh positions) is mathematically correct but patches the architecture rather than aligning with upstream behavior. Holding the PR pending capture data.
Probe script
scripts/.tmp/probe_pnl_142.py (not committed; uses .venv\Scripts\python.exe directly).
Related
Summary
After the v0.4.5 fix for #142,
req_pnl()no longer leaks notional values intodaily_pnl, but introduces a silent regression: thepnl()callback never fires for positions opened intraday.Repro
Probe script: connect to paper account (flat at start),
req_account_updates(True)+req_pnl(), place a 1-share marketable BUY, observe callbacks for 180s, place SELL to flatten.Probe results (paper, US pre-market 2026-05-08, account/IDs redacted):
Key data points:
pnl()fires once at startup (computed from prior-session state via the midnight-seed reconstruction)pnl()never firesupdate_account_value("DailyPnL", ...)also pushes only once with stale0.00, despiteNetLiquidationupdating multiple timesDiagnosis
src/client_core.rs:898-956(poll_pnl) iterates only over midnight seeds:Positions opened intraday have no entry in the midnight-seed map, so they're excluded from the computation entirely. For a flat-at-midnight account, the loop produces stale totals that never change → cached
last_pnlsuppresses any callback.Bug class progression
daily_pnlaccumulates trade notional per fill (the original report)pnl()callback never fires at allThe v0.4.5 fix swapped a loud bug for a quiet one. Both stem from the same root cause: client-side reconstruction from midnight seeds cannot represent positions opened today.
Open architectural question
Whether the upstream client achieves this via:
is unknown without protocol capture. A capture request has been filed at
deepentropy/ib-agent.A purely-local fix (extend
poll_pnlto handle no-seed positions by synthesizingmoney_traded = qty_now × avg_cost, which collapses to unrealized P&L for fresh positions) is mathematically correct but patches the architecture rather than aligning with upstream behavior. Holding the PR pending capture data.Probe script
scripts/.tmp/probe_pnl_142.py(not committed; uses.venv\Scripts\python.exedirectly).Related