feat(stock): get_holdings (현재가·손익·KRX판별)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
37
stock/app/holdings_intel.py
Normal file
37
stock/app/holdings_intel.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
"""보유종목 인텔리전스 — 순수연산 중심 (advisory). KIS 실주문 미사용."""
|
||||||
|
from __future__ import annotations
|
||||||
|
import datetime as dt
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
from . import db
|
||||||
|
from . import price_fetcher
|
||||||
|
|
||||||
|
|
||||||
|
def _krx_tickers() -> set:
|
||||||
|
"""krx_master에 존재하는 ticker 집합 (KRX 판별용)."""
|
||||||
|
with db._conn() as conn:
|
||||||
|
try:
|
||||||
|
rows = conn.execute("SELECT ticker FROM krx_master").fetchall()
|
||||||
|
except Exception:
|
||||||
|
return set()
|
||||||
|
return {r["ticker"] for r in rows}
|
||||||
|
|
||||||
|
|
||||||
|
def get_holdings() -> list[dict]:
|
||||||
|
"""portfolio + 현재가 + pnl_rate + is_krx."""
|
||||||
|
items = db.get_all_portfolio()
|
||||||
|
tickers = [it["ticker"] for it in items]
|
||||||
|
prices = price_fetcher.get_current_prices(tickers) if tickers else {}
|
||||||
|
krx = _krx_tickers()
|
||||||
|
out = []
|
||||||
|
for it in items:
|
||||||
|
cur = prices.get(it["ticker"])
|
||||||
|
avg = it["avg_price"]
|
||||||
|
pnl = ((cur - avg) / avg * 100.0) if (cur and avg) else None
|
||||||
|
out.append({
|
||||||
|
**it,
|
||||||
|
"current_price": cur,
|
||||||
|
"pnl_rate": pnl,
|
||||||
|
"is_krx": it["ticker"] in krx,
|
||||||
|
})
|
||||||
|
return out
|
||||||
17
stock/app/test_holdings_intel.py
Normal file
17
stock/app/test_holdings_intel.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from app import holdings_intel as hi
|
||||||
|
|
||||||
|
def test_get_holdings_merges_price_and_pnl(monkeypatch):
|
||||||
|
monkeypatch.setattr(hi.db, "get_all_portfolio", lambda: [
|
||||||
|
{"id": 1, "broker": "kis", "ticker": "005930", "name": "삼성전자",
|
||||||
|
"quantity": 10, "avg_price": 70000, "purchase_price": 70000},
|
||||||
|
{"id": 2, "broker": "kis", "ticker": "AAPL", "name": "Apple",
|
||||||
|
"quantity": 5, "avg_price": 200, "purchase_price": 200},
|
||||||
|
])
|
||||||
|
monkeypatch.setattr(hi.price_fetcher, "get_current_prices",
|
||||||
|
lambda tickers: {"005930": 77000}) # AAPL 미조회(비KRX)
|
||||||
|
monkeypatch.setattr(hi, "_krx_tickers", lambda: {"005930"})
|
||||||
|
hs = hi.get_holdings()
|
||||||
|
s = {h["ticker"]: h for h in hs}
|
||||||
|
assert s["005930"]["is_krx"] is True
|
||||||
|
assert round(s["005930"]["pnl_rate"], 1) == 10.0 # (77000-70000)/70000
|
||||||
|
assert s["AAPL"]["is_krx"] is False # KRX 외
|
||||||
Reference in New Issue
Block a user