CEF app tab: full 239-fund verified tax-arb ranking

This commit is contained in:
Greg Pomerantz 2026-08-28 16:04:59 -04:00
parent 857dfce19f
commit 6f6d6b8047

57
app.py
View File

@ -1110,51 +1110,36 @@ with tab_fundlab:
"table is a rough keyword classification. N-PORT holdings are " "table is a rough keyword classification. N-PORT holdings are "
"quarterly and up to ~60 days stale.") "quarterly and up to ~60 days stale.")
# --- closed-end funds (tax-arb shortlist) --------------------------- # --- closed-end funds (tax-arb, actual distribution character) ------
import pandas as _pd import pandas as _pd
_fl_dir = Path(__file__).parent / "fundlab" _fl_dir = Path(__file__).parent / "fundlab"
_char_path = _fl_dir / "cef_character.json" _rank_path = _fl_dir / "cef_rank_all.json"
if _char_path.exists(): if _rank_path.exists():
_char = json.loads(_char_path.read_text()) _rank = json.loads(_rank_path.read_text())
_annual = {} _univ = json.loads((_fl_dir / "cef_universe.json").read_text())
_ann_path = _fl_dir / "cef_annual.json"
if _ann_path.exists():
_annual = json.loads(_ann_path.read_text())
_rows = [] _rows = []
for _s, _c in _char.items(): for _score, _s, _ch, _t5, _vol, _npos, _tend, _disc in _rank:
_a = _annual.get(_s.upper(), {})
_ch = _c.get("character")
_actual = False
if _a.get("char_actual") is not None:
_ch = _a["char_actual"]
_actual = True
_t5, _vol = _c.get("t5"), _c.get("vol5")
_score = (None if _ch is None or _t5 is None or _vol is None
else round(_ch * (_t5 + 0.4 * _vol), 4))
_disc = _a.get("disc_now_approx")
_rows.append({ _rows.append({
"sym": _s.upper(), "sym": _s,
"name": (_a.get("name") or _c.get("name") or "")[:44], "name": (_univ.get(_s, {}).get("name") or "")[:44],
"char": _ch, "char": _ch,
"actual": _actual,
"disc%": None if _disc is None else round(100 * _disc, 1), "disc%": None if _disc is None else round(100 * _disc, 1),
"t5%": None if _t5 is None else round(100 * _t5, 1), "t5%": None if _t5 is None else round(100 * _t5, 1),
"vol5%": None if _vol is None else round(100 * _vol, 1), "vol5%": None if _vol is None else round(100 * _vol, 1),
"pos_scen": _c.get("n_pos_scen"), "pos_scen": _npos,
"tenders": _a.get("n_tender"), "tenders": _tend,
"score": _score}) "score": _score})
_cef_df = _pd.DataFrame(_rows) _cef_df = _pd.DataFrame(_rows)
st.divider() st.divider()
st.subheader("Closed-end funds — tax-arb shortlist") st.subheader("Closed-end funds — tax-arb ranking")
st.dataframe( st.dataframe(_cef_df, width="stretch", height=460)
_cef_df.sort_values("score", ascending=False, na_position="last"),
width="stretch", height=460)
st.caption( st.caption(
"char = distribution character 01 (0 = all ordinary income, 1 = " f"{len(_cef_df)} CEFs with VERIFIED per-share distribution "
"all capital-gains/ROC). actual = verified from the fund's own " "character (divs + gains + ROC == total distributions per year, "
"per-share financial highlights (divs + gains + ROC == total " "plus the NAV chain, from the fund's own report). char 01: "
"distributions per year, plus the full NAV chain); otherwise the " "0 = all ordinary income, 1 = all capital-gains/ROC (taxable "
"return-sleeve model. disc% = market vs NAV. pos_scen = severe-" "income that defers to the LTCG/ROC rate in a taxable account). "
"drawdown scenarios with a positive 5y return. score = char × " "score = char × (5y total return + 0.4 × 5y vol): harvestable "
"(5y total return + 0.4 × 5y vol) — harvestable character " "character weighted by how much damage a forced sale could do. "
"weighted by damage potential.") "tenders = N-23C repurchase/tender filings (sponsor capital-"
"management pressure). disc% = FY-end market vs NAV.")