[2026-05-18] 포지션 DB 동기화 + pnl 계산 수정
- order_executor: _update_trade_exit에 pnl 계산 저장 추가 - main: 매수 시 positions DB INSERT, 매도 시 DELETE - main: 재시작 시 DB에서 positions 복원 (_restore_positions_from_db)
This commit is contained in:
@@ -88,15 +88,21 @@ class OrderExecutor:
|
|||||||
def _update_trade_exit(self, ticker, exit_price,
|
def _update_trade_exit(self, ticker, exit_price,
|
||||||
qty, reason, fee):
|
qty, reason, fee):
|
||||||
with get_conn() as conn:
|
with get_conn() as conn:
|
||||||
conn.execute("""
|
row = conn.execute("""
|
||||||
UPDATE trades
|
SELECT id, entry_price, quantity FROM trades
|
||||||
SET exit_time=?, exit_price=?, exit_reason=?, fee=fee+?
|
|
||||||
WHERE id = (
|
|
||||||
SELECT id FROM trades
|
|
||||||
WHERE ticker=? AND exit_time IS NULL
|
WHERE ticker=? AND exit_time IS NULL
|
||||||
ORDER BY id DESC LIMIT 1
|
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+?, pnl=?
|
||||||
|
WHERE id=?
|
||||||
""", (
|
""", (
|
||||||
datetime.now().strftime("%H:%M:%S"),
|
datetime.now().strftime("%H:%M:%S"),
|
||||||
exit_price, reason, fee, ticker,
|
exit_price, reason, fee, pnl, trade_id,
|
||||||
))
|
))
|
||||||
|
|||||||
+54
-1
@@ -103,9 +103,53 @@ class StockBot:
|
|||||||
cash = balance["cash"]
|
cash = balance["cash"]
|
||||||
self.risk = RiskManager(init_cash=cash)
|
self.risk = RiskManager(init_cash=cash)
|
||||||
logger.info(f"초기 예수금: {cash:,}원")
|
logger.info(f"초기 예수금: {cash:,}원")
|
||||||
|
|
||||||
|
# DB에서 열린 포지션 복원 (재시작 시)
|
||||||
|
self._restore_positions_from_db()
|
||||||
await send(f"[시작] 단타봇 가동 | 예수금: {cash:,}원 | "
|
await send(f"[시작] 단타봇 가동 | 예수금: {cash:,}원 | "
|
||||||
f"{'모의투자' if self.kis.is_mock else '실거래'}")
|
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)
|
# 유니버스 갱신 (08:30)
|
||||||
# ─────────────────────────────────────────
|
# ─────────────────────────────────────────
|
||||||
@@ -289,7 +333,7 @@ class StockBot:
|
|||||||
entry_price = result["price"] or current
|
entry_price = result["price"] or current
|
||||||
sl_price = entry_price * (1 - self.risk.get_sl_pct())
|
sl_price = entry_price * (1 - self.risk.get_sl_pct())
|
||||||
|
|
||||||
self.positions[ticker] = {
|
pos = {
|
||||||
"name" : name,
|
"name" : name,
|
||||||
"entry" : entry_price,
|
"entry" : entry_price,
|
||||||
"qty" : qty,
|
"qty" : qty,
|
||||||
@@ -298,6 +342,11 @@ class StockBot:
|
|||||||
"sl_price" : sl_price,
|
"sl_price" : sl_price,
|
||||||
"boosted" : signal.get("boosted", False),
|
"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(
|
await notify_buy(
|
||||||
ticker=ticker, name=name,
|
ticker=ticker, name=name,
|
||||||
@@ -370,10 +419,14 @@ class StockBot:
|
|||||||
pos["qty"] -= qty
|
pos["qty"] -= qty
|
||||||
if pos["qty"] <= 0:
|
if pos["qty"] <= 0:
|
||||||
del self.positions[ticker]
|
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)
|
await notify_tp1(ticker, name, pnl_pct)
|
||||||
|
|
||||||
elif reason in ("TP2", "SL", "TIME", "FORCE"):
|
elif reason in ("TP2", "SL", "TIME", "FORCE"):
|
||||||
del self.positions[ticker]
|
del self.positions[ticker]
|
||||||
|
self._db_delete_position(ticker)
|
||||||
if reason == "TP2":
|
if reason == "TP2":
|
||||||
await notify_tp2(ticker, name, pnl_pct)
|
await notify_tp2(ticker, name, pnl_pct)
|
||||||
elif reason == "SL":
|
elif reason == "SL":
|
||||||
|
|||||||
Reference in New Issue
Block a user