Files
kis_trader/remove/legacy_root/auto_ai_reporter.py
Your Name 6d2a706a48 커밋 1 — 실매 가격 TTL 구멍 (본체)
왜: 체결이 없어도 마지막가는 유지인데, TTL로 None 만들고 매도/EOD를 건너뛰어 8/5 돌파·금요일 leftover가 남음. 호가필터 TTL 구멍과 같은 병.

넣을 파일

신규: kis_trader/engine/live_sell_price.py
kis_trader/strategies/base.py (_ws_last_quote, _resolve_sell_price)
전략: momentum.py scalping.py tail_catch.py breakout.py range_break.py dart_strategy.py updow_strategy.py updown_feed.py us_momentum.py
WS: ws_manager.py kis_ws.py kiwoom_ws.py ls_ws.py kis_ws_overseas.py
kis_trader/web/live_config_schema.py (WS_PRICE_MAX_AGE_SEC 기본 0)
database.py (키 주석 + legacy/ sys.path)
kis_trader/execution/order_manager.py (잔고 있는데 40240000 ghost_purge 금지 — 같은 EOD 사고)
EOD가 min_hold에 안 막히게 손본 momentum_hts_logic.py / scalping_engine.py / tail_engine.py (이 대화에서 손본 부분만 확인 후)
문서: docs/like_mcp.md/db_erd.md code_architecture.md (가격 TTL 문구)
메시지 초안

fix: 매수·매도 현재가를 TTL로 버리지 않음 (마지막 RAM)
횡보·체결 공백을 죽은 캐시로 오인해 None 처리하면 손절·EOD가 스킵된다.
호가필터와 같이 나이는 무시하고 마지막 체결가를 유지한다. EOD는 매수가 폴백.
영향: 실매 O / 백테·옵투나 봉 경로 거의 무관 (엔진 식 변경 아님)

커밋 2 — 루트 정리 (remove/ vs legacy/)
왜: 루트 단독봇·테스트는 지울 보관함으로. 웹·알람이 아직 쓰는 모듈은 remove에 두면 나중에 폴더째 삭제 때 깨짐.

넣을 파일

이동: 미사용 → remove/legacy_root/ (래퍼, ETF/키움 옛봇, 테스트, kiwoom_rest_api 등)
이동: 사용 중 → legacy/ (holding_bot kis_holding_ver1 news_analyzer kis_long_ver1/2)
신규: kis_trader/utils/legacy_root.py legacy/README.md remove/README.md
import 경로: backtest_web.py mm_butler.py mm_remote.py updow_holding_cfg.py dbband_stock_cfg.py param_search_updow*.py dbband_param_search.py param_search_apply_snapshot.py verify_three_paths.py
docs/like_mcp.md/code_architecture.md 수동 노트
메시지 초안

chore: 미사용 루트는 remove/, 웹·알람 구모듈은 legacy/
remove는 나중에 통째 삭제 예정. holding_bot·news_analyzer·kis_long은
ensure_legacy_root로 legacy/만 본다.
빼기: scratch/set_ws_price_max_age_zero.py (일회성)
2026-08-18 00:13:17 +09:00

264 lines
7.0 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
자동 AI 분석 보고서
- 매일 13:00에 최근 10건 거래 분석
- AI가 문제점 진단 및 .env 수정 권장
"""
import os
import sys
import time
import json
import sqlite3
import datetime
import warnings
import requests
from dotenv import load_dotenv
# .env 로드
current_dir = os.path.dirname(os.path.abspath(__file__))
env_file = os.path.join(current_dir, '.env')
load_dotenv(env_file)
# Gemini deprecated 경고 억제
warnings.filterwarnings("ignore", message=".*google.generativeai.*")
# Gemini API
try:
import google.generativeai as genai
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
if GEMINI_API_KEY:
genai.configure(api_key=GEMINI_API_KEY)
model = genai.GenerativeModel('gemini-2.5-flash')
else:
model = None
except Exception as e:
print(f"❌ Gemini 초기화 실패: {e}")
model = None
# Mattermost (Ver2와 동일: mm_config.json + MM_BOT_TOKEN_)
MM_SERVER_URL = os.getenv("MM_SERVER_URL", "https://mattermost.hoonfam.org")
MM_TOKEN = os.environ.get("MM_BOT_TOKEN_", os.getenv("MATTERMOST_TOKEN", "")).strip()
MM_CONFIG_FILE = os.path.join(current_dir, "mm_config.json")
MM_CHANNEL = os.getenv("MATTERMOST_CHANNEL", "stock")
def _load_mm_channels():
try:
if os.path.exists(MM_CONFIG_FILE):
with open(MM_CONFIG_FILE, 'r', encoding='utf-8') as f:
return json.load(f).get("channels", {})
except Exception:
pass
return {}
def send_mm(message):
"""Mattermost 전송"""
channels = _load_mm_channels()
channel_id = channels.get(MM_CHANNEL)
if not channel_id or not MM_TOKEN:
print("❌ MM 설정 없음 (mm_config.json 또는 MM_BOT_TOKEN_)")
return False
api_url = f"{MM_SERVER_URL.rstrip('/')}/api/v4/posts"
headers = {"Authorization": f"Bearer {MM_TOKEN}", "Content-Type": "application/json"}
payload = {"channel_id": channel_id, "message": message}
try:
r = requests.post(api_url, headers=headers, json=payload, timeout=5)
r.raise_for_status()
return True
except Exception as e:
print(f"❌ MM 전송 에러: {e}")
return False
def get_recent_trades(limit=10):
"""최근 거래 N건 조회"""
db_path = os.path.join(os.path.dirname(__file__), 'quant_bot.db')
conn = sqlite3.connect(db_path)
cursor = conn.execute(f"""
SELECT
code, name,
buy_price, sell_price, qty,
profit_rate, realized_pnl,
strategy, sell_reason,
buy_date, sell_date, hold_minutes
FROM trade_history
ORDER BY id DESC
LIMIT {limit}
""")
trades = []
for row in cursor.fetchall():
trades.append({
'code': row[0],
'name': row[1],
'buy_price': row[2],
'sell_price': row[3],
'qty': row[4],
'profit_rate': row[5],
'realized_pnl': row[6],
'strategy': row[7],
'sell_reason': row[8],
'buy_date': row[9],
'sell_date': row[10],
'hold_minutes': row[11]
})
conn.close()
return trades
def get_trade_summary(trades):
"""거래 통계 요약"""
if not trades:
return "거래 없음"
total = len(trades)
wins = sum(1 for t in trades if t['profit_rate'] > 0)
losses = total - wins
win_rate = wins / total * 100 if total > 0 else 0
avg_profit = sum(t['profit_rate'] for t in trades) / total
total_pnl = sum(t['realized_pnl'] for t in trades)
avg_hold = sum(t['hold_minutes'] for t in trades) / total
return f"""
📊 최근 {total}건 거래 통계
- 승률: {win_rate:.1f}% ({wins}{losses}패)
- 평균 수익률: {avg_profit:.2f}%
- 총 손익: {total_pnl:,.0f}
- 평균 보유: {avg_hold:.0f}
"""
def analyze_with_ai(trades):
"""AI로 거래 분석 및 권장사항"""
if not model:
return "❌ AI 분석 불가 (Gemini API 키 없음)"
trades_text = ""
for i, t in enumerate(trades, 1):
trades_text += f"""
[거래 {i}] {t['name']} ({t['strategy']})
- 매수: {t['buy_price']:,.0f}× {t['qty']}
- 매도: {t['sell_price']:,.0f}
- 손익: {t['profit_rate']:+.2f}% ({t['realized_pnl']:,.0f}원)
- 보유: {t['hold_minutes']}
- 사유: {t['sell_reason']}
"""
prompt = f"""당신은 퀀트 트레이딩 전문가입니다.
다음은 최근 {len(trades)}건의 거래 내역입니다:
{trades_text}
{get_trade_summary(trades)}
**당신의 임무:**
1. 문제점 3가지 진단 (구체적으로)
2. .env 수정 권장사항 (변수명=값 형식)
3. 예상 효과
**출력 형식:**
## 🔍 문제점
1. [구체적 문제 1]
2. [구체적 문제 2]
3. [구체적 문제 3]
## 💡 권장 수정사항
```
RSI_OVERHEAT_THRESHOLD=XX
HIGH_PRICE_CHASE_THRESHOLD=X.XX
STOP_LOSS_PCT=-X.XX
...
```
## 📈 예상 효과
- [효과 1]
- [효과 2]
**간결하고 명확하게 답변하세요.**
"""
try:
response = model.generate_content(prompt)
return response.text
except Exception as e:
return f"❌ AI 분석 실패: {e}"
def send_daily_report():
"""13시 정기 보고서"""
print(f"\n{'='*80}")
print(f"🤖 자동 AI 분석 시작: {datetime.datetime.now()}")
print(f"{'='*80}\n")
trades = get_recent_trades(10)
if not trades:
print("❌ 거래 내역 없음")
return
print("🧠 AI 분석 중...")
analysis = analyze_with_ai(trades)
summary = get_trade_summary(trades)
message = f"""🤖 **[13시 AI 자동 분석]**
{summary}
{analysis}
---
💬 명령어 사용법:
- `!env 보기` - 현재 설정 확인
- `!env RSI_OVERHEAT_THRESHOLD=75` - 설정 변경
- `!ai 왜 승률이 낮아?` - AI 질문
"""
if send_mm(message):
print("✅ 보고서 전송 완료")
else:
print("❌ 전송 실패")
def main():
"""메인 루프"""
print("🤖 자동 AI 분석 보고서 시작")
print(f"- 보고 시간: 매일 13:00")
print(f"- 분석 대상: 최근 10건 거래")
print(f"- AI 모델: Gemini 2.5 Flash")
print()
if not model:
print("❌ Gemini API 키가 없습니다.")
return
reported_today = False
main.last_date = datetime.date.today()
while True:
now = datetime.datetime.now()
current_date = now.date()
if main.last_date != current_date:
reported_today = False
print(f"\n📅 날짜 변경: {current_date}")
main.last_date = current_date
if now.hour == 13 and now.minute == 0 and not reported_today:
send_daily_report()
reported_today = True
time.sleep(60)
time.sleep(60)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n\n👋 종료")
except Exception as e:
print(f"\n\n❌ 에러: {e}")
import traceback
traceback.print_exc()