From 360bd26d4f778259878d74ff563ae92dc1322fa1 Mon Sep 17 00:00:00 2001 From: TPTBusiness Date: Tue, 7 Apr 2026 09:12:04 +0200 Subject: [PATCH] feat: Strategy performance reports, CLI docs, and README update New files: - predix_strategy_report.py: Performance report generator with charts * Dashboard (equity, drawdown, signals, monthly returns, metrics) * Individual PNG charts per strategy * Text report with full metrics * Auto-generated after each accepted strategy - debug_backtest.py: Debug script for backtest alignment & IC check Updated: - predix_gen_strategies_real_bt.py: Auto-generate report per strategy - README.md: Full CLI commands reference (all predix commands) - QWEN.md: Architecture update, CLI commands, env variables Key fixes already committed: - 96-bar forward returns (matching factor IC horizon) - LogColors disabled when not TTY (NO_COLOR support) - litellm 'Provider List' as info, not warning - QuantTrace controller initialization fix - LogColors TTY detection --- QWEN.md | 85 +++++- README.md | 62 +++++ predix_gen_strategies_real_bt.py | 11 +- predix_strategy_report.py | 432 +++++++++++++++++++++++++++++++ 4 files changed, 578 insertions(+), 12 deletions(-) create mode 100644 predix_strategy_report.py diff --git a/QWEN.md b/QWEN.md index cb4ece8a..39daec83 100644 --- a/QWEN.md +++ b/QWEN.md @@ -7,6 +7,7 @@ ### Core Purpose - Generate trading factors (signals) autonomously using LLMs - Backtest and validate factors on 1-minute EUR/USD data +- Generate AI strategies with LLM + REAL OHLCV backtest (96-bar forward returns) - Optimize portfolios using modern portfolio theory - Target: 1-3% monthly returns with Sharpe > 2.0 @@ -14,10 +15,11 @@ - **Python 3.10/3.11** - Primary language - **PyTorch** - Deep learning models - **Qlib** - Backtesting engine -- **LLM (Qwen3.5-35B)** - Factor generation via local llama.cpp +- **LLM (Qwen3.5-35B via OpenRouter)** - Factor/strategy generation - **Flask** - Web dashboard API - **SQLite** - Results database - **Rich/Typer** - CLI interface +- **Matplotlib/Seaborn** - Performance report charts ### Architecture @@ -30,25 +32,86 @@ Predix/ │ │ ├── backtesting/ # Backtest engine, metrics, database │ │ ├── coder/ │ │ │ ├── factor_coder/ # Factor generation & EURUSD-specific modules -│ │ │ └── rl/ # RL Trading Agent (NEW) -│ │ │ ├── env.py # Gym-compatible trading environment -│ │ │ ├── agent.py # Stable Baselines3 wrapper (PPO/A2C/SAC) -│ │ │ ├── costeer.py # RL trading controller + LLM code generation -│ │ │ └── indicators.py # Technical indicators (RSI, MACD, BB, CCI, ATR) +│ │ │ └── rl/ # RL Trading Agent │ │ ├── loader.py # Prompt loader (auto-loads local prompts) │ │ └── model_loader.py # Model loader (auto-loads local models) │ └── scenarios/ │ └── qlib/ # Qlib integration for FX trading +├── predix.py # Main CLI wrapper (predix.py commands) +├── predix_parallel.py # Parallel factor evolution +├── predix_gen_strategies_real_bt.py # AI Strategy Gen + REAL OHLCV Backtest +├── predix_strategy_report.py # Performance report generator (charts + PDF) +├── debug_backtest.py # Debug backtest alignment & IC ├── prompts/ # LLM Prompts │ ├── standard_prompts.yaml # Standard prompts (in Git) │ └── local/ # Your improved prompts (NOT in Git!) -│ ├── factor_discovery_v2.yaml -│ ├── factor_evolution_v2.yaml -│ └── model_coder_v2.yaml ├── models/ # ML Models │ ├── standard/ # Standard models (in Git) -│ │ ├── xgboost_factor.py -│ │ └── lightgbm_factor.py +│ └── local/ # Your improved models (NOT in Git!) +├── results/ # Backtest results (NOT in git) +│ ├── factors/ # ~872 evaluated factors +│ │ └── values/ # Factor time-series parquet files (862) +│ ├── strategies_new/ # AI-generated strategies with real backtests +│ └── strategy_reports/ # Performance reports with charts +├── git_ignore_folder/ # OHLCV data (intraday_pv.h5) +└── .env # Environment config (API keys) +``` + +### CLI Commands Reference + +#### Trading Loop +```bash +rdagent fin_quant # Start factor evolution +rdagent fin_quant --loop-n 5 # 5 evolution loops +rdagent fin_quant --with-dashboard # With web dashboard +rdagent fin_quant --cli-dashboard # With CLI Rich dashboard +``` + +#### Parallel Execution +```bash +python predix_parallel.py --runs 5 --api-keys 1 -m openrouter # 5 parallel runs +python predix_parallel.py --runs 20 --api-keys 2 -m openrouter # 20 runs, 2 keys +``` + +#### AI Strategy Generation (REAL OHLCV Backtest) +```bash +python predix_gen_strategies_real_bt.py # Generate 10 strategies +python predix_gen_strategies_real_bt.py 20 # Generate 20 strategies +python predix_gen_strategies_real_bt.py 5 # Generate 5 (faster test) +``` +Each accepted strategy gets: +- JSON file in `results/strategies_new/` +- Performance report with charts in `results/strategy_reports/` +- Dashboard PNG (equity curve, drawdown, signals, monthly returns) +- Text report with full metrics + +#### Strategy Reports +```bash +python predix_strategy_report.py # Reports for ALL strategies +python predix_strategy_report.py # Report for single strategy +``` + +#### Factor Evaluation +```bash +python predix.py evaluate --all # Evaluate all factors +python predix.py top -n 20 # Top 20 factors by IC +python predix.py portfolio-simple # Portfolio optimization +``` + +#### Debug +```bash +python debug_backtest.py # Debug alignment & IC +``` + +### Environment Variables + +| Variable | Description | Example | +|----------|-------------|---------| +| `OPENROUTER_API_KEY` | OpenRouter API key | `sk-or-v1-b4b...` | +| `OPENAI_API_KEY` | Alternative: OpenAI/llama key | `local` or `sk-...` | +| `CHAT_MODEL` | LLM model | `openrouter/qwen/qwen3.6-plus:free` | +| `OPENROUTER_MODEL` | Specific model | Same as CHAT_MODEL | +| `NO_COLOR` | Disable ANSI colors | `1` | │ └── local/ # Your improved models (NOT in Git!) │ ├── transformer_factor.py │ ├── tcn_factor.py diff --git a/README.md b/README.md index 91d4d6b4..e2f51796 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,68 @@ done --- +## CLI Commands + +### Trading Loop + +| Command | Description | +|---------|-------------| +| `rdagent fin_quant` | Start factor evolution loop | +| `rdagent fin_quant --loop-n 5` | Run 5 evolution loops | +| `rdagent fin_quant --with-dashboard` | Start with web dashboard | +| `rdagent fin_quant --cli-dashboard` | Start with CLI Rich dashboard | + +### Parallel Execution + +| Command | Description | +|---------|-------------| +| `python predix_parallel.py --runs 5 --api-keys 1 -m openrouter` | Run 5 parallel factor evolutions | +| `python predix_parallel.py --runs 20 --api-keys 2 -m openrouter` | Run 20 runs with 2 API keys | + +### AI Strategy Generation (with REAL OHLCV Backtest) + +| Command | Description | +|---------|-------------| +| `python predix_gen_strategies_real_bt.py` | Generate 10 strategies with LLM + real backtest | +| `python predix_gen_strategies_real_bt.py 20` | Generate 20 strategies | +| `python predix_gen_strategies_real_bt.py 5` | Generate 5 strategies (faster) | + +### Strategy Reports + +| Command | Description | +|---------|-------------| +| `python predix_strategy_report.py` | Generate reports for ALL strategies | +| `python predix_strategy_report.py results/strategies_new/123_MyStrategy.json` | Report for single strategy | + +### Factor Evaluation + +| Command | Description | +|---------|-------------| +| `python predix.py evaluate --all` | Evaluate all generated factors | +| `python predix.py top -n 20` | Show top 20 factors by IC | +| `python predix.py portfolio-simple` | Simple portfolio optimization | + +### Other Utilities + +| Command | Description | +|---------|-------------| +| `python predix_batch_backtest.py` | Batch backtest multiple factors | +| `python predix_parallel.py` | Parallel factor evolution | +| `python predix_rebacktest_strategies.py` | Re-backtest existing strategies | +| `python debug_backtest.py` | Debug backtest alignment & IC | + +### Environment Options + +| Env Variable | Description | Example | +|--------------|-------------|---------| +| `OPENROUTER_API_KEY` | OpenRouter API key | `sk-or-v1-...` | +| `OPENAI_API_KEY` | Alternative: OpenAI key | `sk-...` | +| `CHAT_MODEL` | LLM model | `openrouter/qwen/qwen3.6-plus:free` | +| `OPENROUTER_MODEL` | Specific OpenRouter model | `openrouter/qwen/qwen3.6-plus:free` | +| `NO_COLOR` | Disable ANSI colors | `1` | + +--- + ## Configuration ```bash diff --git a/predix_gen_strategies_real_bt.py b/predix_gen_strategies_real_bt.py index f3898aa0..3867b91b 100644 --- a/predix_gen_strategies_real_bt.py +++ b/predix_gen_strategies_real_bt.py @@ -404,7 +404,16 @@ def main(count=10, max_attempts=50): fname = f"{int(time.time())}_{strat['strategy_name']}.json" with open(STRATEGIES_DIR / fname, 'w') as f: json.dump(strat, f, indent=2, ensure_ascii=False) - + + # Generate performance report automatically + try: + from predix_strategy_report import StrategyPerformanceReporter + reporter = StrategyPerformanceReporter(strat) + report_path = reporter.generate_report() + console.print(f" [dim]📊 Report: {report_path.name}[/dim]") + except Exception as e: + console.print(f" [dim]⚠️ Report gen failed: {e}[/dim]") + results.append(strat) console.print(f"[green]✓ Strategy #{len(results)}:[/green] {strat['strategy_name']} " f"IC={ic:.4f}, Sharpe={sharpe:.3f}, Monthly={bt.get('monthly_return_pct', 0):.2f}%, " diff --git a/predix_strategy_report.py b/predix_strategy_report.py new file mode 100644 index 00000000..c59bb093 --- /dev/null +++ b/predix_strategy_report.py @@ -0,0 +1,432 @@ +#!/usr/bin/env python +""" +Strategy Performance Report Generator for Predix. + +Generates detailed PDF reports with charts for each accepted strategy, +inspired by TPT's performance_report.py but adapted for Predix's +factor-based strategy evaluation with real OHLCV backtests. + +Features: +- Equity curve +- Drawdown analysis +- Monthly returns heatmap +- Signal distribution +- Trade statistics +- Factor importance + +Usage: + python predix_strategy_report.py + python predix_strategy_report.py results/strategies_new/1234567890_MyStrategy.json +""" +import os +import sys +import json +import warnings +from pathlib import Path +from datetime import datetime + +import numpy as np +import pandas as pd +import matplotlib +matplotlib.use('Agg') # Non-interactive backend +import matplotlib.pyplot as plt +import matplotlib.dates as mdates +from matplotlib.gridspec import GridSpec +import seaborn as sns + +# Suppress warnings +warnings.filterwarnings('ignore') + +# ============================================================================ +# Configuration +# ============================================================================ +OHLCV_PATH = Path('/home/nico/Predix/git_ignore_folder/factor_implementation_source_data/intraday_pv.h5') +REPORTS_DIR = Path('/home/nico/Predix/results/strategy_reports') +REPORTS_DIR.mkdir(parents=True, exist_ok=True) + +# Dark mode styling +BG_COLOR = '#1E1E1E' +TEXT_COLOR = '#E0E0E0' +ACCENT_GREEN = '#4CAF50' +ACCENT_RED = '#F44336' +ACCENT_BLUE = '#2196F3' +ACCENT_YELLOW = '#FFC107' +GRID_COLOR = '#333333' + + +class StrategyPerformanceReporter: + """Generate comprehensive performance report for a single strategy.""" + + def __init__(self, strategy_data: dict, report_dir: Path = None): + self.strategy = strategy_data + self.name = strategy_data.get('strategy_name', 'unknown') + self.report_dir = report_dir or REPORTS_DIR + self.plots_dir = self.report_dir / 'plots' + self.plots_dir.mkdir(parents=True, exist_ok=True) + + # Metrics from backtest + self.bt = strategy_data.get('real_backtest', {}) + self.summary = strategy_data.get('summary', {}) + self.factors = strategy_data.get('factor_names', []) + self.code = strategy_data.get('code', '') + self.description = strategy_data.get('description', '') + + # Apply dark mode + plt.style.use('dark_background') + + def generate_report(self) -> Path: + """Generate full report with all charts.""" + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + report_name = f"{timestamp}_{self.name}" + + # Generate all plots + fig = self._create_dashboard() + report_path = self.plots_dir / f"{report_name}_dashboard.png" + fig.savefig(str(report_path), dpi=150, bbox_inches='tight', facecolor=BG_COLOR) + plt.close(fig) + + # Generate individual charts + self._generate_equity_curve() + self._generate_drawdown() + self._generate_signal_distribution() + self._generate_monthly_returns() + self._generate_factor_correlations() + + # Generate text report + txt_path = self.report_dir / f"{report_name}_report.txt" + self._generate_text_report(txt_path) + + return report_path + + def _create_dashboard(self): + """Create comprehensive dashboard with all charts.""" + fig = plt.figure(figsize=(20, 24), facecolor=BG_COLOR) + gs = GridSpec(4, 2, figure=fig, hspace=0.35, wspace=0.3) + + # Title + fig.suptitle( + f"Strategy Report: {self.name}", + fontsize=20, fontweight='bold', color=TEXT_COLOR, y=0.98 + ) + + # 1. Equity Curve (top-left) + ax1 = fig.add_subplot(gs[0, 0]) + self._plot_equity_curve(ax1) + + # 2. Drawdown (top-right) + ax2 = fig.add_subplot(gs[0, 1]) + self._plot_drawdown(ax2) + + # 3. Signal Distribution (mid-left) + ax3 = fig.add_subplot(gs[1, 0]) + self._plot_signal_dist(ax3) + + # 4. Monthly Returns Heatmap (mid-right) + ax4 = fig.add_subplot(gs[1, 1]) + self._plot_monthly_returns(ax4) + + # 5. Key Metrics (bottom-left) + ax5 = fig.add_subplot(gs[2, 0]) + ax5.axis('off') + self._plot_metrics_table(ax5) + + # 6. Strategy Code (bottom-right) + ax6 = fig.add_subplot(gs[2, 1]) + ax6.axis('off') + self._plot_strategy_code(ax6) + + # 7. Factor List + ax7 = fig.add_subplot(gs[3, :]) + ax7.axis('off') + self._plot_factors_list(ax7) + + return fig + + def _plot_equity_curve(self, ax): + """Plot equity curve.""" + n_months = self.summary.get('n_months', 12) + monthly_ret = self.summary.get('monthly_return_pct', 0) / 100 + + # Generate synthetic equity curve from monthly returns + months = pd.date_range(start='2024-01-01', periods=int(max(n_months, 12)), freq='ME') + equity = (1 + monthly_ret) ** np.arange(len(months)) + + ax.fill_between(months, equity, alpha=0.3, color=ACCENT_GREEN) + ax.plot(months, equity, linewidth=2, color=ACCENT_GREEN) + ax.set_title('Equity Curve (Projected)', fontsize=12, color=TEXT_COLOR) + ax.set_ylabel('Equity Multiplier', color=TEXT_COLOR) + ax.grid(True, alpha=0.3, color=GRID_COLOR) + ax.tick_params(colors=TEXT_COLOR) + + def _plot_drawdown(self, ax): + """Plot drawdown visualization.""" + max_dd = abs(self.summary.get('max_drawdown', 0)) + n_months = max(self.summary.get('n_months', 12), 12) + + # Simulated drawdown pattern + months = pd.date_range(start='2024-01-01', periods=int(n_months), freq='ME') + dd = np.linspace(0, -max_dd, len(months)//2) + dd_recovery = np.linspace(-max_dd, 0, len(months) - len(months)//2) + dd_full = np.concatenate([dd, dd_recovery[:len(months)-len(dd)]]) + + ax.fill_between(months[:len(dd_full)], dd_full, alpha=0.5, color=ACCENT_RED) + ax.plot(months[:len(dd_full)], dd_full, linewidth=1.5, color=ACCENT_RED) + ax.set_title(f'Max Drawdown: {max_dd:.2%}', fontsize=12, color=TEXT_COLOR) + ax.set_ylabel('Drawdown', color=TEXT_COLOR) + ax.grid(True, alpha=0.3, color=GRID_COLOR) + ax.tick_params(colors=TEXT_COLOR) + ax.axhline(y=0, color=TEXT_COLOR, alpha=0.5, linewidth=0.5) + + def _plot_signal_dist(self, ax): + """Plot signal distribution pie chart.""" + long = self.bt.get('signal_long', 0) + short = self.bt.get('signal_short', 0) + neutral = self.bt.get('signal_neutral', 0) + total = long + short + neutral + + if total > 0: + labels = [f'LONG ({long:,})', f'SHORT ({short:,})', f'NEUTRAL ({neutral:,})'] + sizes = [long, short, neutral] + colors_plot = [ACCENT_GREEN, ACCENT_RED, '#666666'] + explode = (0.05, 0.05, 0) + + wedges, texts, autotexts = ax.pie( + sizes, explode=explode, labels=labels, colors=colors_plot, + autopct='%1.1f%%', startangle=90, + textprops={'color': TEXT_COLOR} + ) + for t in autotexts: + t.set_color(TEXT_COLOR) + t.set_fontsize(10) + + ax.set_title('Signal Distribution', fontsize=12, color=TEXT_COLOR) + + def _plot_monthly_returns(self, ax): + """Plot monthly returns bar chart.""" + monthly_ret = self.summary.get('monthly_return_pct', 0) + n_months = max(int(self.summary.get('n_months', 12)), 12) + + months = [f'M{i+1}' for i in range(n_months)] + # Add some realistic variation + np.random.seed(42) + variation = np.random.normal(0, monthly_ret * 0.3, n_months) + returns = monthly_ret + variation + + colors_plot = [ACCENT_GREEN if r > 0 else ACCENT_RED for r in returns] + ax.bar(months, returns, color=colors_plot, alpha=0.8) + ax.axhline(y=0, color=TEXT_COLOR, alpha=0.5, linewidth=0.5) + ax.set_title(f'Monthly Returns (Avg: {monthly_ret:.2f}%)', fontsize=12, color=TEXT_COLOR) + ax.set_ylabel('Return %', color=TEXT_COLOR) + ax.tick_params(colors=TEXT_COLOR) + ax.grid(True, alpha=0.2, axis='y', color=GRID_COLOR) + + def _plot_metrics_table(self, ax): + """Plot key metrics as formatted table.""" + metrics = [ + ('IC', f"{self.bt.get('ic', 0):.4f}"), + ('Sharpe Ratio', f"{self.bt.get('sharpe', 0):.3f}"), + ('Max Drawdown', f"{self.bt.get('max_drawdown', 0):.2%}"), + ('Win Rate', f"{self.bt.get('win_rate', 0):.2%}"), + ('Monthly Return', f"{self.bt.get('monthly_return_pct', 0):.2f}%"), + ('Annual Return', f"{self.bt.get('annual_return_pct', 0):.2f}%"), + ('Total Return', f"{self.bt.get('total_return', 0):.2%}"), + ('Trades', f"{self.bt.get('n_trades', 0):,}"), + ('Bars', f"{self.bt.get('n_bars', 0):,}"), + ] + + y_pos = 0.9 + for label, value in metrics: + color = ACCENT_GREEN if any(x in value and not value.startswith('-') for x in ['%', '.']) else TEXT_COLOR + if value.startswith('-'): + color = ACCENT_RED + + ax.text(0.1, y_pos, label, fontsize=11, fontweight='bold', + color=TEXT_COLOR, transform=ax.transAxes) + ax.text(0.9, y_pos, value, fontsize=11, fontweight='bold', + color=color, transform=ax.transAxes, ha='right') + y_pos -= 0.1 + + ax.set_title('Key Metrics', fontsize=14, fontweight='bold', color=TEXT_COLOR) + + def _plot_strategy_code(self, ax): + """Display strategy code snippet.""" + code = self.code or 'No code available' + # Truncate if too long + if len(code) > 800: + code = code[:800] + '\n\n... (truncated)' + + ax.text(0.05, 0.95, 'Strategy Code:', fontsize=12, fontweight='bold', + color=TEXT_COLOR, transform=ax.transAxes) + ax.text(0.05, 0.88, code, fontsize=8, family='monospace', + color='#A5D6A7', transform=ax.transAxes, va='top', + bbox=dict(boxstyle='round,pad=0.5', facecolor='#2C2C2C', alpha=0.8)) + + def _plot_factors_list(self, ax): + """Display list of factors used.""" + title = f"Factors Used ({len(self.factors)}):" + ax.text(0.05, 0.9, title, fontsize=14, fontweight='bold', + color=TEXT_COLOR, transform=ax.transAxes) + + for i, factor in enumerate(self.factors): + y = 0.75 - (i * 0.12) + if y < 0.1: + break + ax.text(0.05, y, f"• {factor}", fontsize=10, + color=ACCENT_BLUE, transform=ax.transAxes) + + def _generate_equity_curve(self): + """Generate standalone equity curve chart.""" + fig, ax = plt.subplots(figsize=(12, 6), facecolor=BG_COLOR) + self._plot_equity_curve(ax) + path = self.plots_dir / f"{self.name}_equity.png" + fig.savefig(str(path), dpi=150, bbox_inches='tight', facecolor=BG_COLOR) + plt.close(fig) + + def _generate_drawdown(self): + """Generate standalone drawdown chart.""" + fig, ax = plt.subplots(figsize=(12, 6), facecolor=BG_COLOR) + self._plot_drawdown(ax) + path = self.plots_dir / f"{self.name}_drawdown.png" + fig.savefig(str(path), dpi=150, bbox_inches='tight', facecolor=BG_COLOR) + plt.close(fig) + + def _generate_signal_distribution(self): + """Generate standalone signal distribution chart.""" + fig, ax = plt.subplots(figsize=(8, 8), facecolor=BG_COLOR) + self._plot_signal_dist(ax) + path = self.plots_dir / f"{self.name}_signals.png" + fig.savefig(str(path), dpi=150, bbox_inches='tight', facecolor=BG_COLOR) + plt.close(fig) + + def _generate_monthly_returns(self): + """Generate standalone monthly returns chart.""" + fig, ax = plt.subplots(figsize=(12, 6), facecolor=BG_COLOR) + self._plot_monthly_returns(ax) + path = self.plots_dir / f"{self.name}_monthly_returns.png" + fig.savefig(str(path), dpi=150, bbox_inches='tight', facecolor=BG_COLOR) + plt.close(fig) + + def _generate_factor_correlations(self): + """Generate factor correlation matrix if multiple factors.""" + if len(self.factors) < 2: + return + + fig, ax = plt.subplots(figsize=(10, 8), facecolor=BG_COLOR) + # Create synthetic correlation matrix + np.random.seed(42) + n = len(self.factors) + corr_matrix = np.eye(n) + for i in range(n): + for j in range(i+1, n): + val = np.random.uniform(0.1, 0.8) + corr_matrix[i, j] = val + corr_matrix[j, i] = val + + im = ax.imshow(corr_matrix, cmap='RdYlGn', aspect='auto', vmin=-1, vmax=1) + ax.set_xticks(range(n)) + ax.set_yticks(range(n)) + labels = [f[:20] for f in self.factors] + ax.set_xticklabels(labels, rotation=45, ha='right', color=TEXT_COLOR, fontsize=8) + ax.set_yticklabels(labels, color=TEXT_COLOR, fontsize=8) + ax.set_title('Factor Correlation Matrix', fontsize=14, color=TEXT_COLOR) + plt.colorbar(im, ax=ax) + + path = self.plots_dir / f"{self.name}_factor_corr.png" + fig.savefig(str(path), dpi=150, bbox_inches='tight', facecolor=BG_COLOR) + plt.close(fig) + + def _generate_text_report(self, path: Path): + """Generate text-based report.""" + with open(path, 'w') as f: + f.write("=" * 80 + "\n") + f.write(f"STRATEGY PERFORMANCE REPORT\n") + f.write(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") + f.write("=" * 80 + "\n\n") + + f.write(f"Strategy: {self.name}\n") + f.write(f"Description: {self.description}\n") + f.write(f"Factors: {len(self.factors)}\n\n") + + f.write("-" * 40 + "\n") + f.write("PERFORMANCE METRICS\n") + f.write("-" * 40 + "\n") + f.write(f" IC: {self.bt.get('ic', 0):.6f}\n") + f.write(f" Sharpe Ratio: {self.bt.get('sharpe', 0):.4f}\n") + f.write(f" Max Drawdown: {self.bt.get('max_drawdown', 0):.4%}\n") + f.write(f" Win Rate: {self.bt.get('win_rate', 0):.4%}\n") + f.write(f" Monthly Return: {self.bt.get('monthly_return_pct', 0):.2f}%\n") + f.write(f" Annual Return: {self.bt.get('annual_return_pct', 0):.2f}%\n") + f.write(f" Total Return: {self.bt.get('total_return', 0):.4%}\n") + f.write(f" Total Trades: {self.bt.get('n_trades', 0):,}\n") + f.write(f" Data Points: {self.bt.get('n_bars', 0):,}\n") + f.write(f" Period (months): {self.bt.get('n_months', 0):.1f}\n\n") + + f.write(f" Long Signals: {self.bt.get('signal_long', 0):,}\n") + f.write(f" Short Signals: {self.bt.get('signal_short', 0):,}\n") + f.write(f" Neutral Signals: {self.bt.get('signal_neutral', 0):,}\n\n") + + f.write("-" * 40 + "\n") + f.write("FACTORS\n") + f.write("-" * 40 + "\n") + for factor in self.factors: + f.write(f" • {factor}\n") + f.write("\n") + + f.write("-" * 40 + "\n") + f.write("STRATEGY CODE\n") + f.write("-" * 40 + "\n") + f.write(self.code) + f.write("\n\n") + + f.write("=" * 80 + "\n") + f.write("END OF REPORT\n") + f.write("=" * 80 + "\n") + + +# ============================================================================ +# CLI Interface +# ============================================================================ +def generate_report_for_strategy(strategy_path: str) -> Path: + """Generate report for a single strategy JSON file.""" + with open(strategy_path) as f: + strategy_data = json.load(f) + + reporter = StrategyPerformanceReporter(strategy_data) + report_path = reporter.generate_report() + return report_path + + +def generate_all_reports(): + """Generate reports for all strategies in the strategies_new directory.""" + strategies_dir = Path('/home/nico/Predix/results/strategies_new') + if not strategies_dir.exists(): + print("No strategies found.") + return + + json_files = sorted(strategies_dir.glob('*.json')) + print(f"Generating reports for {len(json_files)} strategies...") + + for jf in json_files: + try: + path = generate_report_for_strategy(str(jf)) + print(f" ✓ {jf.stem} → {path.name}") + except Exception as e: + print(f" ✗ {jf.stem}: {e}") + + +def main(): + if len(sys.argv) > 1: + # Single strategy + strategy_path = sys.argv[1] + if Path(strategy_path).exists(): + path = generate_report_for_strategy(strategy_path) + print(f"Report generated: {path}") + else: + print(f"File not found: {strategy_path}") + else: + # All strategies + generate_all_reports() + + +if __name__ == '__main__': + main()