feat(stock): portfolio_health (집중도·현금·손익)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -209,6 +209,35 @@ def news_issues(tickers: list[str], date: str, use_llm: bool = True) -> dict[str
|
||||
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,
|
||||
add_score: float = ADD_SCORE) -> tuple[str, str]:
|
||||
"""액션 결정 매트릭스: sell > trim > add > hold (우선순위 순).
|
||||
|
||||
@@ -213,3 +213,16 @@ def test_news_issues_flags_negative_sentiment(monkeypatch):
|
||||
assert "005930" in issues
|
||||
assert issues["005930"][0]["type"] == "news"
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user