feat(stock): portfolio_health (집중도·현금·손익)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-31 22:03:21 +09:00
parent 4ed3794f71
commit 3056e8d35f
2 changed files with 42 additions and 0 deletions

View File

@@ -209,6 +209,35 @@ def news_issues(tickers: list[str], date: str, use_llm: bool = True) -> dict[str
return out return out
# ---- Task 3.3: portfolio_health ----
def portfolio_health(holdings: list[dict], total_cash: int = 0) -> dict:
"""비중 집중도(최대비중·HHI) + 현금비중 + 총손익 요약."""
evals, buys = [], []
for h in holdings:
cur = h.get("current_price") or h.get("avg_price") or 0
ev = cur * h.get("quantity", 0)
bu = (h.get("avg_price") or 0) * h.get("quantity", 0)
evals.append(ev)
buys.append(bu)
total_eval = sum(evals)
total_buy = sum(buys)
weights = [e / total_eval for e in evals] if total_eval else []
hhi = sum(w * w for w in weights)
total_assets = total_eval + (total_cash or 0)
return {
"positions": len(holdings),
"total_eval": total_eval,
"total_buy": total_buy,
"total_pnl": total_eval - total_buy,
"total_pnl_rate": ((total_eval - total_buy) / total_buy * 100.0) if total_buy else 0.0,
"max_weight": max(weights) if weights else 0.0,
"hhi": round(hhi, 4),
"cash_ratio": ((total_cash or 0) / total_assets) if total_assets else 0.0,
}
def decide_action(tech_score: float, exit_flags: dict, pnl: float | None, def decide_action(tech_score: float, exit_flags: dict, pnl: float | None,
add_score: float = ADD_SCORE) -> tuple[str, str]: add_score: float = ADD_SCORE) -> tuple[str, str]:
"""액션 결정 매트릭스: sell > trim > add > hold (우선순위 순). """액션 결정 매트릭스: sell > trim > add > hold (우선순위 순).

View File

@@ -213,3 +213,16 @@ def test_news_issues_flags_negative_sentiment(monkeypatch):
assert "005930" in issues assert "005930" in issues
assert issues["005930"][0]["type"] == "news" assert issues["005930"][0]["type"] == "news"
assert issues["005930"][0]["severity"] in ("med", "high") assert issues["005930"][0]["severity"] in ("med", "high")
def test_portfolio_health():
holdings = [
{"ticker": "005930", "quantity": 10, "avg_price": 70000, "current_price": 77000,
"is_krx": True},
{"ticker": "000660", "quantity": 5, "avg_price": 100000, "current_price": 90000,
"is_krx": True},
]
h = hi.portfolio_health(holdings, total_cash=1000000)
assert h["positions"] == 2
assert 0 <= h["max_weight"] <= 1.0
assert "total_eval" in h and "total_pnl" in h and "cash_ratio" in h