main_server.py가 중복 실행되면서 좀비 프로세스가 수행되는 오류 해결, process_tracker.py가 감시하면서 할당되지 않은 pid가 존재하면 좀비프로세스로 판단하여 kill

This commit is contained in:
2026-02-11 07:48:06 +09:00
parent 7f2f575ec8
commit 4fd0aa91bc
8 changed files with 689 additions and 371 deletions

View File

@@ -3,6 +3,7 @@ import os
import sys
import json
from concurrent.futures import ProcessPoolExecutor
from concurrent.futures.process import BrokenProcessPool
from datetime import datetime
# 모듈 임포트
@@ -24,12 +25,26 @@ except ImportError:
class ThemeManager:
def get_themes(self, code): return []
# 워커 프로세스 초기화 함수
def init_worker():
try:
from modules.utils.process_tracker import ProcessTracker
ProcessTracker.register("Trading Bot Worker")
except: pass
class AutoTradingBot:
def __init__(self):
# 1. 서비스 초기화
self.kis = KISClient()
self.news = NewsCollector()
self.executor = ProcessPoolExecutor(max_workers=4)
# [안정성] GPU OOM 방지를 위해 워커 수 축소 (4 -> 2)
# [식별] 워커 프로세스 이름 등록
self.executor = ProcessPoolExecutor(max_workers=2, initializer=init_worker)
# 워커 프로세스 워밍업 (PID 등록 유도)
try:
list(self.executor.map(lambda x: x, range(2)))
except: pass
self.messenger = TelegramMessenger()
self.theme_manager = ThemeManager()
self.ollama_monitor = OllamaManager() # GPU 모니터링용
@@ -67,8 +82,6 @@ class AutoTradingBot:
from modules.analysis.deep_learning import PricePredictor
PricePredictor.verify_hardware()
# 텔레그램 명령 서버 시작 (Server에서 관리하도록 변경)
# self.start_telegram_command_server()
pass
def load_trade_history(self):
@@ -129,6 +142,17 @@ class AutoTradingBot:
self.messenger.send_message(report)
self.report_sent = True
def restart_executor(self):
"""프로세스 풀 복구"""
print("🔄 [Bot] Restarting Process Executor...")
try:
self.executor.shutdown(wait=False)
except:
pass
# 워커 재시작
self.executor = ProcessPoolExecutor(max_workers=2, initializer=init_worker)
print("✅ [Bot] Process Executor Restarted.")
def run_cycle(self):
now = datetime.now()
@@ -261,75 +285,96 @@ class AutoTradingBot:
# [수정] 실시간 잔고 추적용 변수 (매수 시 차감)
tracking_deposit = int(balance.get("deposit", 0))
for ticker, name in target_dict.items():
prices = self.kis.get_daily_price(ticker)
if not prices: continue
future = self.executor.submit(analyze_stock_process, ticker, prices, news_data)
analysis_tasks.append(future)
# 결과 처리
for future in analysis_tasks:
try:
res = future.result()
ticker_name = target_dict.get(res['ticker'], 'Unknown')
print(f"📊 [{ticker_name}] Score: {res['score']:.2f} ({res['decision']})")
try:
for ticker, name in target_dict.items():
prices = self.kis.get_daily_price(ticker)
if not prices: continue
if res['decision'] == "BUY":
if is_crash: continue
# [신규] 외인 수급 분석
investor_trend = self.kis.get_investor_trend(ticker)
future = self.executor.submit(analyze_stock_process, ticker, prices, news_data, investor_trend)
analysis_tasks.append(future)
# 결과 처리
for future in analysis_tasks:
try:
res = future.result()
ticker_name = target_dict.get(res['ticker'], 'Unknown')
print(f"📊 [{ticker_name}] Score: {res['score']:.2f} ({res['decision']})")
# 매수 로직 (예수금 체크 추가)
current_qty = 0
if res['ticker'] in current_holdings:
current_qty = current_holdings[res['ticker']]['qty']
if res['decision'] == "BUY":
if is_crash: continue
current_price = float(res['current_price'])
if current_price <= 0: continue
# 매수 수량 결정 (기본 1주, 추후 금액 기반으로 변경 가능)
qty = 1
required_amount = current_price * qty
# 예수금 확인
if tracking_deposit < required_amount:
print(f"💰 [Skip Buy] 예수금 부족 ({ticker_name}): 필요 {required_amount:,.0f} > 잔고 {tracking_deposit:,.0f}")
continue
# 매수 로직 (예수금 체크 추가)
current_qty = 0
if res['ticker'] in current_holdings:
current_qty = current_holdings[res['ticker']]['qty']
current_price = float(res['current_price'])
if current_price <= 0: continue
# 매수 수량 결정 (기본 1주, 추후 금액 기반으로 변경 가능)
qty = 1
required_amount = current_price * qty
# 예수금 확인
if tracking_deposit < required_amount:
print(f"💰 [Skip Buy] 예수금 부족 ({ticker_name}): 필요 {required_amount:,.0f} > 잔고 {tracking_deposit:,.0f}")
continue
print(f"🚀 Buying {ticker_name} {qty}ea")
# 실제 주문
order = self.kis.buy_stock(res['ticker'], qty)
if order.get("status"):
self.messenger.send_message(f"🚀 **[BUY]** {ticker_name} {qty}\n"
f" • 가격: {current_price:,.0f}")
print(f"🚀 Buying {ticker_name} {qty}ea")
self.daily_trade_history.append({
"action": "BUY",
"name": ticker_name,
"qty": qty,
"price": current_price
})
self.save_trade_history()
# [중요] 가상 잔고 차감 (연속 매수 시 초과 방지)
tracking_deposit -= required_amount
elif res['decision'] == "SELL":
print(f"📉 Selling {ticker_name} (Simulation)")
# 매도 로직 (필요 시 추가)
except Exception as e:
print(f"❌ Analysis Error: {e}")
# 실제 주문
order = self.kis.buy_stock(res['ticker'], qty)
if order.get("status"):
self.messenger.send_message(f"🚀 **[BUY]** {ticker_name} {qty}\n"
f" • 가격: {current_price:,.0f}")
self.daily_trade_history.append({
"action": "BUY",
"name": ticker_name,
"qty": qty,
"price": current_price
})
self.save_trade_history()
# [중요] 가상 잔고 차감 (연속 매수 시 초과 방지)
tracking_deposit -= required_amount
elif res['decision'] == "SELL":
print(f"📉 Selling {ticker_name} (Simulation)")
# 매도 로직 (필요 시 추가)
except BrokenProcessPool:
raise # 상위 레벨에서 처리
except Exception as e:
print(f"❌ Analysis Worker Error: {e}")
except BrokenProcessPool:
print("⚠️ [Bot] Worker Process Crashed (OOM, CUDA Error?). Restarting Executor...")
self.restart_executor()
except KeyboardInterrupt:
raise
except Exception as e:
print(f"❌ Cycle Loop Error: {e}")
def loop(self):
print(f"🤖 Bot Module Started (PID: {os.getpid()})")
self.messenger.send_message("🤖 **[Bot Started]** 리팩토링된 봇이 시작되었습니다.")
while True:
try:
self.run_cycle()
except Exception as e:
print(f"⚠️ Loop Error: {e}")
self.messenger.send_message(f"⚠️ Loop Error: {e}")
time.sleep(60)
try:
while True:
try:
self.run_cycle()
except Exception as e:
print(f"⚠️ Loop Error: {e}")
self.messenger.send_message(f"⚠️ Loop Error: {e}")
time.sleep(60)
except KeyboardInterrupt:
print("🛑 [Bot] Stopped by User.")
finally:
print("🛑 [Bot] Shutting down executor...")
self.executor.shutdown(wait=False)
print("✅ [Bot] Executor shutdown complete.")
def start_telegram_command_server(self):
"""텔레그램 봇 프로세스 실행 (독립 프로세스)"""