Files
Stock-trading-programming/app/execution/order_executor.py
T
whdwo798 a64a3f017b [2026-05-15] rate limit·전일데이터·TR ID 등 버그 수정
- main.py: sleep 0.05/0.1 → 1.1초 (KIS rate limit 준수)
- main.py: 전일 날짜 계산 수정 (월요일→금요일), 인라인 주석 env 파싱, 장 중 재시작 즉시 루프 진입
- strategy/volatility_breakout.py: has_prev_data() 추가, 중복 수집 skip
- db/repository.py, order_executor.py: UPDATE ORDER BY → 서브쿼리 수정 (SQLite 호환)
- kis_client.py: get_balance TR ID VTTC8001R → VTTC8434R
- test_connection.py: API 호출 간 sleep 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 13:38:40 +09:00

103 lines
3.5 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:
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
)
""", (
datetime.now().strftime("%H:%M:%S"),
exit_price, reason, fee, ticker,
))