From c7401c5d9f01c1ffb93be08bde1ba7f04bf48593 Mon Sep 17 00:00:00 2001 From: gahusb Date: Thu, 19 Mar 2026 22:31:17 +0900 Subject: [PATCH] =?UTF-8?q?=EC=A3=BC=EC=8B=9D=20=EB=A7=A4=EB=8F=84=20?= =?UTF-8?q?=ED=9E=88=EC=8A=A4=ED=86=A0=EB=A6=AC=20API=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20(/api/portfolio/sell-history)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - sell_history 테이블 신규 생성 (db.py init_db) - CRUD 함수 추가: add_sell_history, get_sell_history, delete_sell_history - GET /api/portfolio/sell-history (broker, days 필터) - POST /api/portfolio/sell-history (id 포함 저장된 레코드 반환) - DELETE /api/portfolio/sell-history/{record_id} Co-Authored-By: Claude Sonnet 4.6 --- stock-lab/app/db.py | 60 +++++++++++++++++++++++++++++++++++++++++++ stock-lab/app/main.py | 39 ++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/stock-lab/app/db.py b/stock-lab/app/db.py index cdb0ed6..e9588fa 100644 --- a/stock-lab/app/db.py +++ b/stock-lab/app/db.py @@ -66,6 +66,23 @@ def init_db(): ) """) + conn.execute(""" + CREATE TABLE IF NOT EXISTS sell_history ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + broker TEXT NOT NULL, + ticker TEXT NOT NULL, + name TEXT NOT NULL, + quantity INTEGER NOT NULL, + avg_price REAL NOT NULL, + sell_price REAL NOT NULL, + buy_amount REAL NOT NULL, + sell_amount REAL NOT NULL, + realized_profit REAL NOT NULL, + realized_rate REAL NOT NULL, + sold_at TEXT NOT NULL + ) + """) + def save_articles(articles: List[Dict[str, str]]) -> int: count = 0 with _conn() as conn: @@ -188,6 +205,49 @@ def upsert_asset_snapshot(date: str, total_eval: int, total_cash: int, total_ass """, (date, total_eval, total_cash, total_assets, now)) +# --- Sell History CRUD --- + +def add_sell_history(data: Dict[str, Any]) -> Dict[str, Any]: + with _conn() as conn: + cur = conn.execute(""" + INSERT INTO sell_history + (broker, ticker, name, quantity, avg_price, sell_price, + buy_amount, sell_amount, realized_profit, realized_rate, sold_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, ( + data["broker"], data["ticker"], data["name"], data["quantity"], + data["avg_price"], data["sell_price"], data["buy_amount"], + data["sell_amount"], data["realized_profit"], data["realized_rate"], + data["sold_at"], + )) + row = conn.execute("SELECT * FROM sell_history WHERE id = ?", (cur.lastrowid,)).fetchone() + return dict(row) + + +def get_sell_history(broker: str = None, days: int = None) -> List[Dict[str, Any]]: + conditions = [] + params = [] + if broker: + conditions.append("broker = ?") + params.append(broker) + if days: + conditions.append("sold_at >= datetime('now', ? || ' days')") + params.append(f"-{days}") + where = f"WHERE {' AND '.join(conditions)}" if conditions else "" + with _conn() as conn: + rows = conn.execute( + f"SELECT * FROM sell_history {where} ORDER BY sold_at DESC", + params, + ).fetchall() + return [dict(r) for r in rows] + + +def delete_sell_history(record_id: int) -> bool: + with _conn() as conn: + cur = conn.execute("DELETE FROM sell_history WHERE id = ?", (record_id,)) + return cur.rowcount > 0 + + def get_asset_snapshots(days: int = 30) -> List[Dict[str, Any]]: with _conn() as conn: if days == 0: diff --git a/stock-lab/app/main.py b/stock-lab/app/main.py index ccee92f..485c1a0 100644 --- a/stock-lab/app/main.py +++ b/stock-lab/app/main.py @@ -15,6 +15,7 @@ from .db import ( update_portfolio_item, delete_portfolio_item, upsert_broker_cash, get_all_broker_cash, get_broker_cash, delete_broker_cash, upsert_asset_snapshot, get_asset_snapshots, + add_sell_history, get_sell_history, delete_sell_history, ) from .scraper import fetch_market_news, fetch_major_indices, fetch_overseas_news from .price_fetcher import get_current_prices @@ -361,3 +362,41 @@ def get_snapshot_history(days: int = Query(30, ge=0)): return {"snapshots": snapshots} +# --- Sell History API --- + +class SellHistoryRequest(BaseModel): + broker: str + ticker: str + name: str + quantity: int + avg_price: float + sell_price: float + buy_amount: float + sell_amount: float + realized_profit: float + realized_rate: float + sold_at: str + + +@app.get("/api/portfolio/sell-history") +def list_sell_history(broker: Optional[str] = None, days: Optional[int] = None): + """매도 내역 조회 (broker, days 필터 선택)""" + records = get_sell_history(broker=broker, days=days) + return {"records": records} + + +@app.post("/api/portfolio/sell-history") +def create_sell_history(req: SellHistoryRequest): + """매도 기록 저장""" + record = add_sell_history(req.model_dump()) + return record + + +@app.delete("/api/portfolio/sell-history/{record_id}") +def remove_sell_history(record_id: int): + """매도 기록 삭제""" + if not delete_sell_history(record_id): + return JSONResponse(status_code=404, content={"error": "not found"}) + return {"ok": True} + +