[2026-05-28] 진입 지연 및 섹터 필터 수정

This commit is contained in:
2026-05-28 19:50:01 +09:00
parent 2712d72525
commit 57e945ef28
2 changed files with 51 additions and 1 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ Claude Code가 이 파일을 읽고 필요시 수정함
# ── 변동성 돌파 ── # ── 변동성 돌파 ──
STRATEGY_K = 0.5 STRATEGY_K = 0.5
ENTRY_START = "09:15" ENTRY_START = "09:20"
ENTRY_END = "14:30" ENTRY_END = "14:30"
FORCE_EXIT = "14:50" # 절대 변경 불가 FORCE_EXIT = "14:50" # 절대 변경 불가
TP1_PCT = 0.020 # 1차 익절 +2.0% → 70% 매도 TP1_PCT = 0.020 # 1차 익절 +2.0% → 70% 매도
+50
View File
@@ -93,6 +93,7 @@ class StockBot:
self.positions = {} # ticker → {name, entry, qty, tp1_done, entry_time} self.positions = {} # ticker → {name, entry, qty, tp1_done, entry_time}
self.universe = [] # 감시 종목 리스트 self.universe = [] # 감시 종목 리스트
self.ticker_names = {} # ticker → 종목명 캐시 self.ticker_names = {} # ticker → 종목명 캐시
self.ticker_sectors = {} # ticker -> sector name cache
self.sl_tickers = set() # 당일 SL 당한 종목 — 재진입 차단 self.sl_tickers = set() # 당일 SL 당한 종목 — 재진입 차단
self.risk = None # RiskManager (잔고 확인 후 초기화) self.risk = None # RiskManager (잔고 확인 후 초기화)
self.running = False self.running = False
@@ -524,6 +525,18 @@ class StockBot:
# ETF/ETN/인버스/레버리지 종목 필터 # ETF/ETN/인버스/레버리지 종목 필터
_ETF_KEYWORDS = ('인버스', '레버리지', '선물', 'KODEX', 'TIGER', 'KBSTAR', _ETF_KEYWORDS = ('인버스', '레버리지', '선물', 'KODEX', 'TIGER', 'KBSTAR',
'HANARO', 'ARIRANG', 'KOSEF', 'SOL', 'ACE', 'RISE', 'PLUS') 'HANARO', 'ARIRANG', 'KOSEF', 'SOL', 'ACE', 'RISE', 'PLUS')
_SECTOR_FIELDS = (
"sector",
"sector_name",
"bstp_kor_isnm",
"bstp_cls_name",
"bstp_name",
)
_AVOID_SECTOR_NAME_HINTS = (
("\uac74\uc124", ("\uac74\uc124", "\ub300\uc6b0\uac74\uc124", "\ud604\ub300\uac74\uc124", "GS\uac74\uc124", "DL\uc774\uc564\uc528", "HDC\ud604\ub300\uc0b0\uc5c5\uac1c\ubc1c")),
("\uc804\uae30\uac00\uc2a4", ("\ud55c\uad6d\uc804\ub825", "\ud55c\uc804", "\ud55c\uad6d\uac00\uc2a4", "\uc9c0\uc5ed\ub09c\ubc29", "\uc804\uae30", "\uac00\uc2a4")),
("\uc8fc\ub958", ("\ud558\uc774\ud2b8\uc9c4\ub85c", "\ub86f\ub370\uce60\uc131", "\ubb34\ud559", "\ubcf4\ud574\uc591\uc870", "\uad6d\uc21c\ub2f9", "\uc8fc\ub958")),
)
@staticmethod @staticmethod
def _is_etf(ticker: str, name: str) -> bool: def _is_etf(ticker: str, name: str) -> bool:
@@ -531,6 +544,35 @@ class StockBot:
return True return True
return any(kw in name for kw in StockBot._ETF_KEYWORDS) return any(kw in name for kw in StockBot._ETF_KEYWORDS)
@classmethod
def _sector_from_rank_row(cls, row: dict) -> str:
for field in cls._SECTOR_FIELDS:
value = row.get(field)
if value:
return str(value).strip()
return ""
def _infer_avoid_sector_from_name(self, name: str) -> str:
compact_name = (name or "").replace(" ", "")
if not compact_name:
return ""
for sector in self.strategy.context.get("avoid_sectors", []):
sector_name = str(sector or "").strip()
if not sector_name:
continue
compact_sector = sector_name.replace(" ", "")
for key, hints in self._AVOID_SECTOR_NAME_HINTS:
if key in compact_sector and any(hint in compact_name for hint in hints):
return sector_name
generic_token = compact_sector.replace("\uc5c5", "")
if len(generic_token) >= 2 and generic_token in compact_name:
return sector_name
return ""
async def update_universe(self): async def update_universe(self):
"""종목 풀 갱신 + 전일 데이터 수집""" """종목 풀 갱신 + 전일 데이터 수집"""
logger.info("유니버스 갱신 시작") logger.info("유니버스 갱신 시작")
@@ -541,6 +583,9 @@ class StockBot:
# 종목명 캐시 갱신 + ETF 필터 # 종목명 캐시 갱신 + ETF 필터
for r in rank: for r in rank:
self.ticker_names[r["ticker"]] = r["name"] self.ticker_names[r["ticker"]] = r["name"]
sector = self._sector_from_rank_row(r)
if sector:
self.ticker_sectors[r["ticker"]] = sector
tickers = [r["ticker"] for r in rank tickers = [r["ticker"] for r in rank
if not self._is_etf(r["ticker"], r["name"])] if not self._is_etf(r["ticker"], r["name"])]
@@ -732,11 +777,16 @@ class StockBot:
price_info = await self.kis.get_price(ticker) # rate limiter가 자동 throttle price_info = await self.kis.get_price(ticker) # rate limiter가 자동 throttle
current = price_info["current"] current = price_info["current"]
name = self.ticker_names.get(ticker, ticker) name = self.ticker_names.get(ticker, ticker)
sector = (
self.ticker_sectors.get(ticker, "")
or self._infer_avoid_sector_from_name(name)
)
signal = self.strategy.check_entry( signal = self.strategy.check_entry(
ticker=ticker, ticker=ticker,
name=name, name=name,
current_price=current, current_price=current,
sector=sector,
) )
if not signal["signal"]: if not signal["signal"]: