[2026-05-18] DRY_RUN 가격=0 버그 수정 / 종목명 캐시 / ETF 유니버스 제외
This commit is contained in:
@@ -226,8 +226,10 @@ class KISClient:
|
||||
dry_run = os.getenv("DRY_RUN", "true").lower() == "true"
|
||||
|
||||
if dry_run:
|
||||
logger.info(f"[DRY_RUN] 매수 {ticker} {qty}주 @ {price or '시장가'}")
|
||||
return {"dry_run": True, "ticker": ticker, "qty": qty}
|
||||
price_info = await self.get_price(ticker)
|
||||
current = price_info["current"]
|
||||
logger.info(f"[DRY_RUN] 매수 {ticker} {qty}주 @ {current:,}원")
|
||||
return {"dry_run": True, "ticker": ticker, "qty": qty, "entry_price": current}
|
||||
|
||||
# 모의/실거래 TR 구분
|
||||
tr_id = "VTTC0802U" if self.is_mock else "TTTC0802U"
|
||||
@@ -259,8 +261,10 @@ class KISClient:
|
||||
dry_run = os.getenv("DRY_RUN", "true").lower() == "true"
|
||||
|
||||
if dry_run:
|
||||
logger.info(f"[DRY_RUN] 매도 {ticker} {qty}주 @ {price or '시장가'}")
|
||||
return {"dry_run": True, "ticker": ticker, "qty": qty}
|
||||
price_info = await self.get_price(ticker)
|
||||
current = price_info["current"]
|
||||
logger.info(f"[DRY_RUN] 매도 {ticker} {qty}주 @ {current:,}원")
|
||||
return {"dry_run": True, "ticker": ticker, "qty": qty, "exit_price": current}
|
||||
|
||||
tr_id = "VTTC0801U" if self.is_mock else "TTTC0801U"
|
||||
|
||||
|
||||
+27
-10
@@ -77,12 +77,13 @@ from app.config import (
|
||||
class StockBot:
|
||||
def __init__(self):
|
||||
self.kis = KISClient()
|
||||
self.executor = OrderExecutor(self.kis)
|
||||
self.strategy = VolatilityBreakout()
|
||||
self.positions = {} # ticker → {name, entry, qty, tp1_done, entry_time}
|
||||
self.universe = [] # 감시 종목 리스트
|
||||
self.risk = None # RiskManager (잔고 확인 후 초기화)
|
||||
self.running = False
|
||||
self.executor = OrderExecutor(self.kis)
|
||||
self.strategy = VolatilityBreakout()
|
||||
self.positions = {} # ticker → {name, entry, qty, tp1_done, entry_time}
|
||||
self.universe = [] # 감시 종목 리스트
|
||||
self.ticker_names = {} # ticker → 종목명 캐시
|
||||
self.risk = None # RiskManager (잔고 확인 후 초기화)
|
||||
self.running = False
|
||||
|
||||
mode = "모의투자" if self.kis.is_mock else "실거래"
|
||||
dry = " [DRY_RUN]" if os.getenv("DRY_RUN","true")=="true" else ""
|
||||
@@ -109,12 +110,28 @@ class StockBot:
|
||||
# 유니버스 갱신 (08:30)
|
||||
# ─────────────────────────────────────────
|
||||
|
||||
# ETF/ETN/인버스/레버리지 종목 필터
|
||||
_ETF_KEYWORDS = ('인버스', '레버리지', '선물', 'KODEX', 'TIGER', 'KBSTAR',
|
||||
'HANARO', 'ARIRANG', 'KOSEF', 'SOL', 'ACE', 'RISE', 'PLUS')
|
||||
|
||||
@staticmethod
|
||||
def _is_etf(ticker: str, name: str) -> bool:
|
||||
if ticker.startswith('Q') or len(ticker) != 6: # ETN or 비정상 코드
|
||||
return True
|
||||
return any(kw in name for kw in StockBot._ETF_KEYWORDS)
|
||||
|
||||
async def update_universe(self):
|
||||
"""종목 풀 갱신 + 전일 데이터 수집"""
|
||||
logger.info("유니버스 갱신 시작")
|
||||
try:
|
||||
rank = await self.kis.get_volume_rank(top_n=MAX_UNIVERSE)
|
||||
tickers = [r["ticker"] for r in rank]
|
||||
# ETF 필터 후 MAX_UNIVERSE 확보 위해 여유분 요청
|
||||
rank = await self.kis.get_volume_rank(top_n=MAX_UNIVERSE + 20)
|
||||
|
||||
# 종목명 캐시 갱신 + ETF 필터
|
||||
for r in rank:
|
||||
self.ticker_names[r["ticker"]] = r["name"]
|
||||
tickers = [r["ticker"] for r in rank
|
||||
if not self._is_etf(r["ticker"], r["name"])]
|
||||
|
||||
ctx = self.strategy.context
|
||||
blacklist = ctx.get("blacklist_tickers", [])
|
||||
@@ -127,7 +144,7 @@ class StockBot:
|
||||
)[:MAX_UNIVERSE]
|
||||
|
||||
self.universe = tickers
|
||||
logger.info(f"유니버스: {len(tickers)}종목")
|
||||
logger.info(f"유니버스: {len(tickers)}종목 (ETF 제외)")
|
||||
|
||||
# 전일 날짜 계산
|
||||
from datetime import timedelta
|
||||
@@ -246,7 +263,7 @@ class StockBot:
|
||||
try:
|
||||
price_info = await self.kis.get_price(ticker) # rate limiter가 자동 throttle
|
||||
current = price_info["current"]
|
||||
name = price_info.get("name", ticker)
|
||||
name = self.ticker_names.get(ticker, ticker)
|
||||
|
||||
signal = self.strategy.check_entry(
|
||||
ticker=ticker,
|
||||
|
||||
Reference in New Issue
Block a user