From 57e945ef28aaec5eac745204caa1bf1285e72767 Mon Sep 17 00:00:00 2001 From: jongjae Date: Thu, 28 May 2026 19:50:01 +0900 Subject: [PATCH] =?UTF-8?q?[2026-05-28]=20=EC=A7=84=EC=9E=85=20=EC=A7=80?= =?UTF-8?q?=EC=97=B0=20=EB=B0=8F=20=EC=84=B9=ED=84=B0=20=ED=95=84=ED=84=B0?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/config.py | 2 +- app/main.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/app/config.py b/app/config.py index 8566867..fff8329 100644 --- a/app/config.py +++ b/app/config.py @@ -5,7 +5,7 @@ Claude Code가 이 파일을 읽고 필요시 수정함 # ── 변동성 돌파 ── STRATEGY_K = 0.5 -ENTRY_START = "09:15" +ENTRY_START = "09:20" ENTRY_END = "14:30" FORCE_EXIT = "14:50" # 절대 변경 불가 TP1_PCT = 0.020 # 1차 익절 +2.0% → 70% 매도 diff --git a/app/main.py b/app/main.py index b0be194..d7ca8e6 100644 --- a/app/main.py +++ b/app/main.py @@ -93,6 +93,7 @@ class StockBot: self.positions = {} # ticker → {name, entry, qty, tp1_done, entry_time} self.universe = [] # 감시 종목 리스트 self.ticker_names = {} # ticker → 종목명 캐시 + self.ticker_sectors = {} # ticker -> sector name cache self.sl_tickers = set() # 당일 SL 당한 종목 — 재진입 차단 self.risk = None # RiskManager (잔고 확인 후 초기화) self.running = False @@ -524,6 +525,18 @@ class StockBot: # ETF/ETN/인버스/레버리지 종목 필터 _ETF_KEYWORDS = ('인버스', '레버리지', '선물', 'KODEX', 'TIGER', 'KBSTAR', '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 def _is_etf(ticker: str, name: str) -> bool: @@ -531,6 +544,35 @@ class StockBot: return True 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): """종목 풀 갱신 + 전일 데이터 수집""" logger.info("유니버스 갱신 시작") @@ -541,6 +583,9 @@ class StockBot: # 종목명 캐시 갱신 + ETF 필터 for r in rank: 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 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 current = price_info["current"] 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( ticker=ticker, name=name, current_price=current, + sector=sector, ) if not signal["signal"]: