From bf041e4d18167538cb5bedb5bb001763a7f2ced1 Mon Sep 17 00:00:00 2001 From: jongjae Date: Mon, 18 May 2026 13:32:43 +0900 Subject: [PATCH] =?UTF-8?q?[2026-05-18]=20=ED=8F=AC=EC=A7=80=EC=85=98=20DB?= =?UTF-8?q?=20=EB=8F=99=EA=B8=B0=ED=99=94=20+=20pnl=20=EA=B3=84=EC=82=B0?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - order_executor: _update_trade_exit에 pnl 계산 저장 추가 - main: 매수 시 positions DB INSERT, 매도 시 DELETE - main: 재시작 시 DB에서 positions 복원 (_restore_positions_from_db) --- app/execution/order_executor.py | 20 +++++++----- app/main.py | 55 ++++++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/app/execution/order_executor.py b/app/execution/order_executor.py index 8f137a1..d62b5ea 100644 --- a/app/execution/order_executor.py +++ b/app/execution/order_executor.py @@ -88,15 +88,21 @@ class OrderExecutor: def _update_trade_exit(self, ticker, exit_price, qty, reason, fee): with get_conn() as conn: + row = conn.execute(""" + SELECT id, entry_price, quantity FROM trades + WHERE ticker=? AND exit_time IS NULL + ORDER BY id DESC LIMIT 1 + """, (ticker,)).fetchone() + if not row: + return + trade_id, entry_price, trade_qty = row + actual_qty = qty if qty else trade_qty + pnl = (exit_price - entry_price) * actual_qty - fee conn.execute(""" UPDATE trades - SET exit_time=?, exit_price=?, exit_reason=?, fee=fee+? - WHERE id = ( - SELECT id FROM trades - WHERE ticker=? AND exit_time IS NULL - ORDER BY id DESC LIMIT 1 - ) + SET exit_time=?, exit_price=?, exit_reason=?, fee=fee+?, pnl=? + WHERE id=? """, ( datetime.now().strftime("%H:%M:%S"), - exit_price, reason, fee, ticker, + exit_price, reason, fee, pnl, trade_id, )) diff --git a/app/main.py b/app/main.py index da3dd93..ed3c762 100644 --- a/app/main.py +++ b/app/main.py @@ -103,9 +103,53 @@ class StockBot: cash = balance["cash"] self.risk = RiskManager(init_cash=cash) logger.info(f"초기 예수금: {cash:,}원") + + # DB에서 열린 포지션 복원 (재시작 시) + self._restore_positions_from_db() await send(f"[시작] 단타봇 가동 | 예수금: {cash:,}원 | " f"{'모의투자' if self.kis.is_mock else '실거래'}") + def _restore_positions_from_db(self): + """재시작 시 DB positions 테이블에서 인메모리 복원""" + with get_conn() as conn: + rows = conn.execute("SELECT * FROM positions").fetchall() + for r in rows: + ticker, name, entry_time, entry_price, qty, tp1_done, target_price, stop_price, ai_boosted = r + self.positions[ticker] = { + "name" : name, + "entry" : entry_price, + "qty" : qty, + "tp1_done" : bool(tp1_done), + "entry_time": datetime.strptime(entry_time, "%H:%M:%S").replace( + year=datetime.now().year, + month=datetime.now().month, + day=datetime.now().day), + "sl_price" : stop_price, + "boosted" : bool(ai_boosted), + } + if self.positions: + logger.info(f"DB 포지션 복원: {list(self.positions.keys())}") + + def _db_save_position(self, ticker: str, pos: dict, target_price: float): + with get_conn() as conn: + conn.execute(""" + INSERT OR REPLACE INTO positions + (ticker, name, entry_time, entry_price, quantity, + tp1_done, target_price, stop_price, ai_boosted) + VALUES (?,?,?,?,?,?,?,?,?) + """, ( + ticker, pos["name"], + pos["entry_time"].strftime("%H:%M:%S"), + pos["entry"], pos["qty"], + 1 if pos.get("tp1_done") else 0, + target_price, pos["sl_price"], + 1 if pos.get("boosted") else 0, + )) + + def _db_delete_position(self, ticker: str): + with get_conn() as conn: + conn.execute("DELETE FROM positions WHERE ticker=?", (ticker,)) + # ───────────────────────────────────────── # 유니버스 갱신 (08:30) # ───────────────────────────────────────── @@ -289,7 +333,7 @@ class StockBot: entry_price = result["price"] or current sl_price = entry_price * (1 - self.risk.get_sl_pct()) - self.positions[ticker] = { + pos = { "name" : name, "entry" : entry_price, "qty" : qty, @@ -298,6 +342,11 @@ class StockBot: "sl_price" : sl_price, "boosted" : signal.get("boosted", False), } + self.positions[ticker] = pos + self._db_save_position( + ticker, pos, + target_price=self.strategy.get_target(ticker), + ) await notify_buy( ticker=ticker, name=name, @@ -370,10 +419,14 @@ class StockBot: pos["qty"] -= qty if pos["qty"] <= 0: del self.positions[ticker] + self._db_delete_position(ticker) + else: + self._db_save_position(ticker, pos, self.strategy.get_target(ticker)) await notify_tp1(ticker, name, pnl_pct) elif reason in ("TP2", "SL", "TIME", "FORCE"): del self.positions[ticker] + self._db_delete_position(ticker) if reason == "TP2": await notify_tp2(ticker, name, pnl_pct) elif reason == "SL":