feat(backtest): 대대적인 Optuna 백테스트 웹 UI 및 백엔드 파이프라인 개편
- Web UI: - Optuna 탭 추가 및 mode_combo (최빈값 조합), 사후합격 Top 10 시각화 기능 - 파라미터 분포(p25~p75, median, mode) 히스토그램 및 과적합(Overfit) 위험도 진단 UI 신설 - 체크박스 렌더링 깨짐 현상을 네이티브(appearance: auto)로 강제 복구 (CSS) - 다단 트레일링 스탑, 꼬리 진입/돌파 손절 등 고급 조건 설정 폼 UI 고도화 - Backend (Optuna Jobs): - CLI 환경에서 구동된 Optuna json 결과물을 웹 대시보드로 읽어오는 import 기능 강화 - JSON 메타데이터에 sort_by, mode, 호가 적용 여부 등 핵심 파라미터 파싱 누락 수정 - optuna_mode_combo.py 등 최빈값 조합 및 후보군 2차 검증을 위한 신규 모듈 추가 - DB & Execution: - WebSocket 호가/틱 피드 수집 통계(api_feed_collect_stats) 메모리 캐시 최적화 - KIS client 접속 키(approval_key) 등 인프라스트럭처 안정성 및 공유 관리 구조 개선 - 테스트 및 디버깅용 briefing 마크다운 자동 생성 기능 추가
This commit is contained in:
@@ -124,7 +124,7 @@ def token_covers_session(
|
||||
return exp_dt >= deadline
|
||||
|
||||
|
||||
def get_token_status(is_mock: bool) -> dict:
|
||||
def get_token_status(is_mock: bool, current_app_key: str = None) -> dict:
|
||||
"""
|
||||
캐시 파일 상태 반환.
|
||||
반환: valid=세션커버(ensure 재사용 기준), usable=만료 전 API 사용 가능
|
||||
@@ -143,6 +143,18 @@ def get_token_status(is_mock: bool) -> dict:
|
||||
token = cache.get("access_token", "")
|
||||
expired_s = cache.get("access_token_token_expired", "")
|
||||
exp_dt = _parse_expired(expired_s)
|
||||
app_key_prefix = cache.get("app_key_prefix", "")
|
||||
|
||||
if current_app_key and app_key_prefix:
|
||||
if not current_app_key.startswith(app_key_prefix):
|
||||
return {
|
||||
"valid": False,
|
||||
"usable": False,
|
||||
"token": "",
|
||||
"expires": "앱키변경됨",
|
||||
"expires_in_h": -999,
|
||||
}
|
||||
|
||||
if not token or exp_dt is None:
|
||||
return {
|
||||
"valid": False,
|
||||
@@ -299,6 +311,7 @@ def _issue_token(app_key: str, app_secret: str, is_mock: bool) -> bool:
|
||||
"access_token_token_expired": exp,
|
||||
"mock": is_mock,
|
||||
"issued_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"app_key_prefix": app_key[:8] if app_key else "",
|
||||
}, ensure_ascii=False, indent=2),
|
||||
encoding="utf-8",
|
||||
)
|
||||
@@ -321,7 +334,14 @@ def ensure_token(is_mock: bool, env: dict = None) -> bool:
|
||||
단일 모드(실전/모의) 토큰: 오늘 세션을 덮으면 재사용, 아니면만 발급.
|
||||
1일 1회 원칙 — 충분하면 tokenP 호출 없음.
|
||||
"""
|
||||
status = get_token_status(is_mock)
|
||||
if env is None:
|
||||
env = _load_env()
|
||||
|
||||
key_suffix = "MOCK" if is_mock else "REAL"
|
||||
app_key = str(env.get(f"KIS_APP_KEY_{key_suffix}", "") or "").strip()
|
||||
app_secret = str(env.get(f"KIS_APP_SECRET_{key_suffix}", "") or "").strip()
|
||||
|
||||
status = get_token_status(is_mock, current_app_key=app_key)
|
||||
mode = "모의" if is_mock else "실전"
|
||||
|
||||
if status["valid"]:
|
||||
@@ -351,7 +371,7 @@ def ensure_token(is_mock: bool, env: dict = None) -> bool:
|
||||
return False
|
||||
try:
|
||||
# 잠금 획득 후 다시 확인 (다른 프로세스가 갱신했을 수 있음)
|
||||
status = get_token_status(is_mock)
|
||||
status = get_token_status(is_mock, current_app_key=app_key)
|
||||
if status["valid"]:
|
||||
logger.info(f"🔑 {mode} 토큰 이미 갱신됨 (다른 프로세스) → 재사용")
|
||||
return True
|
||||
@@ -427,6 +447,15 @@ class KisTokenManager:
|
||||
self._lock = threading.Lock()
|
||||
self._token: Optional[str] = None
|
||||
self._expiry: Optional[datetime] = None
|
||||
self._app_key_prefix: Optional[str] = None
|
||||
|
||||
env = _load_env()
|
||||
if env:
|
||||
suffix = "MOCK" if is_mock else "REAL"
|
||||
key = str(env.get(f"KIS_APP_KEY_{suffix}", "")).strip()
|
||||
if key:
|
||||
self._app_key_prefix = key[:8]
|
||||
|
||||
self._load_from_file() # 재시작 후에도 기존 토큰 재사용
|
||||
|
||||
# ── 내부 ──────────────────────────────────────────────────────
|
||||
@@ -438,6 +467,14 @@ class KisTokenManager:
|
||||
data = json.loads(self._cache_path.read_text(encoding="utf-8"))
|
||||
token = data.get("access_token", "")
|
||||
exp_dt = _parse_expired(data.get("access_token_token_expired", ""))
|
||||
|
||||
# 앱키 변경 감지: 캐시된 app_key_prefix 가 있고, 현재 prefix 와 다르면 무시
|
||||
cached_prefix = data.get("app_key_prefix", "")
|
||||
if self._app_key_prefix and cached_prefix:
|
||||
if self._app_key_prefix != cached_prefix:
|
||||
logger.warning("🔑 [%s] 앱키 변경 감지 → 기존 토큰 캐시 폐기", self._mode_str)
|
||||
return
|
||||
|
||||
if token and exp_dt:
|
||||
self._token = token
|
||||
self._expiry = exp_dt
|
||||
|
||||
Reference in New Issue
Block a user