# analyze_backtest_log.py # 백테스트 로그 분석 스크립트: exit_reason별 성과, 승률, 평균 손익 계산 import re from typing import Dict, List, Tuple from collections import defaultdict def parse_sell_trades(log_path: str) -> List[Dict[str, any]]: """로그 파일에서 SELL 거래 파싱""" trades = [] line_count = 0 match_count = 0 try: with open(log_path, 'r', encoding='utf-16-le', errors='ignore') as f: for line in f: line_count += 1 # SELL 패턴: [날짜] SELL (exit_reason): 종목명(티커) @ 가격 / 수량주 (수익률%) # 예: [2022-01-10] SELL (STOP_LOSS_TECH): Delta Air Lines(DAL) @ 40 / 247,260주 (-2.12%) match = re.search( r'\[(\d{4}-\d{2}-\d{2})\]\s+SELL\s+\(([^)]+)\):\s+(.+?)\s+@\s+[\d,]+\s+/\s+[\d,]+주\s+\(([+-]?\d+\.\d+)%\)', line ) if match: match_count += 1 date, exit_reason, ticker, return_pct = match.groups() trades.append({ 'date': date, 'exit_reason': exit_reason, 'ticker': ticker.strip(), 'return_pct': float(return_pct) }) except Exception as e: print(f"Error reading file: {e}") print(f"Debug: Scanned {line_count} lines, found {match_count} SELL trades") return trades def analyze_by_exit_reason(trades: List[Dict[str, any]]) -> None: """exit_reason별 통계 분석""" stats: Dict[str, List[float]] = defaultdict(list) for trade in trades: stats[trade['exit_reason']].append(trade['return_pct']) print("\n" + "="*70) print("📊 EXIT REASON별 성과 분석") print("="*70) print(f"{'Exit Reason':<30} {'횟수':>6} {'승률':>7} {'평균':>8} {'누적':>8}") print("-"*70) total_trades = len(trades) total_profit = sum(t['return_pct'] for t in trades) total_wins = sum(1 for t in trades if t['return_pct'] > 0) for reason in sorted(stats.keys()): returns = stats[reason] count = len(returns) win_rate = sum(1 for r in returns if r > 0) / count * 100 avg_return = sum(returns) / count total_return = sum(returns) print(f"{reason:<30} {count:>6} {win_rate:>6.1f}% {avg_return:>7.2f}% {total_return:>7.2f}%") print("-"*70) if total_trades > 0: print(f"{'TOTAL':<30} {total_trades:>6} {total_wins/total_trades*100:>6.1f}% " f"{total_profit/total_trades:>7.2f}% {total_profit:>7.2f}%") print("="*70) def analyze_losers(trades: List[Dict[str, any]], top_n: int = 10) -> None: """손실 거래 TOP N 분석""" losers = sorted([t for t in trades if t['return_pct'] < 0], key=lambda x: x['return_pct']) print(f"\n❌ 손실 TOP {top_n} 거래") print("-"*70) for i, trade in enumerate(losers[:top_n], 1): print(f"{i:2}. [{trade['date']}] {trade['ticker']:<30} " f"{trade['exit_reason']:<25} {trade['return_pct']:>7.2f}%") def analyze_winners(trades: List[Dict[str, any]], top_n: int = 10) -> None: """수익 거래 TOP N 분석""" winners = sorted([t for t in trades if t['return_pct'] > 0], key=lambda x: x['return_pct'], reverse=True) print(f"\n✅ 수익 TOP {top_n} 거래") print("-"*70) for i, trade in enumerate(winners[:top_n], 1): print(f"{i:2}. [{trade['date']}] {trade['ticker']:<30} " f"{trade['exit_reason']:<25} {trade['return_pct']:>7.2f}%") def main() -> None: log_path = 'latest_backtest.log' print("백테스트 로그 분석 시작...") trades = parse_sell_trades(log_path) print(f"총 {len(trades)}개 거래 파싱 완료") analyze_by_exit_reason(trades) analyze_losers(trades, top_n=15) analyze_winners(trades, top_n=15) # 추가 인사이트 print("\n" + "="*70) print("💡 최적화 힌트") print("="*70) stop_losses = [t for t in trades if 'STOP_LOSS' in t['exit_reason']] trailing_stops = [t for t in trades if 'TRAILING_STOP' in t['exit_reason']] profit_takes = [t for t in trades if 'PROFIT_TAKE' in t['exit_reason']] if stop_losses: avg_sl = sum(t['return_pct'] for t in stop_losses) / len(stop_losses) print(f"• STOP_LOSS 평균 손실: {avg_sl:.2f}% → 너무 타이트하면 완화 고려") if trailing_stops: ts_win_rate = sum(1 for t in trailing_stops if t['return_pct'] > 0) / len(trailing_stops) * 100 print(f"• TRAILING_STOP 승률: {ts_win_rate:.1f}% → 50% 이하면 파라미터 조정 필요") if profit_takes: avg_pt = sum(t['return_pct'] for t in profit_takes) / len(profit_takes) print(f"• PROFIT_TAKE 평균 수익: {avg_pt:.2f}% → 10% 이상이면 목표가 상향 고려") if __name__ == '__main__': main()