Download tests/test_recommend.py from s-ttp/vitals: direct link, hf CLI and curl.
- Browser
- Download file 22.2 kB
-
https://huggingface.co/spaces/s-ttp/vitals/resolve/main/tests/test_recommend.py
- Command line
-
hf download hf://spaces/s-ttp/vitals/tests/test_recommend.py
-
curl -L -o test_recommend.py https://huggingface.co/spaces/s-ttp/vitals/resolve/main/tests/test_recommend.py
22.2 kB
| """Portfolio-aware recommendation engine — needs diagnosis and marginal-fit scoring.""" | |
| import numpy as np | |
| import pandas as pd | |
| from src import metrics as M | |
| def _pm(**kw): | |
| m = {"sector_allocation": {"Technology": 0.7, "Consumer Cyclical": 0.3}, "effective_n": 2.5, | |
| "realized_beta": 1.6, "n_holdings": 3} | |
| m.update(kw) | |
| return m | |
| def test_diagnose_needs_reads_the_book(): | |
| risk = {"stress": [{"name": "COVID crash", "window": "2020-02-19 → 2020-03-23", "portfolio": -0.40}], | |
| "factor_tilts": {"tilts": {"value": -0.3, "quality": 0.2}}} | |
| hs = [{"weight": 0.5, "dcf": {"upside": -0.4}}, {"weight": 0.5, "dcf": {"upside": -0.3}}] | |
| needs = M.diagnose_needs(_pm(), risk, hs, {"loss_tolerance": "20%", "objective": "income"}) | |
| assert "Healthcare" in needs["sector_gaps"]["sectors"] | |
| assert needs["sector_overweight"]["sector"] == "Technology" | |
| assert needs["diversifier"]["weight"] > 0.5 | |
| assert needs["defensive"]["weight"] == 1.0 and "breaches" in needs["defensive"]["label"] | |
| assert "income" in needs and "value" in needs | |
| def test_diagnose_needs_healthy_book_falls_back_to_quality(): | |
| pm = _pm(sector_allocation={s: 1 / 11 for s in M.ALL_SECTORS}, effective_n=12, realized_beta=1.0) | |
| needs = M.diagnose_needs(pm, {}, [], None) | |
| assert list(needs) == ["quality"] | |
| def _hist_pair(corr_target: float, n=700, seed=3): | |
| rng = np.random.default_rng(seed) | |
| idx = pd.bdate_range(end="2026-09-01", periods=n) | |
| base = rng.normal(0, 0.012, n) | |
| other = corr_target * base + np.sqrt(1 - corr_target ** 2) * rng.normal(0, 0.012, n) | |
| px = lambda r: 100 * np.cumprod(1 + r) | |
| return pd.DataFrame({"AAA": px(base), "BBB": px(base * 0.9), "CAND": px(other)}, index=idx) | |
| def test_candidate_fit_prefers_diversifier_that_fills_gap(): | |
| hist = _hist_pair(0.1) | |
| hs = [{"symbol": "AAA", "weight": 0.6, "data": {}}, {"symbol": "BBB", "weight": 0.4, "data": {}}] | |
| pm = _pm(effective_n=1.9, realized_beta=1.5, sector_allocation={"Technology": 1.0}) | |
| needs = M.diagnose_needs(pm, {}, [], {"loss_tolerance": "20%"}) | |
| port = M.portfolio_returns(hs, hist) | |
| good = {"ticker": "CAND", "sector": "Healthcare", "rank_pct": 80, "signals": [], | |
| "live": {"beta": 0.7, "div_yield": 1.0}, "upside": 0.1} | |
| bad = {"ticker": "CAND", "sector": "Technology", "rank_pct": 95, "signals": [], | |
| "live": {"beta": 1.8, "div_yield": 0.0}, "upside": 0.1} | |
| fg = M.candidate_fit(good, hs, pm, {}, needs, port, hist) | |
| fb = M.candidate_fit(bad, hs, pm, {}, needs, port, hist) | |
| assert fg["score"] > fb["score"] | |
| assert any("adds Healthcare" in f for f in fg["fixes"]) | |
| assert any("correlation" in f for f in fg["fixes"]) and fg["corr_to_book"] < 0.5 | |
| assert fg["deltas"]["effective_n"][1] > fg["deltas"]["effective_n"][0] | |
| assert any("beta" in f for f in fg["fixes"]) | |
| def test_conservative_income_prefers_payer_over_high_beta_diversifier(): | |
| hist = _hist_pair(-0.1) | |
| hs = [{"symbol": "AAA", "weight": 0.5, "data": {}}, {"symbol": "BBB", "weight": 0.5, "data": {}}] | |
| pm = _pm(effective_n=2.0, realized_beta=0.1, sector_allocation={"Real Estate": 1.0}) | |
| needs = M.diagnose_needs(pm, {}, [], {"risk": "conservative", "objective": "income"}) | |
| port = M.portfolio_returns(hs, hist) | |
| tech = {"ticker": "CAND", "sector": "Technology", "rank_pct": 80, "signals": [], | |
| "live": {"beta": 1.3, "div_yield": 1.0}, "upside": 0.1} | |
| payer = {"ticker": "CAND", "sector": "Consumer Defensive", "rank_pct": 80, "signals": [], | |
| "live": {"beta": 0.5, "div_yield": 6.0}, "upside": 0.1} | |
| assert M.candidate_fit(payer, hs, pm, {}, needs, port, hist)["score"] > \ | |
| M.candidate_fit(tech, hs, pm, {}, needs, port, hist)["score"] | |
| def test_switch_pairs_avoid_overweight_sector(): | |
| from src.revival import switch_pairs | |
| hs = [{"symbol": "PLTR", "weight": 0.4, "data": {"sector": "Technology", "price": 1.0}}] | |
| dec = {"PLTR": {"action": "Trim", "conviction": 38}} | |
| cands = [{"ticker": "TSM", "sector": "Technology", "trend_ok": True, "live": {}}, | |
| {"ticker": "ED", "sector": "Utilities", "trend_ok": True, "live": {}}] | |
| assert switch_pairs(hs, dec, cands)[0]["in"] == "TSM" | |
| assert switch_pairs(hs, dec, cands, {"sector_overweight": {"sector": "Technology"}})[0]["in"] == "ED" | |
| def test_peak_cycle_and_smart_money_guards(): | |
| hs = [{"symbol": "AAA", "weight": 1.0, "data": {}}] | |
| pm = _pm(sector_allocation={"Technology": 1.0}, effective_n=1.0) | |
| needs = M.diagnose_needs(pm, {}, [], None) | |
| clean = {"ticker": "X", "sector": "Industrials", "rank_pct": 94, "signals": [], | |
| "live": {"eps_growth": 0.08, "sma200": 100.0, "price": 104.0, "beta": 1.0}, "upside": 0.0} | |
| expd = {"ticker": "X", "sector": "Industrials", "rank_pct": 94, "signals": [], "smart_money_neg": True, | |
| "live": {"eps_growth": 0.51, "sma200": 158.0, "price": 186.0, "beta": 1.0}, "upside": 0.0} | |
| fc = M.candidate_fit(clean, hs, pm, {}, needs, None, None) | |
| fe = M.candidate_fit(expd, hs, pm, {}, needs, None, None) | |
| assert fe["score"] < fc["score"] - 0.9 | |
| assert any("peak-cycle" in c for c in fe["cautions"]) and any("informed money" in c for c in fe["cautions"]) | |
| assert fc["cautions"] == [] | |
| def test_strong_ok_requires_clean_committee_verdict(): | |
| from src.revival import strong_ok | |
| assert strong_ok({"committee": {"conviction": 64}, "fit": {"cautions": ["peak-cycle"]}}) # flagged: ≥62 | |
| assert not strong_ok({"committee": {"conviction": 60}, "fit": {"cautions": ["peak-cycle"]}}) | |
| assert strong_ok({"committee": {"conviction": 56}, "fit": {"cautions": []}}) # clean: ≥55 | |
| assert not strong_ok({"committee": {"conviction": 52}, "fit": {"cautions": []}}) | |
| assert not strong_ok({"committee": {"conviction": 45}, "fit": {}}) | |
| assert not strong_ok({"committee": {"conviction": 70}, "fit": {}, "trend_ok": False}) # broken trend: never | |
| assert strong_ok({"fit": {"cautions": []}}) and not strong_ok({"fit": {"cautions": ["extended"]}}) | |
| def test_candidate_fit_flags_penalized(): | |
| hs = [{"symbol": "AAA", "weight": 1.0, "data": {}}] | |
| c = {"ticker": "X", "sector": "Energy", "rank_pct": 99, "signals": [], "live": {}, "flags": ["VALUE_TRAP"]} | |
| assert M.candidate_fit(c, hs, _pm(), {}, {"quality": {"weight": 0.5}}, None, None)["score"] < 0 | |
| def test_exclusions_short_tokens_match_whole_words(monkeypatch): | |
| from src import orchestrator as O | |
| monkeypatch.setattr(O, "fetch_holding", lambda t: {"name": {"CL": "Colgate-Palmolive", "MO": "Altria Group"}.get(t, t), "industry": ""}) | |
| cands = [{"ticker": "CL", "sector": "Consumer Defensive"}, {"ticker": "MO", "sector": "Consumer Defensive"}, | |
| {"ticker": "PM", "sector": "Consumer Defensive"}] | |
| kept, rej = O._apply_exclusions(cands, {"exclusions": "tobacco, MO"}) | |
| assert [c["ticker"] for c in kept] == ["CL", "PM"] and rej[0]["ticker"] == "MO" | |
| def test_instrument_gets_need_credit_and_measured_beta(): | |
| hist = _hist_pair(0.0) | |
| hist["SPY"] = hist["AAA"] # SPY proxy = the book's driver | |
| hs = [{"symbol": "AAA", "weight": 1.0, "data": {}}] | |
| pm = _pm(effective_n=1.0, realized_beta=1.5, sector_allocation={"Technology": 1.0}) | |
| needs = M.diagnose_needs(pm, {}, [], {"loss_tolerance": "20%"}) | |
| port = M.portfolio_returns(hs, hist) | |
| shy = {"ticker": "CAND", "sector": "Diversified fund", "instrument": True, "need_target": "defensive", | |
| "signals": [], "live": {}} | |
| f = M.candidate_fit(shy, hs, pm, {}, needs, port, hist) | |
| assert any("ballast" in x for x in f["fixes"]) and isinstance(shy["live"].get("beta"), float) | |
| assert f["score"] > 0.5 | |
| def test_european_benchmark_and_tax_note(): | |
| from src.market_data import benchmark_for | |
| assert benchmark_for([{"symbol": "VOD.L", "weight": .4}, {"symbol": "SAP.DE", "weight": .3}, {"symbol": "MC.PA", "weight": .3}]) == "^STOXX" | |
| assert benchmark_for([{"symbol": "VOD.L", "weight": .7}, {"symbol": "SAP.DE", "weight": .3}]) == "^FTSE" | |
| assert benchmark_for([{"symbol": "AAPL", "weight": .5}, {"symbol": "SAP.DE", "weight": .5}]) == "ACWI" | |
| h = {"symbol": "VOD.L", "price": 130, "entry": 100, "qty": 100, "pl_pct": 0.3, "data": {"currency": "GBp"}, "bought": "2026-08-01"} | |
| note = M.tax_note(h, "Trim") or "" | |
| assert "jurisdiction" in note and "SHORT-TERM" not in note | |
| def test_europanel_scoring_is_cross_sectional(): | |
| import pandas as pd | |
| from src import europanel as EP | |
| df = pd.DataFrame({"pe": [10, 20, 40], "pb": [1, 2, 4], "ev_ebitda": [6, 10, 20], "div_yield": [5, 3, 1], | |
| "roe": [0.2, 0.1, 0.05], "roa": [0.1, 0.05, 0.02], "op_margin": [0.3, 0.2, 0.1], "de": [0.2, 0.5, 1.0], | |
| "mom_12_1": [0.1, 0.2, 0.3], "ret_26w": [0.05, 0.1, 0.2], "ret_13w": [0.02, 0.05, 0.1], | |
| "vol_13w": [0.15, 0.25, 0.4], "dist_52w_high": [-0.05, -0.1, -0.2]}, index=["CHEAP.L", "MID.DE", "DEAR.PA"]) | |
| sc = EP._score(df) | |
| assert sc.loc["CHEAP.L", "n_earnings_yield"] > sc.loc["DEAR.PA", "n_earnings_yield"] | |
| assert sc.loc["CHEAP.L", "pctile"] > sc.loc["DEAR.PA", "pctile"] | |
| assert -1 <= sc["n_mom_12_1"].min() and sc["n_mom_12_1"].max() <= 1 | |
| def test_uk_stamp_duty_in_order_plan(): | |
| from src import revival as R | |
| hs = [{"symbol": "VOD.L", "weight": 1.0, "market_value": 10000.0, "price": 1.0, "fx_usd": 1.0, | |
| "data": {"avg_volume": 1e9, "quote_type": "EQUITY"}}] | |
| target = [{"ticker": "VOD.L", "current_w": 1.0, "target_w": 0.6, "delta": -0.4, "is_new": False}, | |
| {"ticker": "AZN.L", "current_w": 0.0, "target_w": 0.4, "delta": 0.4, "is_new": True}] | |
| R.order_plan.quote_types = {"VOD.L": "EQUITY", "AZN.L": "EQUITY"} | |
| orders = {o["ticker"]: o for o in R.order_plan(hs, target)} | |
| assert orders["AZN.L"]["cost_note"] == "UK stamp duty 0.5%" and orders["AZN.L"]["est_cost_usd"] == 4 + 20 | |
| assert "cost_note" not in orders["VOD.L"] # sells carry no duty | |
| def test_europanel_resolves_home_market_primary_lines(monkeypatch): | |
| from src import europanel as EP | |
| monkeypatch.setattr(EP, "av_symbol_search", lambda q: [ | |
| {"av": "SAP", "yahoo": "SAP", "name": "Sap SE", "region": "United States", "currency": "USD", "type": "Equity"}, | |
| {"av": "SAP.FRK", "yahoo": "SAP.F", "name": "SAP SE", "region": "Frankfurt", "currency": "EUR", "type": "Equity"}, | |
| {"av": "0NW4.LON", "yahoo": "0NW4.L", "name": "SAP SE", "region": "United Kingdom", "currency": "EUR", "type": "Equity"}, | |
| {"av": "SAP.DEX", "yahoo": "SAP.DE", "name": "SAP SE", "region": "XETRA", "currency": "EUR", "type": "Equity"}]) | |
| assert EP._resolve_name("SAP SE")["yahoo"] == "SAP.DE" # XETRA over Frankfurt, never the London 0-line | |
| monkeypatch.setattr(EP, "av_symbol_search", lambda q: [ | |
| {"av": "YASM.LON", "yahoo": "YASM.L", "name": "ASML (ASML) OPTIONS ETP", "region": "United Kingdom", "currency": "GBX", "type": "ETF"}, | |
| {"av": "ASML.AMS", "yahoo": "ASML.AS", "name": "ASML Holding N.V.", "region": "Amsterdam", "currency": "EUR", "type": "Equity"}]) | |
| assert EP._resolve_name("ASML Holding NV")["yahoo"] == "ASML.AS" # not the leveraged ETP | |
| def test_europanel_universe_dedupes_vendor_spellings(monkeypatch): | |
| from src import europanel as EP | |
| profiles = {"VGK": [{"description": "UNILEVER PLC", "weight": "0.009"}], | |
| "SPEU": [{"description": "Unilever plc", "weight": "0.0085"}, {"description": "RELX PLC", "weight": "0.004"}], | |
| "DBEU": [], "FEZ": []} | |
| monkeypatch.setattr(EP, "_av_call", lambda params, raw=False: {"holdings": profiles[params["symbol"]]}) | |
| u = EP._universe(10) | |
| assert [r["name"] for r in u] == ["UNILEVER PLC", "RELX PLC"] and u[0]["weight"] == 0.009 | |
| def test_legal_form_prefers_home_venue(): | |
| from src import europanel as EP | |
| assert EP._home_sfx("HSBC HOLDINGS PLC") == {"L"} | |
| assert "DE" in EP._home_sfx("Novo Nordisk AS Class B") and "F" in EP._home_sfx("Novo Nordisk AS Class B") | |
| assert EP._home_sfx("Some Company") == set() | |
| def test_fundamentals_scores_on_synthetic_statements(): | |
| from src import fundamentals as FU | |
| y1 = {"assets": 1000, "liabilities": 400, "netinc": 120, "ncfo": 150, "debt": 100, "currentratio": 1.8, "sharesbas": 100, | |
| "grossmargin": 0.45, "revenue": 900, "workingcapital": 200, "retearn": 300, "ebit": 160, "marketcap": 2400, | |
| "receivables": 90, "gp": 405, "assetsc": 400, "ppnenet": 300, "depamor": 40, "sgna": 150, "capex": -50, "fcf": 100, | |
| "sbcomp": 10, "dps": 1.0, "roic": 0.18, "invcap": 700, "taxexp": 30, "ebt": 150, "epsdil": 1.2, "bvps": 6.0, "pe": 20} | |
| y0 = {**y1, "netinc": 100, "ncfo": 120, "debt": 120, "currentratio": 1.6, "sharesbas": 102, "grossmargin": 0.43, | |
| "revenue": 820, "receivables": 80, "gp": 353, "epsdil": 0.98, "dps": 0.9} | |
| older = [{**y0, "revenue": 700, "epsdil": 0.9, "dps": 0.8}, {**y0, "revenue": 650, "epsdil": 0.85, "dps": 0.7}, | |
| {**y0, "revenue": 600, "epsdil": 0.8, "dps": 0.6}, {**y0, "revenue": 560, "epsdil": 0.7, "dps": 0.5, "invcap": 500, "ebit": 100}] | |
| a = [y1, y0] + older | |
| f = FU.piotroski(a) | |
| assert f["score"] == 9 | |
| assert FU.altman_z(y1, 2400) > 2.99 | |
| assert FU.beneish_m(a) < -1.78 | |
| e = FU.economics(a, 24.0, 2400, "Technology", "Software - Application") | |
| assert e["cash_conversion"] == round(100 / 120, 2) and e["dividend_streak_years"] == 5 | |
| assert abs(e["buyback_yield_1y"] - (1 - 100 / 102)) < 1e-3 and e["fcf_payout"] == 1.0 | |
| assert e.get("greenblatt_ey") is None | |
| assert e["lynch_category"] in ("stalwart", "fast grower") | |
| us = {"piotroski": f, "altman_z": 5.0, "beneish_m": -2.5, "econ": e, "insiders": {}, "earnings": {}} | |
| assert FU.forensic_flags(us) == [] and FU.sell_triggers({}, us) | |
| def test_harvest_pairs_pick_correlated_non_identical_substitute(): | |
| import numpy as np, pandas as pd | |
| from src import revival as R | |
| idx = pd.bdate_range("2024-01-01", periods=300) | |
| rng = np.random.default_rng(3) | |
| base = rng.normal(0, 0.01, 300) | |
| px = lambda r: pd.Series(100 * np.cumprod(1 + r), index=idx) | |
| port_hist = pd.DataFrame({"PFE": px(base), "SPY": px(rng.normal(0, 0.01, 300))}) | |
| cand_hist = pd.DataFrame({"XLV": px(0.9 * base + rng.normal(0, 0.003, 300)), # same-sector ETF, highly correlated | |
| "XLE": px(rng.normal(0, 0.01, 300))}) # unrelated | |
| hs = [{"symbol": "PFE", "entry": 42.0, "price": 25.0, "qty": 100, "pl_pct": -0.4, "fx_usd": 1.0, | |
| "data": {"sector": "Healthcare"}}] | |
| dec = {"PFE": {"action": "Keep"}} | |
| cands = [{"ticker": "XLV", "sector": "Healthcare", "instrument": True, "live": {"name": "Health Care SPDR"}}, | |
| {"ticker": "XLE", "sector": "Energy", "instrument": True, "live": {"name": "Energy SPDR"}}] | |
| out = R.harvest_pairs(hs, dec, cands, cand_hist, port_hist) | |
| assert len(out) == 1 and out[0]["buy"] == "XLV" and out[0]["loss_usd"] == 1700 and "31 days" in out[0]["note"] | |
| assert R.harvest_pairs(hs, {"PFE": {"action": "Trim"}}, cands, cand_hist, port_hist) == [] # exits harvest themselves | |
| def test_cautious_book_marks_needs_and_mutes_gaps(): | |
| from src import metrics as M | |
| pm = {"sector_allocation": {"Consumer Defensive": 0.6, "Communication Services": 0.4}, "effective_n": 6.5} | |
| n = M.diagnose_needs(pm, {}, [], {"risk": "conservative", "horizon": "under 3 years"}) | |
| assert "_cautious" in n and n["_cautious"]["label"] is None | |
| n2 = M.diagnose_needs(pm, {}, [], {"risk": "aggressive"}) | |
| assert "_cautious" not in n2 and n["sector_gaps"]["weight"] < n2["sector_gaps"]["weight"] | |
| def test_ballast_floor_in_bounds(): | |
| from src import construct as CX | |
| hs = [{"symbol": "BND", "weight": 0.14}, {"symbol": "JNJ", "weight": 0.14}] | |
| dec = {"BND": {"action": "Keep", "ballast": True}, "JNJ": {"action": "Trim"}} | |
| b = CX.build_bounds(hs, dec, [], {"BND": 0.3, "JNJ": 0.3}) | |
| assert b["BND"][0] == 0.14 and abs(b["JNJ"][0] - 0.056) < 1e-9 # a Trim keeps ≥40%: never a silent full exit | |
| def test_importer_reads_plain_tables_without_llm(): | |
| from src.importer import parse_table | |
| csv = "Symbol,Description,Quantity,Avg Cost,Current Price,Market Value\nAAPL,Apple,10,150.25,320,3200\nNFLX,Netflix,5,\"1,210.50\",78,390\nTOTAL,,,,,3590\n" | |
| rows = parse_table(csv) | |
| assert rows == [{"query": "AAPL", "entry": 150.25, "qty": 10.0}, {"query": "NFLX", "entry": 1210.5, "qty": 5.0}] | |
| assert parse_table("Date,Action,Symbol,Price,Quantity\n2024-01-01,BUY,AAPL,150,10\n") is None # transactions → LLM | |
| assert parse_table("just some text\nwithout a table") is None | |
| def test_presplit_entry_is_a_question_never_a_silent_rewrite(monkeypatch): | |
| from src import portfolio as PF | |
| monkeypatch.setattr(PF, "resolve_symbol", lambda q: {"symbol": "NFLX", "name": "Netflix", "price": 78.0, "data": {"currency": "USD"}}) | |
| monkeypatch.setattr(PF, "fx_to_usd", lambda c: 1.0) | |
| monkeypatch.setattr(PF, "validate_row", lambda *a, **k: [{"code": "presplit", "message": "NFLX had a 10-for-1 split on 2025-11-17 — x", | |
| "suggest": {"field": "entry", "value": 17.1}}]) | |
| hs, errs = PF.build_holdings([{"query": "NFLX", "entry": 171, "qty": 5}]) | |
| assert not hs and errs[0]["code"] == "needs_decision" and "keep as entered" in errs[0]["error"] | |
| hs, _ = PF.build_holdings([{"query": "NFLX", "entry": 171, "qty": 5, "split": "both"}]) | |
| assert hs[0]["entry"] == 17.1 and hs[0]["qty"] == 50 and "split-adjusted" in hs[0]["entry_note"] | |
| hs, _ = PF.build_holdings([{"query": "NFLX", "entry": 171, "qty": 5, "split": "price"}]) | |
| assert hs[0]["entry"] == 17.1 and hs[0]["qty"] == 5 | |
| hs, _ = PF.build_holdings([{"query": "NFLX", "entry": 171, "qty": 5, "split": "keep"}]) | |
| assert hs[0]["entry"] == 171 and hs[0]["qty"] == 5 and "kept as entered" in hs[0]["entry_note"] | |
| def test_strong_ok_raises_the_bar_for_flagged_names_in_a_cautious_book(): | |
| from src.revival import strong_ok | |
| flagged = {"committee": {"conviction": 64}, "fit": {"cautions": ["peak-cycle"]}} | |
| strong_ok.cautious = True | |
| try: | |
| assert not strong_ok(flagged) # 64 with a flag: not for a retiree | |
| assert strong_ok({"committee": {"conviction": 67}, "fit": {"cautions": ["peak-cycle"]}}) | |
| assert strong_ok({"committee": {"conviction": 56}, "fit": {"cautions": []}}) | |
| finally: | |
| strong_ok.cautious = False | |
| assert strong_ok(flagged) | |
| def test_harvest_substitutes_a_broad_fund_with_a_broad_fund_only(): | |
| from src.revival import harvest_pairs | |
| rng = np.random.default_rng(1) | |
| base = rng.normal(0, 0.01, 400) | |
| px = lambda r: pd.Series(100 * np.exp(np.cumsum(r)), index=pd.date_range("2024-01-01", periods=400)) | |
| port = pd.DataFrame({"VOO": px(base)}) | |
| cands = pd.DataFrame({"VTI": px(base + rng.normal(0, 0.002, 400)), # ≈0.98 correlated broad fund | |
| "XLY": px(base + rng.normal(0, 0.006, 400))}) # ≈0.86 correlated sector fund | |
| hs = [{"symbol": "VOO", "entry": 600.0, "price": 400.0, "qty": 10, "pl_pct": -0.33, | |
| "data": {"quote_type": "ETF", "sector": None}}] | |
| dec = {"VOO": {"action": "Keep"}} | |
| only_sector = [{"ticker": "XLY", "sector": "Consumer Cyclical", "instrument": True, "live": {"name": "XLY"}}] | |
| assert harvest_pairs(hs, dec, only_sector, cands, port) == [] | |
| both = only_sector + [{"ticker": "VTI", "sector": "Diversified fund", "instrument": True, "broad": True, "live": {"name": "VTI"}}] | |
| out = harvest_pairs(hs, dec, both, cands, port) | |
| assert out and out[0]["buy"] == "VTI" and out[0]["correlation"] >= 0.9 | |
| def test_distress_flag_disqualifies_a_candidate(): | |
| from src.revival import strong_ok | |
| assert not strong_ok({"committee": {"conviction": 64}, "fit": {"cautions": ["forensic: distress zone (Altman Z 1.45)"]}}) | |
| assert strong_ok({"committee": {"conviction": 64}, "fit": {"cautions": ["peak-cycle risk"]}}) | |
| def test_tax_plan_and_triage_consistency(): | |
| from src.revival import tax_plan, triage | |
| from datetime import date, timedelta | |
| recent = (date.today() - timedelta(days=300)).isoformat() | |
| hs = [{"symbol": "META", "weight": 0.24, "price": 700.0, "entry": 135.0, "qty": 95, "fx_usd": 1.0, "market_value": 66500.0, | |
| "bought": None, "pl_pct": 4.2, "lots": [{"entry": 100.0, "qty": 50, "bought": "2021-01-05"}, {"entry": 170.0, "qty": 45, "bought": recent}], "data": {}}, | |
| {"symbol": "LULU", "weight": 0.003, "price": 98.0, "entry": 276.0, "qty": 3, "fx_usd": 1.0, "market_value": 294.0, "bought": None, "pl_pct": -0.64, "data": {}, "dcf": {}}] | |
| orders = [{"side": "SELL", "ticker": "META", "delta_pct": -0.10}] | |
| tp = tax_plan(hs, {}, orders, 277000.0) | |
| note = tp[0]["note"] | |
| assert "170.00 first" in note and "turns long-term" in note and "two tax years" in note | |
| dec = {"META": {"action": "Trim", "conviction": 58}, "LULU": {"action": "Keep", "conviction": 38}} | |
| t = {x["ticker"]: x["disposition"] for x in triage(hs, dec, bought=set(), sold={"META"})} | |
| assert t["LULU"] != "exit to fund" # the plan keeps LULU, so triage must not say exit | |
| def test_shortlist_covers_the_missing_sectors_first(): | |
| from src import orchestrator as O | |
| meta = lambda t, s, conv: {"ticker": t, "sector": s, "committee": {"conviction": conv}, "fit": {"fixes": [f"adds {s}"], "cautions": []}, | |
| "trend_ok": True, "live": {"name": t}} | |
| eligible = [meta("TSM", "Technology", 68), meta("NOC", "Industrials", 60), meta("SHW", "Basic Materials", 59), meta("EXC", "Utilities", 57)] | |
| holdings = [{"symbol": f"H{i}", "data": {}} for i in range(14)] | |
| O._ensure_single_names.needs = {"sector_gaps": {"sectors": ["Industrials", "Basic Materials", "Utilities"]}} | |
| try: | |
| out = O._ensure_single_names([{"ticker": "TSM", "reason": "x"}], eligible, holdings, set()) | |
| finally: | |
| O._ensure_single_names.needs = None | |
| tickers = [a["ticker"] for a in out] | |
| assert tickers[:3] == ["TSM", "NOC", "SHW"] and "EXC" in tickers | |