stock 실계좌 정보 표출 추가

This commit is contained in:
2026-02-25 23:49:28 +09:00
parent 71d9d7a571
commit ea9eb749aa
4 changed files with 236 additions and 4 deletions

View File

@@ -8,8 +8,13 @@ import requests
from apscheduler.schedulers.background import BackgroundScheduler
from pydantic import BaseModel
from .db import init_db, save_articles, get_latest_articles
from .db import (
init_db, save_articles, get_latest_articles,
add_portfolio_item, get_all_portfolio, get_portfolio_item,
update_portfolio_item, delete_portfolio_item,
)
from .scraper import fetch_market_news, fetch_major_indices, fetch_overseas_news
from .price_fetcher import get_current_prices
app = FastAPI()
@@ -130,3 +135,97 @@ def version():
return {"version": os.getenv("APP_VERSION", "dev")}
# --- Portfolio API ---
class PortfolioItemRequest(BaseModel):
broker: str
ticker: str
name: str
quantity: int
avg_price: int
class PortfolioUpdateRequest(BaseModel):
broker: Optional[str] = None
ticker: Optional[str] = None
name: Optional[str] = None
quantity: Optional[int] = None
avg_price: Optional[int] = None
@app.get("/api/portfolio")
def get_portfolio():
"""전체 포트폴리오 조회 (현재가 + 손익 포함)"""
items = get_all_portfolio()
if not items:
return {"holdings": [], "summary": {"total_buy": 0, "total_eval": 0, "total_profit": 0, "total_profit_rate": 0.0}}
tickers = list({item["ticker"] for item in items})
prices = get_current_prices(tickers)
holdings = []
total_buy = 0
total_eval = 0
for item in items:
current_price = prices.get(item["ticker"])
buy_amount = item["avg_price"] * item["quantity"]
eval_amount = current_price * item["quantity"] if current_price is not None else None
profit_amount = (eval_amount - buy_amount) if eval_amount is not None else None
profit_rate = round((profit_amount / buy_amount) * 100, 2) if (profit_amount is not None and buy_amount) else None
holdings.append({
"id": item["id"],
"broker": item["broker"],
"ticker": item["ticker"],
"name": item["name"],
"quantity": item["quantity"],
"avg_price": item["avg_price"],
"current_price": current_price,
"eval_amount": eval_amount,
"profit_amount": profit_amount,
"profit_rate": profit_rate,
})
total_buy += buy_amount
if eval_amount is not None:
total_eval += eval_amount
total_profit = total_eval - total_buy
total_profit_rate = round((total_profit / total_buy) * 100, 2) if total_buy else 0.0
return {
"holdings": holdings,
"summary": {
"total_buy": total_buy,
"total_eval": total_eval,
"total_profit": total_profit,
"total_profit_rate": total_profit_rate,
},
}
@app.post("/api/portfolio", status_code=201)
def create_portfolio_item(req: PortfolioItemRequest):
"""포트폴리오 종목 추가"""
item_id = add_portfolio_item(req.broker, req.ticker, req.name, req.quantity, req.avg_price)
return {"id": item_id, "ok": True}
@app.put("/api/portfolio/{item_id}")
def update_portfolio(item_id: int, req: PortfolioUpdateRequest):
"""포트폴리오 종목 수정"""
if get_portfolio_item(item_id) is None:
return JSONResponse(status_code=404, content={"error": "Item not found"})
update_portfolio_item(item_id, **req.dict())
return {"ok": True}
@app.delete("/api/portfolio/{item_id}")
def delete_portfolio(item_id: int):
"""포트폴리오 종목 삭제"""
if not delete_portfolio_item(item_id):
return JSONResponse(status_code=404, content={"error": "Item not found"})
return {"ok": True}