Commit Graph

6 Commits

Author SHA1 Message Date
d2ff607c79 On-demand per-fund reports for standalone Symbol-box funds
Any symbol that stands alone in the Symbol box (not a comma-joined
portfolio component) now gets a full report, built on demand and cached
in reports/report_data_adhoc.json. Rendered at the top of the Fund Lab
Summary as group 'A'; pointer at the top of the page. Rendering logic
extracted into render_fund_report() shared by pre-built and on-demand
entries. ~0.3-0.9 s per new fund after one-time panel warmup; instant
afterwards (memory + disk cache).
2026-08-30 18:35:42 -04:00
a09861f39f N-PORT cross-check: verify top candidates' actual holdings
fundlab/xcheck.py - for each screen candidate, resolve the fund's OWN
registrant CIK (browse-edgar; the 497-cover CIK is the family/trust),
get the exact series name for the ticker (the only reliable
disambiguator between sibling funds), walk the 4 most recent NPORT-P
filing dates, and parse holdings from the interactive NPORT XML
(primary_doc.xml at the accession root - NOT the XSL-rendered view the
submissions API points at). Exact seriesName match > best htm exhibit
parse. Buckets from the authoritative assetCat+issuerCat codes (ABS-O,
ABS-CBDO, DBT+UST/CORP/MUN/NUSS, LON, STIV, RA, EC+RF=fund, ...), not
position-name keywords. Resumable; raw filings cached under
nport_cache/raw/ (gitignored).

nport.py - _SECTION gains the "INVESTMENT PORTFOLIO (unaudited)"
variant (NPORT-EX Sch-F files); find_section/build gain a frac
token-tolerance param (Yahoo names drift from filing names); CMBS/ABS
bucket gains CLO/CBDO/DAC terms.

app Fund Lab - "N-PORT cross-check" expander: per-candidate table
(as-of, n, t5, top code-bucket, #1 position) + per-fund holdings
detail.

RESEARCH.md - cross-check verdicts. 21/22 resolved (qcmmrx is an MMF,
no holdings). The screen's top names are REAL:
- hmezx/mervx = genuine merger arb (equity in deal targets + escrow)
- egrix = 100% wrapper in one macro managed portfolio (underlying not
  NPORT-disclosed); etsix = fund of EV internal multi-strat accounts
- wmnux = discounted/zero-coupon corporate bonds + equity swaps (the
  "equity names" are bond issuers/swap underlyings)
- scfzx/rctix/aflix = securitized credit/CLO/distressed/levered loans
- hicox/fhmix/usmsx/btmix (munis), aguax/femdx (EM sovereign), anglx
  (agency MBS), lpxax (rotated out of prefs into bank/financial debt)
  = genuine missing-factor exposures the 35-sleeve model lacks
- fhcox/dultx/safex = short-duration carry (a short-duration sleeve
  would explain them)

tests/test_fundlab.py - test_xcheck (14 checks): parse_interactive,
code buckets, name-match normalization, series-name disambiguation.
Also: untrack fundlab/streamlit.log; gitignore raw/ + xcheck_run.log.

84 fundlab / 32 app / 14 data tests pass.
2026-08-27 12:43:17 -04:00
592d12958f Find candidate funds NOT in the DB: exhaustive EDGAR 497-universe pass
fundlab/edgar_universe.py - the 'search' for funds we don't have:
  1. SEC full-index (Archives/edgar/full-index/YYYY/QTRn/company.gz)
     lists every filing; CIKs that filed a base 497/497K in the past 4
     quarters = every currently-active US open-end fund (1,668).
  2. one small fetch per CIK: the full-submission .txt carries the
     line-based SGML prospectus cover (<SERIES-NAME> ... unclosed
     <CLASS-CONTRACT-TICKER-SYMBOL> tags) - fund name + every class
     ticker, often several funds per filing.
  3. alpha-leaning name filter (expanded dbmine PATTERN: +relative
     value, risk allocation, dynamic global, real return, hedged),
     drop local-DB + shortlist tickers,
  4. Yahoo chart verify: instrumentType MUTUALFUND (OTC open-end;
     exchange name is useless - OTC funds report 'Nasdaq'),
     >=5y daily history,
  5. share-class dedupe (longest history), goget download, same
     screen_fund engine.
  Resumable (per-CIK covers cache), 4-thread, Range-free small files.

First pass results (46 funds screened, 5 NEW candidates):
  egrix/ecgmx Eaton Vance Global Macro Absolute Return: R2 0.07,
    +7.9%/+4.8% 5y alpha, t 4.9/4.6, corr-port 0.22 - pure macro idio
  dmszx Destinations Multi-Strategy Alternatives: R2 0.57, +3.3%, t3.5
  cbhax Victory Market Neutral Income: R2 0.07, +4.6%, t2.9, corr 0.11
  pdinx Putnam Diversified Income: semi-alpha (full t5.8, 62% 6m+)
  (+ wmnux/gioax = 2nd share classes of already-known candidates)
  vmnix Vanguard MN: alpha but corr 0.35 (portfolio already 50% MN)

app Fund Lab alpha table now also reads search_external.json.
tests: parse_cover unit tests (unclosed-tag SGML, ticker series
attach, malformed rejected). 65/65 fundlab, 32/32 app.
2026-08-26 15:22:52 -04:00
54d26939dc Fund Lab: N-PORT holdings page for the 16-fund shortlist
- fundlab/nport.py: parse the fund's own category/percentage lines,
  as-of date, net assets and dollar-valued positions from N-PORT
  schedules of investments (handles per-fund and combined multi-fund
  family filings; conservative keyword bucketing of positions)
- fundlab/nport_cache/<sym>.json: parsed snapshots for 15 of 16 funds
  (raw SOI HTML kept locally, gitignored; source URLs + filing dates in
  nport_manifest.json, md5-verified against EDGAR)
- atesx: no current SOI found (Anchor's recent filings cover the Income
  fund) - listed with an honest note
- app.py: new 'Fund Lab' tab - pick a fund, see objective, reported
  composition (bar + table), rough keyword buckets, top positions, and
  the prospectus strategy excerpt
- tests: parser unit tests (section finding, category regex, buckets)
2026-08-26 09:59:24 -04:00
4f36bc7aea app: background cache refresh, per-benchmark stats, correlation tab, global date range
- data.py: non-blocking load_bundle(); background watcher thread refreshes
  the parquet cache (5s scan, 30s min rebuild cadence); refresh()/
  up_to_date()/generation()
- statistics tab: one table per benchmark (vs <label>), plain column names
  (beta/alpha/return/vol...), selectable+reorderable stat list in
  settings.json
- correlation tab: per-portfolio components-vs-benchmarks +
  all-portfolios-vs-benchmarks; numbered columns
- global date range (window radio + start/end boxes) applied to all tabs;
  metrics.xcorr(); equity window radio gains YTD/3M/1M
2026-08-25 18:15:47 -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