main_server.py가 중복 실행되면서 좀비 프로세스가 수행되는 오류 해결, process_tracker.py가 감시하면서 할당되지 않은 pid가 존재하면 좀비프로세스로 판단하여 kill
This commit is contained in:
171
modules/bot.py
171
modules/bot.py
@@ -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):
|
||||
"""텔레그램 봇 프로세스 실행 (독립 프로세스)"""
|
||||
|
||||
Reference in New Issue
Block a user