From 89c52b1fb65b37ce379a74ba4cda8d5b5679eae4 Mon Sep 17 00:00:00 2001 From: gahusb Date: Thu, 2 Jul 2026 15:45:14 +0900 Subject: [PATCH] =?UTF-8?q?feat(stock):=20watchlist=20CRUD=20+=20=EC=95=8C?= =?UTF-8?q?=EB=9E=8C=20=EC=9D=B4=EB=A0=A5=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- stock/app/main.py | 36 +++++++++++++++++++++++++++++++ stock/tests/test_watchlist_api.py | 22 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 stock/tests/test_watchlist_api.py diff --git a/stock/app/main.py b/stock/app/main.py index 58a7d95..ab598d1 100644 --- a/stock/app/main.py +++ b/stock/app/main.py @@ -21,6 +21,7 @@ from .db import ( upsert_broker_cash, get_all_broker_cash, delete_broker_cash, upsert_asset_snapshot, get_asset_snapshots, add_sell_history, get_sell_history, update_sell_history, delete_sell_history, + add_watchlist, remove_watchlist, get_watchlist, get_alert_history, ) from .scraper import fetch_market_news, fetch_major_indices from .price_fetcher import get_current_prices, get_current_prices_detail @@ -653,6 +654,41 @@ def remove_sell_history(record_id: int): return {"ok": True} +# --- Watchlist & Trade Alerts API (실시간 매매 알람) --- + +class WatchlistItemRequest(BaseModel): + ticker: str + name: str | None = None + note: str | None = None + + +@app.get("/api/stock/watchlist") +def list_watchlist(): + """관심종목 목록 조회""" + return {"watchlist": get_watchlist()} + + +@app.post("/api/stock/watchlist", status_code=201) +def create_watchlist_item(req: WatchlistItemRequest): + """관심종목 추가 (이미 존재하면 name/note 갱신, 멱등)""" + add_watchlist(req.ticker, req.name, req.note) + return {"ok": True} + + +@app.delete("/api/stock/watchlist/{ticker}") +def delete_watchlist_item(ticker: str): + """관심종목 삭제""" + if not remove_watchlist(ticker): + raise HTTPException(status_code=404, detail="not in watchlist") + return {"ok": True} + + +@app.get("/api/stock/trade-alerts") +def list_trade_alerts(days: int = 7): + """매매 알람 이력 조회 (최근 N일)""" + return {"alerts": get_alert_history(days)} + + # --- Holdings Intelligence API --- @app.get("/api/stock/holdings/intel") diff --git a/stock/tests/test_watchlist_api.py b/stock/tests/test_watchlist_api.py new file mode 100644 index 0000000..5b4ad9a --- /dev/null +++ b/stock/tests/test_watchlist_api.py @@ -0,0 +1,22 @@ +import pytest +from fastapi.testclient import TestClient + +@pytest.fixture +def client(monkeypatch, tmp_path): + from app import db as _db + monkeypatch.setattr(_db, "DB_PATH", str(tmp_path / "stock.db")) + _db.init_db() + from app.main import app + return TestClient(app) + +def test_watchlist_crud(client): + assert client.get("/api/stock/watchlist").json()["watchlist"] == [] + r = client.post("/api/stock/watchlist", json={"ticker": "005930", "name": "삼성전자"}) + assert r.status_code == 201 + wl = client.get("/api/stock/watchlist").json()["watchlist"] + assert wl[0]["ticker"] == "005930" + assert client.delete("/api/stock/watchlist/005930").status_code == 200 + assert client.delete("/api/stock/watchlist/005930").status_code == 404 + +def test_trade_alerts_history_empty(client): + assert client.get("/api/stock/trade-alerts?days=7").json()["alerts"] == []