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