변경 사항 (Changes): 구문 오류(Syntax error) 및 토큰 낭비를 방지하기 위해 에이전트 쉘(Agent shell)과 파이썬 코드 스니펫에 다수의 신규 안전 규칙(Safety rules)을 추가함. 스키마 검증 및 적절한 SQL 포맷팅을 보장하기 위해 임시(Ad-hoc) 데이터베이스 쿼리 작성 가이드라인을 도입함. 코드 수정 후 UI 기능이 정상 작동하는지 확인하기 위해, 백테스트 웹 서비스 재시작 및 브라우저 검증에 대한 새로운 규칙을 구현함. 시스템 전반의 무결성(Integrity)을 유지하기 위해 실전 매매(Live trading), 웹 백테스팅, 파라미터 탐색(Parameter searches) 간의 일관성 검사(Consistency checks) 체계를 확립함. 기대 효과 (Impact): 이러한 개선 사항들은 트레이딩 시스템의 견고성(Robustness)과 신뢰성을 향상시키며, 에러 발생을 최소화하고 다양한 시스템 컴포넌트 간의 원활한 상호작용을 보장함.
143 lines
3.9 KiB
Python
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
|