Coverage for src / monte_neo / utils / visualization.py: 84%
79 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-28 16:27 +0200
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-28 16:27 +0200
1"""Visualization utilities for backtest results.
2"""
4from typing import Any
6import matplotlib.pyplot as plt
7import numpy as np
8import pandas as pd
9import seaborn as sns
12def plot_equity_curves(results: list[dict[str, Any]], title: str = "Monte Carlo Equity Curves"):
13 """Plot equity curves for all scenarios."""
14 plt.figure(figsize=(12, 6))
16 # If results contain equity history, plot them.
17 # Currently our results only contain final metrics.
18 # To plot equity curves, we need to return them from the engine.
19 # For now, let's plot a distribution of final returns.
21 returns = [res['metrics']['total_return'] for res in results]
23 sns.histplot(returns, kde=True)
24 plt.axvline(np.mean(returns), color='r', linestyle='--', label=f'Mean: {np.mean(returns):.2%}')
25 plt.axvline(0, color='k', linestyle='-')
26 plt.title(title)
27 plt.xlabel("Total Return")
28 plt.ylabel("Frequency")
29 plt.legend()
30 plt.grid(True, alpha=0.3)
31 return plt
33def plot_drawdown_dist(results: list[dict[str, Any]], title: str = "Max Drawdown Distribution"):
34 """Plot distribution of maximum drawdowns."""
35 plt.figure(figsize=(10, 5))
36 drawdowns = [res['metrics']['max_drawdown'] for res in results]
38 sns.histplot(drawdowns, kde=True, color='orange')
39 plt.axvline(np.mean(drawdowns), color='r', linestyle='--', label=f'Mean DD: {np.mean(drawdowns):.2%}')
40 plt.title(title)
41 plt.xlabel("Max Drawdown")
42 plt.ylabel("Frequency")
43 plt.legend()
44 plt.grid(True, alpha=0.3)
45 return plt
47def plot_metrics_summary(results: list[dict[str, Any]]):
48 """Plot a summary table/heatmap of key metrics."""
49 metrics_df = pd.DataFrame([res['metrics'] for res in results])
51 fig, axes = plt.subplots(2, 2, figsize=(15, 10))
53 sns.boxplot(y=metrics_df['total_return'], ax=axes[0,0], color='skyblue')
54 axes[0,0].set_title("Total Return")
56 sns.boxplot(y=metrics_df['sharpe_ratio'], ax=axes[0,1], color='lightgreen')
57 axes[0,1].set_title("Sharpe Ratio")
59 sns.boxplot(y=metrics_df['win_rate'], ax=axes[1,0], color='salmon')
60 axes[1,0].set_title("Win Rate")
62 sns.boxplot(y=metrics_df['max_drawdown'], ax=axes[1,1], color='orange')
63 axes[1,1].set_title("Max Drawdown")
65 plt.tight_layout()
66 return fig
68def plot_stress_test_summary(stress_results: dict[str, Any]):
69 """Plot a summary of stress test results."""
70 plt.figure(figsize=(10, 6))
72 names = []
73 values = []
75 # 1. Black Swan Impact
76 bs = stress_results.get("black_swan", {})
77 if bs:
78 names.append("Black Swan Ret")
79 values.append(bs.get("total_return", 0))
81 # 2. Sensitivity
82 sens = stress_results.get("sensitivity", {})
83 if sens:
84 names.append("Sens. Std Var")
85 values.append(sens.get("std_return_variation", 0))
87 # 3. Breaking Point
88 bp = stress_results.get("breaking_point", {})
89 if bp:
90 bp_val = bp.get("breaking_point_bps", 0)
91 if isinstance(bp_val, str) and ">" in bp_val:
92 bp_val = float(bp_val.replace(">", "").strip())
93 names.append("Break Pt (bps)/100") # Scaled for plotting
94 values.append(float(bp_val) / 100.0)
96 plt.bar(names, values, color=['red', 'blue', 'green'])
97 plt.axhline(0, color='black', linewidth=1)
98 plt.title("Deep Stress Test Summary")
99 plt.ylabel("Metric Value")
100 plt.grid(True, alpha=0.3)
102 return plt
104def plot_sensitivity_heatmap(sensitivity_results: dict[str, Any]):
105 """Plot a heatmap of parameter sensitivity."""
106 grid = sensitivity_results.get("grid")
107 if not grid or not grid.get("matrix"):
108 return None
110 plt.figure(figsize=(10, 8))
112 matrix = np.array(grid["matrix"])
113 p1_vals = [f"{v:.2f}" for v in grid["p1_values"]]
114 p2_vals = [f"{v:.2f}" for v in grid["p2_values"]]
116 sns.heatmap(matrix, annot=True, fmt=".2%", xticklabels=p2_vals, yticklabels=p1_vals, cmap="RdYlGn")
117 plt.title(f"Sensitivity: {grid['p1_name']} vs {grid['p2_name']}")
118 plt.xlabel(grid["p2_name"])
119 plt.ylabel(grid["p1_name"])
121 return plt