first vibe coding
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
"""
|
||||
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 ticker=? AND exit_time IS NULL
|
||||
ORDER BY id DESC LIMIT 1
|
||||
""", (
|
||||
datetime.now().strftime("%H:%M:%S"),
|
||||
exit_price, reason, fee, ticker,
|
||||
))
|
||||
Reference in New Issue
Block a user