반복적인 IPC 오류 해결, 봇 오류 해결, 인증 오류 해결, 서버 자원 할당 오류 해결, 코드 리팩토링
This commit is contained in:
387
modules/bot.py
387
modules/bot.py
@@ -1,172 +1,203 @@
|
||||
import time
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
from concurrent.futures.process import BrokenProcessPool
|
||||
from datetime import datetime
|
||||
|
||||
# 모듈 임포트
|
||||
from modules.config import Config
|
||||
from modules.services.kis import KISClient
|
||||
from modules.services.news import NewsCollector
|
||||
from modules.services.news import AsyncNewsCollector
|
||||
from modules.services.ollama import OllamaManager
|
||||
from modules.services.telegram import TelegramMessenger
|
||||
from modules.analysis.macro import MacroAnalyzer
|
||||
from modules.utils.monitor import SystemMonitor
|
||||
from modules.strategy.process import analyze_stock_process
|
||||
|
||||
# 기존 코드와의 호환성을 위해 상대 경로나 절대 경로로 임포트
|
||||
# (리팩토링 과도기에는 일부 파일은 그대로 있을 수 있음)
|
||||
try:
|
||||
from theme_manager import ThemeManager
|
||||
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
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
class AutoTradingBot:
|
||||
def __init__(self):
|
||||
def __init__(self, ipc_lock=None, command_queue=None, shutdown_event=None):
|
||||
# 1. 서비스 초기화
|
||||
self.kis = KISClient()
|
||||
self.news = NewsCollector()
|
||||
# [안정성] GPU OOM 방지를 위해 워커 수 축소 (4 -> 2)
|
||||
# [식별] 워커 프로세스 이름 등록
|
||||
self.executor = ProcessPoolExecutor(max_workers=2, initializer=init_worker)
|
||||
# 워커 프로세스 워밍업 (PID 등록 유도)
|
||||
self.news = AsyncNewsCollector()
|
||||
# GPU 경합 방지: 워커 1개만 사용 (LSTM 학습이 GPU 독점)
|
||||
self.executor = ProcessPoolExecutor(max_workers=1, initializer=init_worker)
|
||||
try:
|
||||
list(self.executor.map(lambda x: x, range(2)))
|
||||
except: pass
|
||||
|
||||
self.messenger = TelegramMessenger()
|
||||
list(self.executor.map(lambda x: x, range(1)))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self.messenger = TelegramMessenger()
|
||||
self.theme_manager = ThemeManager()
|
||||
self.ollama_monitor = OllamaManager() # GPU 모니터링용
|
||||
|
||||
self.ollama_monitor = OllamaManager()
|
||||
|
||||
# 2. 유틸리티 초기화
|
||||
self.monitor = SystemMonitor(self.messenger, self.ollama_monitor)
|
||||
|
||||
|
||||
# 3. 상태 변수
|
||||
self.daily_trade_history = []
|
||||
self.daily_trade_history = []
|
||||
self.discovered_stocks = set()
|
||||
self.is_macro_warning_sent = False
|
||||
self.watchlist_updated_today = False
|
||||
self.report_sent = False
|
||||
|
||||
# [IPC] BotIPC
|
||||
|
||||
# 4. 프로세스 관리
|
||||
self.shutdown_event = shutdown_event
|
||||
|
||||
# 5. IPC (Shared Memory)
|
||||
try:
|
||||
from modules.utils.ipc import BotIPC
|
||||
self.ipc = BotIPC()
|
||||
from modules.utils.ipc import SharedIPC
|
||||
self.ipc = SharedIPC(lock=ipc_lock, command_queue=command_queue)
|
||||
except ImportError:
|
||||
print("⚠️ BotIPC module not found.")
|
||||
print("[Bot] SharedIPC module not found.")
|
||||
self.ipc = None
|
||||
|
||||
# [Watchlist Manager]
|
||||
# 6. Watchlist Manager
|
||||
try:
|
||||
from watchlist_manager import WatchlistManager
|
||||
self.watchlist_manager = WatchlistManager(self.kis, watchlist_file=Config.WATCHLIST_FILE)
|
||||
except ImportError:
|
||||
self.watchlist_manager = None
|
||||
|
||||
# 기록 로드
|
||||
# 7. 기록 로드
|
||||
self.history_file = Config.HISTORY_FILE
|
||||
self.load_trade_history()
|
||||
|
||||
# AI 및 하드웨어 점검
|
||||
|
||||
# 8. AI 하드웨어 점검
|
||||
from modules.analysis.deep_learning import PricePredictor
|
||||
PricePredictor.verify_hardware()
|
||||
|
||||
pass
|
||||
|
||||
# 9. KIS 비동기 클라이언트
|
||||
try:
|
||||
from modules.services.kis import KISAsyncClient
|
||||
self.kis_async = KISAsyncClient(self.kis)
|
||||
except ImportError:
|
||||
self.kis_async = None
|
||||
|
||||
def load_trade_history(self):
|
||||
"""파일에서 금일 매매 기록 로드"""
|
||||
if os.path.exists(self.history_file):
|
||||
try:
|
||||
with open(self.history_file, "r", encoding="utf-8") as f:
|
||||
self.daily_trade_history = json.load(f)
|
||||
except Exception as e:
|
||||
print(f"⚠️ Failed to load history: {e}")
|
||||
except Exception:
|
||||
self.daily_trade_history = []
|
||||
else:
|
||||
self.daily_trade_history = []
|
||||
|
||||
def save_trade_history(self):
|
||||
"""매매 기록 저장"""
|
||||
try:
|
||||
with open(self.history_file, "w", encoding="utf-8") as f:
|
||||
json.dump(self.daily_trade_history, f, ensure_ascii=False, indent=2)
|
||||
except Exception as e:
|
||||
print(f"⚠️ Failed to save history: {e}")
|
||||
print(f"[Bot] Failed to save history: {e}")
|
||||
|
||||
def load_watchlist(self):
|
||||
try:
|
||||
with open(Config.WATCHLIST_FILE, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except:
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
def send_daily_report(self):
|
||||
"""장 마감 리포트"""
|
||||
if self.report_sent: return
|
||||
|
||||
print("📝 [Bot] Generating Daily Report...")
|
||||
if self.report_sent:
|
||||
return
|
||||
print("[Bot] Generating Daily Report...")
|
||||
balance = self.kis.get_balance()
|
||||
|
||||
total_eval = 0
|
||||
if "total_eval" in balance:
|
||||
total_eval = int(balance.get("total_eval", 0))
|
||||
|
||||
report = f"📅 **[Daily Closing Report]**\n" \
|
||||
f"────────────────\n" \
|
||||
f"💰 **총 자산**: `{total_eval:,}원`\n" \
|
||||
f"📜 **오늘의 매매**: `{len(self.daily_trade_history)}건`\n\n"
|
||||
total_eval = int(balance.get("total_eval", 0))
|
||||
report = f"📅 <b>[Daily Closing Report]</b>\n" \
|
||||
f"💰 <b>Total Asset:</b> <code>{total_eval:,}원</code>\n" \
|
||||
f"📜 <b>Trades Today:</b> <code>{len(self.daily_trade_history)}건</code>\n\n"
|
||||
|
||||
if self.daily_trade_history:
|
||||
for trade in self.daily_trade_history:
|
||||
icon = "🔴" if trade['action'] == "BUY" else "🔵"
|
||||
report += f"{icon} {trade['name']} {trade['qty']}주\n"
|
||||
|
||||
action = trade['action']
|
||||
icon = "🔴" if action == "BUY" else "🔵"
|
||||
report += f"{icon} <b>{action}</b> {trade['name']} {trade['qty']}주\n"
|
||||
|
||||
if "holdings" in balance and balance["holdings"]:
|
||||
report += "\n📊 **보유 현황**\n"
|
||||
report += "\n📊 <b>[Holdings]</b>\n"
|
||||
for stock in balance["holdings"]:
|
||||
yld = stock.get('yield', 0)
|
||||
icon = "🔺" if yld > 0 else "🔻"
|
||||
report += f"{icon} {stock['name']}: `{yld}%`\n"
|
||||
yld = float(stock.get('yield', 0))
|
||||
if yld > 0:
|
||||
icon = "🔴"
|
||||
yld_str = f"+{yld}"
|
||||
elif yld < 0:
|
||||
icon = "🔵"
|
||||
yld_str = f"{yld}"
|
||||
else:
|
||||
icon = "⚪"
|
||||
yld_str = f"{yld}"
|
||||
|
||||
report += f"{icon} {stock['name']}: <code>{yld_str}%</code>\n"
|
||||
|
||||
self.messenger.send_message(report)
|
||||
self.report_sent = True
|
||||
|
||||
def restart_executor(self):
|
||||
"""프로세스 풀 복구"""
|
||||
print("🔄 [Bot] Restarting Process Executor...")
|
||||
print("[Bot] Restarting Process Executor...")
|
||||
try:
|
||||
self.executor.shutdown(wait=False)
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
# 워커 재시작
|
||||
self.executor = ProcessPoolExecutor(max_workers=2, initializer=init_worker)
|
||||
print("✅ [Bot] Process Executor Restarted.")
|
||||
self.executor = ProcessPoolExecutor(max_workers=1, initializer=init_worker)
|
||||
print("[Bot] Process Executor Restarted.")
|
||||
|
||||
def run_cycle(self):
|
||||
def _process_commands(self):
|
||||
"""IPC command queue 폴링 및 처리"""
|
||||
if not self.ipc:
|
||||
return
|
||||
|
||||
commands = self.ipc.poll_commands()
|
||||
for cmd in commands:
|
||||
command = cmd.get('command', '')
|
||||
print(f"[Bot] Received command: {command}")
|
||||
|
||||
if command == 'restart':
|
||||
self.messenger.send_message("[Bot] Restart requested via Telegram.")
|
||||
# executor 재시작
|
||||
self.restart_executor()
|
||||
|
||||
elif command == 'update_watchlist':
|
||||
if self.watchlist_manager:
|
||||
try:
|
||||
summary = self.watchlist_manager.update_watchlist_daily()
|
||||
self.messenger.send_message(f"[Watchlist Updated]\n{summary}")
|
||||
except Exception as e:
|
||||
self.messenger.send_message(f"Watchlist update failed: {e}")
|
||||
|
||||
async def run_cycle(self):
|
||||
now = datetime.now()
|
||||
|
||||
# 1. 거시경제 분석 (우선 순위 상향)
|
||||
|
||||
# 0. 명령 큐 폴링
|
||||
self._process_commands()
|
||||
|
||||
# 1. 거시경제 분석
|
||||
macro_status = MacroAnalyzer.get_macro_status(self.kis)
|
||||
is_crash = False
|
||||
if macro_status['status'] == 'DANGER':
|
||||
is_crash = True
|
||||
if not self.is_macro_warning_sent:
|
||||
self.messenger.send_message("🚨 **[MARKET CRASH ALERT]** 시장 급락 감지! 매수 중단.")
|
||||
self.messenger.send_message("🚨 <b>[MARKET CRASH ALERT]</b> 시장 급락 감지! 매수 중단.")
|
||||
self.is_macro_warning_sent = True
|
||||
else:
|
||||
if self.is_macro_warning_sent:
|
||||
self.messenger.send_message("🌤️ **[MARKET RECOVERY]** 시장 안정화.")
|
||||
self.messenger.send_message("🌤️ <b>[MARKET RECOVERY]</b> 시장 안정화.")
|
||||
self.is_macro_warning_sent = False
|
||||
|
||||
# 2. IPC 상태 업데이트
|
||||
@@ -181,16 +212,16 @@ class AutoTradingBot:
|
||||
'watchlist': watchlist,
|
||||
'discovered_stocks': list(self.discovered_stocks),
|
||||
'is_macro_warning': self.is_macro_warning_sent,
|
||||
'macro_indices': macro_status['indicators'], # [수정] 거시경제 지표 전달
|
||||
'macro_indices': macro_status['indicators'],
|
||||
'themes': {}
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 2. 아침 업데이트 (08:00)
|
||||
# 3. 아침 업데이트 (08:00)
|
||||
if now.hour == 8 and 0 <= now.minute < 5:
|
||||
if not self.watchlist_updated_today and self.watchlist_manager:
|
||||
print("🌅 Morning Update...")
|
||||
print("[Bot] Morning Update...")
|
||||
try:
|
||||
summary = self.watchlist_manager.update_watchlist_daily()
|
||||
self.messenger.send_message(summary)
|
||||
@@ -198,7 +229,7 @@ class AutoTradingBot:
|
||||
except Exception as e:
|
||||
self.messenger.send_message(f"Update Failed: {e}")
|
||||
|
||||
# 3. 리셋 (09:00)
|
||||
# 4. 리셋 (09:00)
|
||||
if now.hour == 9 and now.minute < 5:
|
||||
self.daily_trade_history = []
|
||||
self.save_trade_history()
|
||||
@@ -206,182 +237,168 @@ class AutoTradingBot:
|
||||
self.discovered_stocks.clear()
|
||||
self.watchlist_updated_today = False
|
||||
|
||||
# 4. 시스템 감시
|
||||
# 5. 시스템 감시 (3분 간격)
|
||||
self.monitor.check_health()
|
||||
|
||||
# 5. 장 운영 시간 체크
|
||||
# 6. 장 운영 시간 체크
|
||||
if not (9 <= now.hour < 15 or (now.hour == 15 and now.minute < 30)):
|
||||
# 장 마감 리포트 (15:40)
|
||||
if now.hour == 15 and now.minute >= 40:
|
||||
self.send_daily_report()
|
||||
|
||||
print("💤 Market Closed. Waiting...")
|
||||
print("[Bot] Market Closed. Waiting...")
|
||||
return
|
||||
|
||||
print(f"⏰ [Bot] Cycle Start: {now.strftime('%H:%M:%S')}")
|
||||
|
||||
# 6. 거시경제 분석 (완료됨)
|
||||
# macro_status = ... (Moved to top)
|
||||
print(f"[Bot] Cycle Start: {now.strftime('%H:%M:%S')}")
|
||||
|
||||
# 7. 종목 분석 및 매매
|
||||
target_dict = self.load_watchlist()
|
||||
# (Discovery 로직 생략 - 필요시 추가)
|
||||
|
||||
|
||||
# 보유 종목 리스크 관리
|
||||
balance = self.kis.get_balance()
|
||||
current_holdings = {}
|
||||
|
||||
|
||||
if balance and "holdings" in balance:
|
||||
for stock in balance["holdings"]:
|
||||
code = stock.get("code")
|
||||
name = stock.get("name")
|
||||
qty = int(stock.get("qty", 0))
|
||||
yld = float(stock.get("yield", 0.0))
|
||||
|
||||
|
||||
current_holdings[code] = stock
|
||||
|
||||
# 보유 수량이 0 이하라면 스킵 (오류 방지)
|
||||
|
||||
if qty <= 0:
|
||||
continue
|
||||
|
||||
|
||||
action = None
|
||||
reason = ""
|
||||
|
||||
# 손절/익절 로직
|
||||
if yld <= -5.0:
|
||||
|
||||
if yld <= -5.0:
|
||||
action = "SELL"
|
||||
reason = "Stop Loss 📉"
|
||||
elif yld >= 8.0:
|
||||
reason = "Stop Loss"
|
||||
elif yld >= 8.0:
|
||||
action = "SELL"
|
||||
reason = "Take Profit 🚀"
|
||||
|
||||
reason = "Take Profit"
|
||||
|
||||
if action == "SELL":
|
||||
print(f"🚨 Risk Management: {reason} - {name} (Qty: {qty}, Yield: {yld}%)")
|
||||
|
||||
# 전량 매도
|
||||
print(f"[Bot] Risk Management: {reason} - {name} (Qty: {qty}, Yield: {yld}%)")
|
||||
res = self.kis.sell_stock(code, qty)
|
||||
|
||||
if res and res.get("status"):
|
||||
self.messenger.send_message(f"🛡️ **[Risk SELL]** {name}\n"
|
||||
f" • 사유: {reason}\n"
|
||||
f" • 수량: {qty}주\n"
|
||||
f" • 수익률: {yld}%")
|
||||
|
||||
self.messenger.send_message(
|
||||
f"🔵 <b>[Risk SELL]</b> {name}\n"
|
||||
f" Reason: {reason}\n"
|
||||
f" Qty: {qty}\n"
|
||||
f" Yield: <code>{yld}%</code>")
|
||||
self.daily_trade_history.append({
|
||||
"action": "SELL",
|
||||
"name": name,
|
||||
"qty": qty,
|
||||
"price": stock.get('current_price'),
|
||||
"yield": yld
|
||||
"action": "SELL", "name": name, "qty": qty,
|
||||
"price": stock.get('current_price'), "yield": yld
|
||||
})
|
||||
self.save_trade_history()
|
||||
else:
|
||||
print(f"❌ Sell Failed for {name}: {res}")
|
||||
|
||||
# 분석 실행 (병렬 처리)
|
||||
analysis_tasks = []
|
||||
news_data = self.news.get_market_news()
|
||||
|
||||
# [수정] 실시간 잔고 추적용 변수 (매수 시 차감)
|
||||
news_data = await self.news.get_market_news_async()
|
||||
|
||||
# 실시간 잔고 추적용 변수 (매수 시 차감)
|
||||
tracking_deposit = int(balance.get("deposit", 0))
|
||||
|
||||
|
||||
try:
|
||||
for ticker, name in target_dict.items():
|
||||
prices = self.kis.get_daily_price(ticker)
|
||||
if not prices: continue
|
||||
|
||||
# [신규] 외인 수급 분석
|
||||
if not prices:
|
||||
continue
|
||||
|
||||
# 외인 수급 분석
|
||||
investor_trend = self.kis.get_investor_trend(ticker)
|
||||
|
||||
future = self.executor.submit(analyze_stock_process, ticker, prices, news_data, investor_trend)
|
||||
|
||||
future = self.executor.submit(
|
||||
analyze_stock_process, ticker, prices, news_data, investor_trend)
|
||||
analysis_tasks.append(future)
|
||||
|
||||
|
||||
# 결과 처리
|
||||
loop = asyncio.get_event_loop()
|
||||
for future in analysis_tasks:
|
||||
try:
|
||||
res = future.result()
|
||||
res = await loop.run_in_executor(None, future.result)
|
||||
ticker_name = target_dict.get(res['ticker'], 'Unknown')
|
||||
print(f"📊 [{ticker_name}] Score: {res['score']:.2f} ({res['decision']})")
|
||||
|
||||
print(f"[Bot] [{ticker_name}] Score: {res['score']:.2f} ({res['decision']})")
|
||||
|
||||
if res['decision'] == "BUY":
|
||||
if is_crash: 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}")
|
||||
if is_crash:
|
||||
continue
|
||||
|
||||
print(f"🚀 Buying {ticker_name} {qty}ea")
|
||||
|
||||
# 실제 주문
|
||||
current_price = float(res['current_price'])
|
||||
if current_price <= 0:
|
||||
continue
|
||||
|
||||
qty = 1
|
||||
required_amount = current_price * qty
|
||||
|
||||
# 예수금 확인
|
||||
if tracking_deposit < required_amount:
|
||||
print(f"[Bot] [Skip Buy] 예수금 부족 ({ticker_name}): "
|
||||
f"필요 {required_amount:,.0f} > 잔고 {tracking_deposit:,.0f}")
|
||||
continue
|
||||
|
||||
print(f"[Bot] 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}원")
|
||||
|
||||
self.messenger.send_message(
|
||||
f"🔴 <b>[BUY]</b> {ticker_name} {qty}주\n"
|
||||
f" Price: <code>{current_price:,.0f}원</code>")
|
||||
self.daily_trade_history.append({
|
||||
"action": "BUY",
|
||||
"name": ticker_name,
|
||||
"qty": qty,
|
||||
"price": current_price
|
||||
"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)")
|
||||
# 매도 로직 (필요 시 추가)
|
||||
print(f"[Bot] Selling {ticker_name} (Simulation)")
|
||||
|
||||
except BrokenProcessPool:
|
||||
raise # 상위 레벨에서 처리
|
||||
raise
|
||||
except Exception as e:
|
||||
print(f"❌ Analysis Worker Error: {e}")
|
||||
print(f"[Bot] Analysis Worker Error: {e}")
|
||||
|
||||
except BrokenProcessPool:
|
||||
print("⚠️ [Bot] Worker Process Crashed (OOM, CUDA Error?). Restarting Executor...")
|
||||
print("[Bot] Worker Process Crashed. Restarting Executor...")
|
||||
self.restart_executor()
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except Exception as e:
|
||||
print(f"❌ Cycle Loop Error: {e}")
|
||||
print(f"[Bot] Cycle Loop Error: {e}")
|
||||
|
||||
def loop(self):
|
||||
print(f"🤖 Bot Module Started (PID: {os.getpid()})")
|
||||
self.messenger.send_message("🤖 **[Bot Started]** 리팩토링된 봇이 시작되었습니다.")
|
||||
print(f"[Bot] Module Started (PID: {os.getpid()})")
|
||||
self.messenger.send_message("[Bot Started] 리팩토링된 봇이 시작되었습니다.")
|
||||
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
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.")
|
||||
# shutdown 시그널 체크
|
||||
if self.shutdown_event and self.shutdown_event.is_set():
|
||||
print("[Bot] Shutdown signal received.")
|
||||
break
|
||||
|
||||
def start_telegram_command_server(self):
|
||||
"""텔레그램 봇 프로세스 실행 (독립 프로세스)"""
|
||||
script = os.path.join(os.getcwd(), "modules", "services", "telegram_bot", "runner.py")
|
||||
if os.path.exists(script):
|
||||
import subprocess
|
||||
subprocess.Popen([sys.executable, script], creationflags=subprocess.CREATE_NEW_PROCESS_GROUP)
|
||||
print("🚀 Telegram Command Server Started")
|
||||
else:
|
||||
print(f"⚠️ Telegram Bot Runner not found: {script}")
|
||||
try:
|
||||
loop.run_until_complete(self.run_cycle())
|
||||
except Exception as e:
|
||||
print(f"[Bot] Loop Error: {e}")
|
||||
self.messenger.send_message(f"[Bot] Loop Error: {e}")
|
||||
|
||||
# 비동기 sleep (shutdown 체크하면서 대기)
|
||||
for _ in range(60):
|
||||
if self.shutdown_event and self.shutdown_event.is_set():
|
||||
break
|
||||
time.sleep(1)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("[Bot] Stopped by User.")
|
||||
finally:
|
||||
print("[Bot] Shutting down executor...")
|
||||
self.executor.shutdown(wait=False)
|
||||
if self.ipc:
|
||||
self.ipc.cleanup()
|
||||
loop.close()
|
||||
print("[Bot] Executor shutdown complete.")
|
||||
|
||||
Reference in New Issue
Block a user