mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-07-27 15:37:44 +00:00
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
This commit is contained in:
@@ -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 <path.json> # 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}%, "
|
||||
|
||||
@@ -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 <strategy_json_path>
|
||||
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()
|
||||
Reference in New Issue
Block a user