[2026-06-02] 결산 중복과 모의투자 호출 안정화
This commit is contained in:
+28
-9
@@ -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}원)")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user