Files
ai-trade/modules/bot.py

343 lines
14 KiB
Python

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}")