Commit Graph

8 Commits

Author SHA1 Message Date
6c147cc850 gitignore notes/ (personal tax profile); README pointer 2026-09-01 12:42:25 -04:00
19b33d5a18 Add NIIT + state/local rates to the after-tax model (NYC support)
NYC residents pay, on a capital-gain distribution, roughly 20% federal
+ 3.8% NIIT + 6.85-9.65% NY + 3.876% NYC = ~34-37% — the old model's
flat 20% understated the real after-tax drag of high-distribution
funds for this user by ~15 points on exactly the flows that matter.

tax.py: new niit + sl_rate params (decimals). sl_rate is the
state+local marginal rate applied at ORDINARY rates to every flow —
state and local have NO preferential cap-gain rate, so the composite
is lt_rate+niit+sl_rate on cap-gain dists, div_rate+niit+sl_rate on
dividends, and (st/lt_rate)+niit+sl_rate on realized gains.

app.py: two new sidebar fields (persisted in settings.json), wired
through _compute_portfolio's cache key.

tests/test_tax.py: 3 new cases (capg and div composite rates,
net-taxed realized ST gains/losses at st+NIIT+SL).

README: NYC rate note with the 2025 IT-201 schedule values (NYC 3.876%
over $50k; NY 6.85% at $215,400-$1.077M, 9.65% at $1.077M-$5M,
single filer) and the composite example.
2026-09-01 09:37:58 -04:00
bb8e39605e Event-history protection: backup dir, empty-download fallback, docs
Yahoo's 2026 event-feed change (capitalGain events dropped for some
funds; no events at all for terminated tickers) can wipe good event
history on re-download. Defense in depth:

- overrides/event-backup/: last-known-good copy of every dividend/
  capitalGain file (8,192 files, 58 MB); refresh with
  scripts/backup_events.py after each dump update
- data.py event_file(): frozen > data-root (while populated) > backup;
  used by panel reads, verify_official, and the double-listing scanner
- README: capital-gain files are mutual-fund-only in this dump; Yahoo
  has no LT/ST split (tax.py taxes capg at lt_rate; the per-fund split
  would come from fund-company annual tax statements or commercial feeds)
- tests: exact Timestamp .loc keys (pandas 3.x string matching returns a
  Series on large DatetimeIndex)
2026-08-31 21:59:25 -04:00
e20b2e30cb Double-listing: layout-agnostic invariants, whole-dump sweep, bulk corrections
Yahoo changed the shape of its event feed between downloads: a 2026-08
re-download of CVIX/JLPSX shows it no longer returns capitalGain events at
all (the dividend stream still carries the year-end rows), while stale
tickers now return no events at all. File-specific remove ops therefore
break silently on the next re-download, so the double-listing fix is now
expressed as layout-agnostic invariants applied at bundle assembly
(idempotent, hold for full and incremental builds):

  dedup: [[date, amount]]      keep at most one copy of a same-date/
      same-amount cross-file pair (the capitalGain copy when both present)
  drop_capg_copy: [date]       the capitalGain row on that date is the
      spurious copy of the dividend row

- data.py: apply_invariants() at bundle assembly + pure dedupe_event_rows()
  shared with verify_official and tests (8 new test cases, 22 passing)
- JLPSX/CVSIX corrections rewritten with the invariants (2019-08-08 now
  keeps the dividend amount per the verified same-date pattern)
- scripts/scan_double_listing.py: whole-dump sweep -> reports/double_listing/
  6,421 symbols scanned: 1,218 with same-amount pairs (6,589), 1,631 with
  differing-amount pairs (12,366, reported only - not distinguishable from
  legitimate same-day div+capg without per-fund official data), 83 with
  repeated within-file rows (ingest keep-last already collapses them)
- scripts/apply_dedup_corrections.py: bulk 'dedup' corrections for the
  1,218 same-amount symbols (1,212 new files; the 6 verified funds keep
  their explicit, official-verified corrections)
- scripts/check_corrections.py: integrity check for every correction op
  against the actual (frozen) files - caught a mis-filed CVSIX entry
2026-08-31 21:00:36 -04:00
08bc63a2ef README: update the remaining data gaps after the double-listing corrections 2026-08-31 19:50:52 -04:00
1f5720d7db verify_official: 497/497K forms, per-share layout, candidate re-ranking
- EFTS queries now include 497/497K (many fund families publish their
  per-fund highlights there, not in the consolidated N-CSR) and re-rank
  hits by registrant name match (ticker/brand words), newest first,
  capped at 2 filings per CIK
- new parse_per_share_blocks for the JPMorgan-style 'Per share operating
  performance' table (per-class value blocks; dashes = zero)
- parse_highlights now tolerates row labels split across table cells
  (modernized N-CSRS format, e.g. Calamos 2026)
- region finders: word-flexible name patterns (US vs U.S., class letters),
  self-validating per-share regions (a candidate block must match the
  local series, so a name mention in notes doesn't attribute another
  fund's tables in a combined 58 MB report)
- main() keeps the best result across candidate filings (N-CSRS vs 497
  can round differently) and stops early on 'ok'
- local_series applies the corrections overlay so corrected funds verify
  against their filing

Results: JLPSX and CVSIX now 'ok' (all bounded fiscal years agree with
the official filings); CVSIX also gets a 2023-12-21 0.510 capital-gain
correction. bnd/pmaix still ok (no regression).
2026-08-31 18:10:39 -04:00
cba7291676 data: manifest-based incremental refresh of the parquet cache
The cache now tracks every file in the data dir (mtime_ns + size) in
.cache/manifest.json. On load, a directory scan is compared against the
manifest:
  - changed/added files are re-read and merged into the parquet panels
    (one read + one concat + one write per touched panel; new values
    win where present, old values kept where the new file is short)
  - removed files drop their symbols (and names)
  - an up-to-date cache is a ~30 ms memo hit

Measured on the real 4k-symbol set: full build 54 s, refresh of
5 modified + 1 added + 1 removed files 3.4 s. No scan TTL (a scan is
a few ms); a previous 5 s scan cache masked data updates.

Tests: tests/test_data.py (11 checks) added as step 1 of run_tests.sh.
2026-08-24 17:28:20 -04:00
d8703a7a63 Stock & Portfolio Analyzer: full UI rework
- single spec grammar for symbol and benchmark fields: commas join one
  portfolio (MSFT:0.6,V:0.4), spaces separate distinct symbols/portfolios;
  both fields accept one or many entries
- benchmarks simulated with the same scheme/cost/tax rules; per-benchmark
  beta/alpha columns; after-tax benchmark curves
- global Curve mode (pre/after/both) above the tabs; clean names in
  single-curve mode
- live updates: field commits on Enter/blur, page recomputes per rerun;
  portfolio+tax sims cached (st.cache_data); plotly.js from CDN (4.6MB ->
  browser-cached) with F_INLINE_PLOTLY=1 offline fallback
- chart: legend underneath, solid lines, pan sticks to data edges
  (width-preserving), zoom edge-clamped
- inputs persist in settings.json across reloads/restarts/devices
- tests: tests/test_app.py (AppTest) + tests/test_e2e_browser.py
  (Playwright) via ./run_tests.sh
2026-08-24 16:05:27 -04:00