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
|
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 (우선순위 순).
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user