[2026-06-02] 결산 중복과 모의투자 호출 안정화

This commit is contained in:
2026-06-02 18:26:12 +09:00
parent b71e08b498
commit 77ddf6760d
4 changed files with 49 additions and 15 deletions
+28 -9
View File
@@ -165,6 +165,7 @@ class StockBot:
self._midday_pos_mult : float = 1.0 # midday position_size_multiplier
self._midday_loaded : bool = False
self._last_diag : float = 0.0 # 신호 진단 로그 마지막 시각
self._daily_summary_dates = set()
mode = "모의투자" if self.kis.is_mock else "실거래"
dry = " [DRY_RUN]" if os.getenv("DRY_RUN","true")=="true" else ""
@@ -659,17 +660,22 @@ class StockBot:
or "too many" in msg.lower()
)
async def _get_price_with_retry(self, ticker: str, purpose: str, attempts: int = 3):
delays = (1.2, 2.5, 5.0)
@staticmethod
def _is_retryable_price_error(err) -> bool:
msg = str(err).lower()
return StockBot._is_rate_limit_error(err) or "타임아웃" in msg or "timeout" in msg
async def _get_price_with_retry(self, ticker: str, purpose: str, attempts: int = 4):
delays = (1.3, 2.8, 5.0, 8.0)
for attempt in range(1, attempts + 1):
try:
return await self.kis.get_price(ticker)
except Exception as e:
if attempt >= attempts or not self._is_rate_limit_error(e):
if attempt >= attempts or not self._is_retryable_price_error(e):
raise
wait = delays[min(attempt - 1, len(delays) - 1)]
logger.warning(
"%s price retry %s/%s for %s after rate limit: %s",
"%s price retry %s/%s for %s after transient KIS error: %s",
purpose,
attempt,
attempts,
@@ -684,11 +690,12 @@ class StockBot:
name: str,
qty: int,
reason: str,
fill_price: float | None = None,
attempts: int = 3,
) -> dict:
delays = (1.2, 2.5, 5.0)
for attempt in range(1, attempts + 1):
result = await self.executor.sell(ticker, name, qty, reason)
result = await self.executor.sell(ticker, name, qty, reason, fill_price=fill_price)
if result.get("success"):
return result
error = result.get("error", "")
@@ -832,7 +839,7 @@ class StockBot:
valid_count = 0
for ticker in self.universe:
try:
price_info = await self.kis.get_price(ticker)
price_info = await self._get_price_with_retry(ticker, "TARGET")
self.strategy.set_today_open(ticker, price_info["open"])
target = self.strategy.get_target(ticker)
name = self.ticker_names.get(ticker, ticker)
@@ -1025,6 +1032,7 @@ class StockBot:
ticker=ticker, name=name,
qty=qty, reason=signal["reason"],
ai_boosted=signal.get("boosted", False),
fill_price=current,
)
if result["success"]:
@@ -1148,7 +1156,7 @@ class StockBot:
current: float, qty: int, reason: str):
"""실제 청산 실행"""
name = pos["name"]
result = await self._sell_with_retry(ticker, name, qty, reason)
result = await self._sell_with_retry(ticker, name, qty, reason, fill_price=current)
if not result["success"]:
return
@@ -1224,6 +1232,10 @@ class StockBot:
async def daily_summary(self):
"""당일 결산 로그 및 디스코드 알림 + DB 저장"""
today = datetime.now().strftime("%Y-%m-%d")
if today in self._daily_summary_dates:
logger.info("결산 이미 처리됨: %s", today)
return
with get_conn() as conn:
rows = conn.execute("""
SELECT pnl, fee, exit_reason FROM trades
@@ -1254,11 +1266,18 @@ class StockBot:
VALUES (?,?,?,?,?,?,?,?,?)
""", (today, total, wins, losses, gross_pnl, total_fee, net, mdd, stopped))
await notify_daily_summary(total, wins, losses, net)
self._daily_summary_dates.add(today)
try:
await notify_daily_summary(total, wins, losses, net)
except Exception as e:
logger.error("결산 Discord 요약 전송 실패: %s", e)
if exit_counts:
dist = " / ".join(f"{k}:{v}" for k, v in sorted(exit_counts.items()))
logger.info("Exit distribution: %s", dist)
await send(f"[청산분포] {dist}")
try:
await send(f"[청산분포] {dist}")
except Exception as e:
logger.error("청산분포 Discord 전송 실패: %s", e)
self.risk.reset_daily()
logger.info(f"결산: {total}회 / 승{wins}{losses} / {net:+,.0f}원 (fee {total_fee:,.0f}원)")