Files
Stock-trading-programming/app/execution/order_executor.py
T
whdwo798 bf041e4d18 [2026-05-18] 포지션 DB 동기화 + pnl 계산 수정
- order_executor: _update_trade_exit에 pnl 계산 저장 추가
- main: 매수 시 positions DB INSERT, 매도 시 DELETE
- main: 재시작 시 DB에서 positions 복원 (_restore_positions_from_db)
2026-05-18 13:32:43 +09:00

109 lines
3.8 KiB
Python

"""
execution/order_executor.py
주문 실행 모듈
DRY_RUN=true 시 실제 주문 전송 없음
"""
import os
import asyncio
import logging
from datetime import datetime
from app.execution.kis_client import KISClient
from app.db.models import get_conn
from app.config import FEE_RATE, TAX_RATE
logger = logging.getLogger(__name__)
class OrderExecutor:
def __init__(self, kis: KISClient):
self.kis = kis
self.dry_run = os.getenv("DRY_RUN", "true").lower() == "true"
def _calc_fee(self, price: float, qty: int, is_buy: bool) -> float:
amt = price * qty
return amt * (FEE_RATE + (0 if is_buy else TAX_RATE))
async def buy(self, ticker: str, name: str,
qty: int, reason: str = "",
ai_boosted: bool = False) -> dict:
"""시장가 매수"""
try:
result = await self.kis.order_buy(ticker, qty)
price = result.get("entry_price", 0)
# DB 저장
fee = self._calc_fee(price, qty, True)
self._save_trade(
ticker=ticker, name=name,
entry_price=price, qty=qty,
side="BUY", fee=fee,
ai_boosted=ai_boosted,
)
mode = "[DRY]" if self.dry_run else ""
logger.info(f"{mode} 매수 {name}({ticker}) {qty}주 @ {price:,}")
return {"success": True, "price": price, "qty": qty}
except Exception as e:
logger.error(f"매수 실패 {ticker}: {e}")
return {"success": False, "error": str(e)}
async def sell(self, ticker: str, name: str,
qty: int, reason: str = "") -> dict:
"""시장가 매도"""
try:
result = await self.kis.order_sell(ticker, qty)
price = result.get("exit_price", 0)
fee = self._calc_fee(price, qty, False)
self._update_trade_exit(
ticker=ticker, exit_price=price,
qty=qty, reason=reason, fee=fee,
)
mode = "[DRY]" if self.dry_run else ""
logger.info(f"{mode} 매도 {name}({ticker}) {qty}주 @ {price:,}원 [{reason}]")
return {"success": True, "price": price, "qty": qty}
except Exception as e:
logger.error(f"매도 실패 {ticker}: {e}")
return {"success": False, "error": str(e)}
def _save_trade(self, ticker, name, entry_price,
qty, side, fee, ai_boosted=False):
with get_conn() as conn:
conn.execute("""
INSERT INTO trades
(date, ticker, name, entry_time, entry_price,
quantity, side, fee, ai_boosted)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
datetime.now().strftime("%Y-%m-%d"),
ticker, name,
datetime.now().strftime("%H:%M:%S"),
entry_price, qty, side, fee,
1 if ai_boosted else 0,
))
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+?, pnl=?
WHERE id=?
""", (
datetime.now().strftime("%H:%M:%S"),
exit_price, reason, fee, pnl, trade_id,
))