stock 실계좌 정보 표출 추가
This commit is contained in:
@@ -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}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user