import time import os import sys import json from concurrent.futures import ProcessPoolExecutor from datetime import datetime # 모듈 임포트 from modules.config import Config from modules.services.kis import KISClient from modules.services.news import NewsCollector 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 [] class AutoTradingBot: def __init__(self): # 1. 서비스 초기화 self.kis = KISClient() self.news = NewsCollector() self.executor = ProcessPoolExecutor(max_workers=4) self.messenger = TelegramMessenger() self.theme_manager = ThemeManager() self.ollama_monitor = OllamaManager() # GPU 모니터링용 # 2. 유틸리티 초기화 self.monitor = SystemMonitor(self.messenger, self.ollama_monitor) # 3. 상태 변수 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 try: from modules.utils.ipc import BotIPC self.ipc = BotIPC() except ImportError: print("⚠️ BotIPC module not found.") self.ipc = None # [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 # 기록 로드 self.history_file = Config.HISTORY_FILE self.load_trade_history() # AI 및 하드웨어 점검 from modules.analysis.deep_learning import PricePredictor PricePredictor.verify_hardware() # 텔레그램 명령 서버 시작 (Server에서 관리하도록 변경) # self.start_telegram_command_server() pass 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}") 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}") def load_watchlist(self): try: with open(Config.WATCHLIST_FILE, "r", encoding="utf-8") as f: return json.load(f) except: return {} def send_daily_report(self): """장 마감 리포트""" 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" 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" if "holdings" in balance and balance["holdings"]: report += "\n📊 **보유 현황**\n" for stock in balance["holdings"]: yld = stock.get('yield', 0) icon = "🔺" if yld > 0 else "🔻" report += f"{icon} {stock['name']}: `{yld}%`\n" self.messenger.send_message(report) self.report_sent = True def run_cycle(self): now = datetime.now() # 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.is_macro_warning_sent = True else: if self.is_macro_warning_sent: self.messenger.send_message("🌤️ **[MARKET RECOVERY]** 시장 안정화.") self.is_macro_warning_sent = False # 2. IPC 상태 업데이트 if self.ipc: try: balance = self.kis.get_balance() gpu_status = self.ollama_monitor.get_gpu_status() watchlist = self.load_watchlist() self.ipc.write_status({ 'balance': balance, 'gpu': gpu_status, 'watchlist': watchlist, 'discovered_stocks': list(self.discovered_stocks), 'is_macro_warning': self.is_macro_warning_sent, 'macro_indices': macro_status['indicators'], # [수정] 거시경제 지표 전달 'themes': {} }) except Exception: pass # 2. 아침 업데이트 (08:00) if now.hour == 8 and 0 <= now.minute < 5: if not self.watchlist_updated_today and self.watchlist_manager: print("🌅 Morning Update...") try: summary = self.watchlist_manager.update_watchlist_daily() self.messenger.send_message(summary) self.watchlist_updated_today = True except Exception as e: self.messenger.send_message(f"Update Failed: {e}") # 3. 리셋 (09:00) if now.hour == 9 and now.minute < 5: self.daily_trade_history = [] self.save_trade_history() self.report_sent = False self.discovered_stocks.clear() self.watchlist_updated_today = False # 4. 시스템 감시 self.monitor.check_health() # 5. 장 운영 시간 체크 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...") return print(f"⏰ [Bot] Cycle Start: {now.strftime('%H:%M:%S')}") # 6. 거시경제 분석 (완료됨) # macro_status = ... (Moved to top) # 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: action = "SELL" reason = "Stop Loss 📉" elif yld >= 8.0: action = "SELL" reason = "Take Profit 🚀" if action == "SELL": print(f"🚨 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.daily_trade_history.append({ "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() # [수정] 실시간 잔고 추적용 변수 (매수 시 차감) 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']})") 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}") 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}원") 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}") 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) 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}")