#!/usr/bin/env python3 """ Trade-Level Analysis - Find Patterns in Losing Trades Purpose: Identify what conditions lead to losses """ from __future__ import annotations import argparse import sys from pathlib import Path from typing import List, Dict PROJECT_ROOT = Path(__file__).resolve().parents[1] if str(PROJECT_ROOT) not in sys.path: sys.path.append(str(PROJECT_ROOT)) import pandas as pd from pathlib import Path from tqdm import tqdm from backtester.data_loader import load_csv from backtester.indicators.money_line import MoneyLineInputs, money_line_signals from backtester.simulator import TradeConfig, simulate_money_line, SimulatedTrade def analyze_trades(trades: List[SimulatedTrade]) -> Dict: """Deep analysis of trade performance.""" if not trades: return {"error": "No trades to analyze"} winners = [t for t in trades if t.pnl > 0] losers = [t for t in trades if t.pnl < 0] print("\n" + "="*80) print("TRADE PERFORMANCE BREAKDOWN") print("="*80) print(f"Total Trades: {len(trades)}") print(f"Winners: {len(winners)} ({len(winners)/len(trades)*100:.1f}%)") print(f"Losers: {len(losers)} ({len(losers)/len(trades)*100:.1f}%)") if winners: print(f"\nWinner Stats:") print(f" Avg PnL: ${sum(t.pnl for t in winners)/len(winners):.2f}") print(f" Max Win: ${max(t.pnl for t in winners):.2f}") print(f" Avg MFE: {sum(t.mfe for t in winners)/len(winners):.2f}%") print(f" Avg Bars: {sum(t.bars_held for t in winners)/len(winners):.1f}") if losers: print(f"\nLoser Stats:") print(f" Avg PnL: ${sum(t.pnl for t in losers)/len(losers):.2f}") print(f" Max Loss: ${min(t.pnl for t in losers):.2f}") print(f" Avg MAE: {sum(t.mae for t in losers)/len(losers):.2f}%") print(f" Avg Bars: {sum(t.bars_held for t in losers)/len(losers):.1f}") # Total P&L stats total_pnl = sum(t.pnl for t in trades) print(f"\nTotal P&L: ${total_pnl:.2f}") print(f"Avg Trade: ${total_pnl/len(trades):.2f}") # MAE/MFE Analysis print("\n" + "="*80) print("MAE/MFE ANALYSIS (Max Adverse/Favorable Excursion)") print("="*80) # Winners that gave back profit if winners: large_mfe_winners = [t for t in winners if t.mfe > 2.0] if large_mfe_winners: avg_giveback = sum(t.mfe - (t.pnl / 8100.0 * 100) for t in large_mfe_winners) / len(large_mfe_winners) print(f"Winners with >2% MFE: {len(large_mfe_winners)}") print(f" Average profit given back: {avg_giveback:.2f}%") print(f" šŸ’” Consider: Wider trailing stop or earlier TP2 trigger") # Losers that could have been winners if losers: positive_mfe_losers = [t for t in losers if t.mfe > 0.5] if positive_mfe_losers: avg_peak = sum(t.mfe for t in positive_mfe_losers) / len(positive_mfe_losers) print(f"\nLosers that reached >0.5% profit: {len(positive_mfe_losers)}") print(f" Average peak profit: {avg_peak:.2f}%") print(f" šŸ’” Consider: Tighter TP1 or better stop management") # Direction analysis print("\n" + "="*80) print("DIRECTION ANALYSIS") print("="*80) longs = [t for t in trades if t.direction == 'long'] shorts = [t for t in trades if t.direction == 'short'] if longs: long_wr = sum(1 for t in longs if t.pnl > 0) / len(longs) * 100 long_pnl = sum(t.pnl for t in longs) print(f"LONGS: {len(longs)} trades, {long_wr:.1f}% WR, ${long_pnl:.2f} PnL") if shorts: short_wr = sum(1 for t in shorts if t.pnl > 0) / len(shorts) * 100 short_pnl = sum(t.pnl for t in shorts) print(f"SHORTS: {len(shorts)} trades, {short_wr:.1f}% WR, ${short_pnl:.2f} PnL") if longs and shorts: if long_wr > short_wr + 10: print(f" šŸ’” LONGs outperform: Consider quality threshold adjustment") elif short_wr > long_wr + 10: print(f" šŸ’” SHORTs outperform: Consider quality threshold adjustment") return { "total": len(trades), "winners": len(winners), "losers": len(losers), "win_rate": len(winners) / len(trades) * 100, "total_pnl": total_pnl } def find_exit_opportunities(trades: List[SimulatedTrade]): """Analyze if different exit strategy could improve results.""" print("\n" + "="*80) print("EXIT STRATEGY ANALYSIS") print("="*80) # Current strategy stats tp1_hits = sum(1 for t in trades if t.exit_type == 'tp1') tp2_hits = sum(1 for t in trades if t.exit_type == 'tp2') sl_hits = sum(1 for t in trades if t.exit_type == 'sl') max_bars = sum(1 for t in trades if t.exit_type == 'max_bars') print(f"Current Exit Distribution:") print(f" TP1: {tp1_hits} ({tp1_hits/len(trades)*100:.1f}%)") print(f" TP2: {tp2_hits} ({tp2_hits/len(trades)*100:.1f}%)") print(f" SL: {sl_hits} ({sl_hits/len(trades)*100:.1f}%)") print(f" Max Bars: {max_bars} ({max_bars/len(trades)*100:.1f}%)") # Simulate tighter TP1 could_take_tp1 = [t for t in trades if t.mfe >= 0.5 and t.pnl < 0] if could_take_tp1: saved_pnl = len(could_take_tp1) * 0.005 * 8100 # 0.5% profit instead of loss print(f"\nšŸ’” Tighter TP1 (0.5%): Would save {len(could_take_tp1)} losing trades") print(f" Estimated improvement: +${saved_pnl:.2f}") # Check if runners are worth it tp2_trades = [t for t in trades if t.exit_type == 'tp2'] if tp2_trades: tp2_avg = sum(t.pnl for t in tp2_trades) / len(tp2_trades) print(f"\nTP2/Runner Performance:") print(f" {len(tp2_trades)} trades reached TP2") print(f" Average TP2 profit: ${tp2_avg:.2f}") if tp2_avg < 100: print(f" šŸ’” Runners not adding much value - consider 100% TP1 close") def compare_to_baseline(csv_path: Path, symbol: str, best_inputs: MoneyLineInputs): """Compare best config to baseline.""" print("\n" + "="*80) print("BASELINE COMPARISON") print("="*80) print("\nLoading data and running simulations...") data_slice = load_csv(Path(csv_path), symbol, "5m") df = data_slice.data config = TradeConfig(position_size=8100.0, max_bars_per_trade=288) baseline = MoneyLineInputs() print("\nRunning baseline config...") baseline_result = simulate_money_line(df, symbol, baseline, config) print(f" Trades: {len(baseline_result.trades)}") print(f" PnL: ${baseline_result.total_pnl:.2f}") print(f" WR: {baseline_result.win_rate:.1f}%") print("\nRunning best sweep config...") best_result = simulate_money_line(df, symbol, best_inputs, config) print(f" Trades: {len(best_result.trades)}") print(f" PnL: ${best_result.total_pnl:.2f}") print(f" WR: {best_result.win_rate:.1f}%") improvement = best_result.total_pnl - baseline_result.total_pnl print(f"\nImprovement: ${improvement:.2f} ({improvement/baseline_result.total_pnl*100:.1f}%)") return best_result.trades def main(): parser = argparse.ArgumentParser(description="Trade-level analysis") parser.add_argument("--csv", type=Path, required=True, help="Path to OHLCV CSV") parser.add_argument("--symbol", default="SOL-PERP", help="Symbol name") args = parser.parse_args() print("="*80) print("V9 TRADE ANALYSIS") print("="*80) # Use "best" config from sweep best_inputs = MoneyLineInputs( flip_threshold_percent=0.6, ma_gap_threshold=0.35, momentum_min_adx=23.0, momentum_long_max_pos=70.0, momentum_short_min_pos=25.0, cooldown_bars=2, momentum_spacing=3, momentum_cooldown=2 ) trades = compare_to_baseline(args.csv, args.symbol, best_inputs) analyze_trades(trades) find_exit_opportunities(trades) print("\n" + "="*80) print("ACTIONABLE RECOMMENDATIONS") print("="*80) print("Based on this analysis, the next optimization steps should focus on:") print("1. Exit strategy improvements (TP1/TP2 levels)") print("2. Direction-specific quality thresholds") print("3. Stop loss positioning") print("4. Entry timing refinement") if __name__ == "__main__": main()