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:
2026-03-29 05:21:23 +09:00
parent 760d1906ed
commit 0aebca7ff0
17 changed files with 3816 additions and 200 deletions

View File

@@ -1,3 +1,6 @@
import os
import signal
import threading
import uvicorn
import multiprocessing
from fastapi import FastAPI, Request
@@ -21,11 +24,11 @@ news_collector = None
watchdog = None
def run_trading_bot(ipc_lock, command_queue, shutdown_event):
def run_trading_bot(ipc_lock, command_queue, shutdown_event, eod_event=None):
"""트레이딩 봇 실행 래퍼"""
ProcessTracker.register("Trading Bot Main")
bot = AutoTradingBot(ipc_lock=ipc_lock, command_queue=command_queue,
shutdown_event=shutdown_event)
shutdown_event=shutdown_event, eod_event=eod_event)
bot.loop()
@@ -53,11 +56,12 @@ async def lifespan(app: FastAPI):
ipc_lock = multiprocessing.Lock()
command_queue = multiprocessing.Queue()
shutdown_event = multiprocessing.Event()
eod_event = multiprocessing.Event() # [v3.1] EOD 셧다운 시그널
print("[Server] Starting AI Trading Bot & Telegram Bot...")
# 5. 자식 프로세스 생성
bot_args = (ipc_lock, command_queue, shutdown_event)
bot_args = (ipc_lock, command_queue, shutdown_event, eod_event)
telegram_args = (ipc_lock, command_queue, shutdown_event)
bot_process = multiprocessing.Process(
@@ -77,6 +81,25 @@ async def lifespan(app: FastAPI):
messenger.send_message("[Server Started] Windows AI Server Online.")
# [v3.1] EOD 모니터 스레드: 봇이 EOD 시그널을 보내면 서버 프로세스 자동 종료
_server_pid = os.getpid()
def _eod_monitor():
"""eod_event 감지 시 SIGTERM으로 uvicorn 우아하게 종료"""
while not shutdown_event.is_set():
if eod_event.is_set():
print("[Server] EOD 시그널 수신 — 서버 종료 중 (15초 후)...")
import time as _time
_time.sleep(15) # 자식 프로세스 정리 시간
print(f"[Server] SIGTERM → PID {_server_pid}")
os.kill(_server_pid, signal.SIGTERM)
return
import time as _time
_time.sleep(5)
_eod_thread = threading.Thread(target=_eod_monitor, daemon=True, name="EODMonitor")
_eod_thread.start()
yield
# [Shutdown]