v3.1 과매수 방지, 앙상블 학습, KRX 캘린더 기반 장중 전용 운영 구현
[잔고 관리] - _today_buy_total 인스턴스 변수로 당일 누적 매수 추적 (KIS T+2 미차감 보완) - MAX_BUY_PER_CYCLE, MAX_DAILY_BUY_RATIO 설정 추가 - available_deposit = max_daily_buy - effective_today_buy 계산 [앙상블 & 포지션 사이징] - AdaptiveEnsemble 실제 연동 (하드코딩 가중치 제거) - Kelly Criterion Half-Kelly 포지션 비중 계산 - SignalWeights.normalize() Water-Filling 알고리즘으로 경계 위반 해결 - _accuracy_weighted() 크기 가중 정확도로 통일 - ensemble_weights.json → ensemble_history.json 통합 [LLM 클라이언트] - GeminiLLMClient 추가 (Gemini → Ollama 폴백 체인) - _class_last_call_ts 클래스 변수로 워커 재시작 후에도 스로틀 유지 - Ollama 미실행 조기 감지 및 명확한 오류 메시지 [KIS API] - 모든 requests.get/post에 timeout=Config.HTTP_TIMEOUT 적용 - get_balance()에 today_buy_amt 필드 추가 [장중 전용 운영] - KRXCalendar: exchange_calendars 기반, 2024~2026 공휴일 하드코딩 폴백 - EOD 셧다운: 15:35에 전체 상태 저장 후 서버 자동 종료 - Watchdog: .eod_date 마커로 EOD 후 재시작 차단 - daily_launcher.py: 매일 08:30 실행, 휴장일 감지 후 봇 미시작 - Windows 작업 스케줄러 WebAI_DailyLauncher 등록 [텔레그램 스킬 수정] - PYTHONIOENCODING=utf-8 서브프로세스 환경 설정 (cp949 이모지 오류 해결) - /regime: IPC macro_indices 파싱 구현, --json 모드 input() 블로킹 제거 - /weights: ensemble_history.json 형식 파싱 업데이트 - /model_health: glob 패턴 *_v3.pt 수정 - /postmortem: 거래 없을 때 빈 JSON 출력으로 Telegram 오류 해결 - /macro: price=0 시 prev_close 폴백 표시 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,11 +6,16 @@
|
||||
"""
|
||||
import os
|
||||
import time
|
||||
import datetime
|
||||
import threading
|
||||
from pathlib import Path
|
||||
from multiprocessing.shared_memory import SharedMemory
|
||||
|
||||
from modules.config import Config
|
||||
|
||||
# EOD 마커 파일: 오늘 장 마감 후 봇이 기록, Watchdog가 재시작 여부 결정에 사용
|
||||
_EOD_DATE_FILE = Path("data") / ".eod_date"
|
||||
|
||||
|
||||
class ProcessTracker:
|
||||
"""메모리 기반 프로세스 추적기"""
|
||||
@@ -136,6 +141,17 @@ class ProcessWatchdog:
|
||||
entry = self._watched.get(name)
|
||||
return entry['process'] if entry else None
|
||||
|
||||
@staticmethod
|
||||
def is_eod_today() -> bool:
|
||||
"""오늘 EOD 마커 파일이 존재하면 True (장 마감 셧다운 → 재시작 차단)"""
|
||||
try:
|
||||
if not _EOD_DATE_FILE.exists():
|
||||
return False
|
||||
eod_date = datetime.date.fromisoformat(_EOD_DATE_FILE.read_text().strip())
|
||||
return eod_date >= datetime.date.today()
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _watchdog_loop(self):
|
||||
"""주기적으로 자식 프로세스 상태 확인"""
|
||||
import multiprocessing
|
||||
@@ -150,10 +166,15 @@ class ProcessWatchdog:
|
||||
if proc.is_alive():
|
||||
continue
|
||||
|
||||
# 프로세스가 죽었음
|
||||
# 프로세스가 종료됨
|
||||
exit_code = proc.exitcode
|
||||
restart_count = entry['restart_count']
|
||||
|
||||
# [EOD 차단] 오늘 장 마감 셧다운이면 재시작하지 않음
|
||||
if ProcessWatchdog.is_eod_today():
|
||||
print(f"[Watchdog] {name}: EOD 셧다운 감지 — 재시작 건너뜀.")
|
||||
continue
|
||||
|
||||
if restart_count >= Config.MAX_RESTART_COUNT:
|
||||
print(f"[Watchdog] {name} crashed (exit={exit_code}). "
|
||||
f"Max restarts ({Config.MAX_RESTART_COUNT}) reached. Giving up.")
|
||||
|
||||
Reference in New Issue
Block a user