매매 성과 평가지표 시스템 구현
- modules/utils/performance_db.py 신규: 일별 자산 스냅샷(16:00~16:30) 및 매매 기록 영구 저장 (PerformanceDB 클래스) - modules/analysis/evaluator.py 신규: Sharpe/Sortino/MDD/Alpha 등 16개 지표 산출, S~F 등급 시스템, Ollama 5명 전문가 패널, 텔레그램 HTML 주간 보고서 (PerformanceEvaluator 클래스) - modules/bot.py 수정: BUY/SELL 시 perf_db 기록 강화, 금요일 15:35 주간 평가 자동 실행, IPC 'evaluate' 명령 처리 - modules/services/telegram_bot/server.py 수정: /evaluate 명령어 추가 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,9 +7,17 @@ import logging
|
||||
import subprocess
|
||||
from telegram import Update
|
||||
from telegram.ext import Application, CommandHandler, ContextTypes
|
||||
|
||||
# [디버깅] 파일 로깅 추가
|
||||
log_file = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))),
|
||||
"telegram_bot.log")
|
||||
file_handler = logging.FileHandler(log_file, encoding='utf-8')
|
||||
file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
|
||||
|
||||
logging.basicConfig(
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
level=logging.INFO
|
||||
level=logging.INFO,
|
||||
handlers=[logging.StreamHandler(), file_handler]
|
||||
)
|
||||
logging.getLogger("httpx").setLevel(logging.WARNING)
|
||||
|
||||
@@ -42,6 +50,7 @@ class TelegramBotServer:
|
||||
return self.bot_instance is not None
|
||||
|
||||
async def start_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
logging.info(f"[Command] /start from user {update.effective_user.id}")
|
||||
await update.message.reply_text(
|
||||
"<b>AI Trading Bot Command Center</b>\n"
|
||||
"명령어 목록:\n"
|
||||
@@ -51,7 +60,8 @@ class TelegramBotServer:
|
||||
"/update_watchlist - Watchlist 즉시 업데이트\n"
|
||||
"/macro - 거시경제 지표 및 시장 위험도\n"
|
||||
"/system - PC 리소스(CPU/GPU) 상태\n"
|
||||
"/ai - AI 모델 학습 상태 조회\n\n"
|
||||
"/ai - AI 모델 학습 상태 조회\n"
|
||||
"/evaluate - 즉시 성과 평가 보고서 생성\n\n"
|
||||
"<b>[관리 명령어]</b>\n"
|
||||
"/restart - 메인 봇 재시작 요청\n"
|
||||
"/exec <code>명령어</code> - 원격 명령어 실행\n"
|
||||
@@ -60,6 +70,7 @@ class TelegramBotServer:
|
||||
)
|
||||
|
||||
async def status_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
logging.info(f"[Command] /status from user {update.effective_user.id}")
|
||||
if not self.refresh_bot_instance():
|
||||
await update.message.reply_text("메인 봇이 실행 중이 아닙니다.")
|
||||
return
|
||||
@@ -172,34 +183,67 @@ class TelegramBotServer:
|
||||
await update.message.reply_text("데이터가 아직 수집되지 않았습니다.")
|
||||
return
|
||||
|
||||
status = "SAFE"
|
||||
msi = indices.get('MSI', 0)
|
||||
msi = float(indices.get('MSI', 0))
|
||||
if msi >= 50:
|
||||
status = "DANGER"
|
||||
risk_status = "🔴 DANGER"
|
||||
risk_desc = "시장 극도 불안정 - 매수 중단 권고"
|
||||
elif msi >= 30:
|
||||
status = "CAUTION"
|
||||
risk_status = "🟡 CAUTION"
|
||||
risk_desc = "시장 불안정 - 보수적 매매 권고"
|
||||
else:
|
||||
risk_status = "🟢 SAFE"
|
||||
risk_desc = "시장 안정 - 정상 매매 가능"
|
||||
|
||||
msg = f"<b>Market Risk: {status}</b>\n\n"
|
||||
from datetime import datetime
|
||||
now_str = datetime.now().strftime("%m/%d %H:%M")
|
||||
|
||||
if 'MSI' in indices:
|
||||
msg += f"<b>Stress Index:</b> <code>{indices['MSI']}</code>\n"
|
||||
msg = f"<b>거시경제 지표</b> <code>{now_str}</code>\n"
|
||||
msg += f"━━━━━━━━━━━━━━━━━━\n"
|
||||
msg += f"<b>Market Risk:</b> {risk_status}\n"
|
||||
msg += f"<i>{risk_desc}</i>\n\n"
|
||||
|
||||
for k, v in indices.items():
|
||||
if k != "MSI":
|
||||
change = float(v.get('change', 0))
|
||||
price = v.get('price', 0)
|
||||
|
||||
if change > 0:
|
||||
icon = "🔴"
|
||||
chg_str = f"+{change}"
|
||||
elif change < 0:
|
||||
icon = "🔵"
|
||||
chg_str = f"{change}"
|
||||
else:
|
||||
icon = "⚪"
|
||||
chg_str = f"{change}"
|
||||
|
||||
msg += f"{icon} <b>{k}</b>: {price} ({chg_str}%)\n"
|
||||
# MSI 상세
|
||||
msi_bar = "█" * int(msi / 10) + "░" * (10 - int(msi / 10))
|
||||
msg += f"<b>Stress Index (MSI):</b> <code>{msi:.1f}/100</code>\n"
|
||||
msg += f"<code>[{msi_bar}]</code>\n\n"
|
||||
|
||||
# 지수 상세
|
||||
index_order = ["KOSPI", "KOSDAQ", "KOSPI200"]
|
||||
for k in index_order:
|
||||
if k not in indices:
|
||||
continue
|
||||
v = indices[k]
|
||||
price = float(v.get('price', 0))
|
||||
change = float(v.get('change', 0))
|
||||
change_val = float(v.get('change_val', 0))
|
||||
high = float(v.get('high', 0))
|
||||
low = float(v.get('low', 0))
|
||||
prev_close = float(v.get('prev_close', 0))
|
||||
volume = int(v.get('volume', 0))
|
||||
|
||||
if price == 0:
|
||||
msg += f"⚫ <b>{k}:</b> <i>데이터 없음 (장 마감 후)</i>\n\n"
|
||||
continue
|
||||
|
||||
if change > 0:
|
||||
icon = "🔴"
|
||||
chg_str = f"+{change:.2f}% (+{change_val:.2f}pt)"
|
||||
elif change < 0:
|
||||
icon = "🔵"
|
||||
chg_str = f"{change:.2f}% ({change_val:.2f}pt)"
|
||||
else:
|
||||
icon = "⚪"
|
||||
chg_str = f"{change:.2f}%"
|
||||
|
||||
msg += f"{icon} <b>{k}:</b> <code>{price:,.2f}</code> {chg_str}\n"
|
||||
if high and low:
|
||||
msg += f" 고: <code>{high:,.2f}</code> 저: <code>{low:,.2f}</code>"
|
||||
if prev_close:
|
||||
msg += f" 전일종가: <code>{prev_close:,.2f}</code>"
|
||||
msg += "\n"
|
||||
if volume:
|
||||
msg += f" 거래량: <code>{volume:,}천주</code>\n"
|
||||
msg += "\n"
|
||||
|
||||
await update.message.reply_text(msg, parse_mode="HTML")
|
||||
|
||||
@@ -256,10 +300,11 @@ class TelegramBotServer:
|
||||
await update.message.reply_text("메인 봇이 실행 중이 아닙니다.")
|
||||
return
|
||||
|
||||
from modules.config import Config
|
||||
gpu = self.bot_instance.ollama_monitor.get_gpu_status()
|
||||
|
||||
msg = "<b>AI Model Status</b>\n"
|
||||
msg += "* <b>LLM Engine:</b> Ollama (Llama 3.1)\n"
|
||||
msg += f"* <b>LLM Engine:</b> Ollama ({Config.OLLAMA_MODEL})\n"
|
||||
msg += f"* <b>Device:</b> {gpu.get('name', 'GPU')}\n"
|
||||
|
||||
if gpu:
|
||||
@@ -349,6 +394,29 @@ class TelegramBotServer:
|
||||
except Exception as e:
|
||||
await update.message.reply_text(f"실행 오류: {e}")
|
||||
|
||||
async def evaluate_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""/evaluate: 즉시 성과 평가 보고서 생성 (LLM 분석 포함)"""
|
||||
await update.message.reply_text(
|
||||
"📊 성과 평가를 실행합니다...\n"
|
||||
"<i>LLM 전문가 패널 분석 포함 시 30초~1분 소요됩니다.</i>",
|
||||
parse_mode="HTML"
|
||||
)
|
||||
try:
|
||||
from modules.utils.performance_db import PerformanceDB
|
||||
from modules.analysis.evaluator import PerformanceEvaluator
|
||||
|
||||
evaluator = PerformanceEvaluator()
|
||||
loop = asyncio.get_running_loop()
|
||||
report = await loop.run_in_executor(None, evaluator.generate_weekly_report)
|
||||
|
||||
if len(report) > 4000:
|
||||
report = report[:4000] + "\n... (일부 생략)"
|
||||
|
||||
await update.message.reply_text(report, parse_mode="HTML")
|
||||
except Exception as e:
|
||||
logging.error(f"[Command] /evaluate error: {e}")
|
||||
await update.message.reply_text(f"평가 오류: {e}")
|
||||
|
||||
def run(self):
|
||||
handlers = [
|
||||
("start", self.start_command),
|
||||
@@ -359,6 +427,7 @@ class TelegramBotServer:
|
||||
("macro", self.macro_command),
|
||||
("system", self.system_command),
|
||||
("ai", self.ai_status_command),
|
||||
("evaluate", self.evaluate_command),
|
||||
("restart", self.restart_command),
|
||||
("stop", self.stop_command),
|
||||
("exec", self.exec_command)
|
||||
@@ -377,6 +446,7 @@ class TelegramBotServer:
|
||||
|
||||
self.application.add_error_handler(error_handler)
|
||||
|
||||
logging.info("[Telegram] Command Server Started (Shared Memory IPC Mode).")
|
||||
print("[Telegram] Command Server Started (Shared Memory IPC Mode).")
|
||||
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user