Files
kis_bot/kis_trader/utils/non_stock.py
Your Name fc27e726f9 feat: 새로운 안전 규칙 및 최적화 적용을 통한 트레이딩 시스템 개선
변경 사항 (Changes):

구문 오류(Syntax error) 및 토큰 낭비를 방지하기 위해 에이전트 쉘(Agent shell)과 파이썬 코드 스니펫에 다수의 신규 안전 규칙(Safety rules)을 추가함.

스키마 검증 및 적절한 SQL 포맷팅을 보장하기 위해 임시(Ad-hoc) 데이터베이스 쿼리 작성 가이드라인을 도입함.

코드 수정 후 UI 기능이 정상 작동하는지 확인하기 위해, 백테스트 웹 서비스 재시작 및 브라우저 검증에 대한 새로운 규칙을 구현함.

시스템 전반의 무결성(Integrity)을 유지하기 위해 실전 매매(Live trading), 웹 백테스팅, 파라미터 탐색(Parameter searches) 간의 일관성 검사(Consistency checks) 체계를 확립함.

기대 효과 (Impact):

이러한 개선 사항들은 트레이딩 시스템의 견고성(Robustness)과 신뢰성을 향상시키며, 에러 발생을 최소화하고 다양한 시스템 컴포넌트 간의 원활한 상호작용을 보장함.
2026-07-17 01:09:09 +09:00

143 lines
3.9 KiB
Python

"""
비본주(우선주·ETF·ETN·스팩) 판별 — 후보 매수 차단 공용.
키움 조건검색은 종목명 없이 code 만 주는 경우가 많아(name==code),
이름 끝 '' 만으로는 우선주를 못 막는다. 코드 끝자리 규칙으로 보완한다.
KRX 단축코드 끝자리(종목구분):
- 보통주: 대개 '0'
- 우선주: '5','7','9' (구형) + 'K','L','M'… (2013년 이후 알파벳)
"""
from __future__ import annotations
from typing import FrozenSet
from .env import get_env_bool, get_env_from_db
# ETF/ETN 브랜드·유형 (이름 부분일치). '파워' 단독은 뉴파워프라즈마 오탐 → 넣지 않음.
_DEFAULT_NON_STOCK_NAME_KEYWORDS = (
"ETN",
"ETF",
"레버리지",
"인버스",
"2X",
"3X",
"선물",
"KODEX",
"TIGER",
"KBSTAR",
"ARIRANG",
"HANARO",
"SOL ",
"KOSEF",
"ACE ",
"KINDEX",
"RISE ",
"PLUS ",
"TIMEFOLIO",
"히어로",
"SMART",
"TREX",
"WON",
"KOACT",
)
def _pref_digit_suffixes() -> FrozenSet[str]:
"""우선주 숫자 끝자리 — NON_STOCK_PREF_CODE_SUFFIXES (기본 5,7,9)."""
raw = get_env_from_db("NON_STOCK_PREF_CODE_SUFFIXES", "5,7,9") or "5,7,9"
return frozenset(s.strip().upper() for s in str(raw).split(",") if s.strip())
def is_preferred_code(code: str) -> bool:
"""종목코드만으로 우선주 여부 (name 없어도 판단)."""
c = (code or "").strip()
if len(c) < 1:
return False
last = c[-1].upper()
if last in _pref_digit_suffixes():
return True
# K,L,M… 알파벳 끝 = 신형 우선주 코드
if get_env_bool("NON_STOCK_EXCLUDE_ALPHA_SUFFIX", True) and last.isalpha():
return True
return False
def is_etn_code(code: str) -> bool:
c = (code or "").strip()
return len(c) == 6 and c[0] == "7"
def _name_keywords() -> tuple:
"""NON_STOCK_NAME_KEYWORDS — 콤마 구분. 비우면 기본 튜플."""
raw = (get_env_from_db("NON_STOCK_NAME_KEYWORDS", "") or "").strip()
if not raw:
return _DEFAULT_NON_STOCK_NAME_KEYWORDS
parts = tuple(s.strip() for s in raw.split(",") if s.strip())
return parts or _DEFAULT_NON_STOCK_NAME_KEYWORDS
def _name_hits_keyword(name: str) -> bool:
nm = name.upper()
for k in _name_keywords():
if not k:
continue
if k.isascii():
if k.upper() in nm:
return True
elif k in name:
return True
return False
def is_non_stock(name: str, code: str) -> bool:
"""ETN/ETF/스팩/우선주 등 매수 금지 종목이면 True.
name 이 비었거나 name==code 여도 코드 규칙으로 우선주·ETN 을 잡는다.
"""
code = (code or "").strip()
name = (name or "").strip()
if not code:
return True
if is_preferred_code(code):
return True
if is_etn_code(code):
return True
if name:
if _name_hits_keyword(name):
return True
nm = name.upper()
if "스팩" in name or "SPAC" in nm:
return True
# 우선주 표기 (우 / 우B / 우C …)
if name.endswith("") or "우B" in name or "우C" in name or name.endswith("(전환)"):
return True
return False
def is_unmanageable_holding(name: str, code: str) -> bool:
"""보유 동기화에서 빼서 봇이 매도하지 않을 종목 (시세 불가 ETF/ETN·스팩).
우선주는 시세·매도 가능 → False. (실수로 산 우선주는 매도로 정리)
"""
code = (code or "").strip()
name = (name or "").strip()
if is_etn_code(code):
return True
if not name or name == code:
return False
nm = name.upper()
for k in ("ETN", "ETF", "레버리지", "인버스", "KODEX", "TIGER", "KBSTAR"):
if k.isascii():
if k in nm:
return True
elif k in name:
return True
if "스팩" in name or "SPAC" in nm:
return True
return False