133 lines
5.0 KiB
Python
133 lines
5.0 KiB
Python
# 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()
|