import pandas as pd import plotly.graph_objects as go from plotly.subplots import make_subplots def plot_normalized_prices(prices: pd.DataFrame) -> go.Figure: """ Affiche les prix normalisés (base 100) des deux actifs superposés. Parameters ---------- prices : pd.DataFrame DataFrame avec deux colonnes de prix (index = date). Returns ------- go.Figure Graphique Plotly interactif. """ ticker_a, ticker_b = prices.columns[0], prices.columns[1] # Normalisation base 100 prix_normalises = prices / prices.iloc[0] * 100 fig = go.Figure() fig.add_trace(go.Scatter( x=prix_normalises.index, y=prix_normalises[ticker_a], name=ticker_a, line=dict(color="#00d4aa", width=2), )) fig.add_trace(go.Scatter( x=prix_normalises.index, y=prix_normalises[ticker_b], name=ticker_b, line=dict(color="#ff6b6b", width=2), )) fig.update_layout( title="Prix normalisés (base 100)", xaxis_title="Date", yaxis_title="Prix (base 100)", template="plotly_dark", hovermode="x unified", height=450, ) return fig def plot_spread_zscore(spread: pd.Series, zscore: pd.Series, entry_threshold: float = 2.0, exit_threshold: float = 0.5) -> go.Figure: """ Affiche le spread et le z-score avec les zones de trading colorées. Parameters ---------- spread : pd.Series Le spread entre les deux actifs. zscore : pd.Series Le z-score rolling du spread. entry_threshold : float Seuil d'entrée en position. exit_threshold : float Seuil de sortie de position. Returns ------- go.Figure Graphique Plotly avec 2 sous-graphiques (spread + z-score). """ fig = make_subplots( rows=2, cols=1, shared_xaxes=True, vertical_spacing=0.08, subplot_titles=("Spread", "Z-Score"), row_heights=[0.4, 0.6], ) # --- Spread --- fig.add_trace(go.Scatter( x=spread.index, y=spread, name="Spread", line=dict(color="#00d4aa", width=1.5), ), row=1, col=1) # Moyenne du spread fig.add_hline( y=spread.mean(), row=1, col=1, line=dict(color="gray", dash="dash", width=1), annotation_text="Moyenne", ) # --- Z-Score --- fig.add_trace(go.Scatter( x=zscore.index, y=zscore, name="Z-Score", line=dict(color="#ffffff", width=1.5), ), row=2, col=1) # Zones de seuils seuil_stop = 3.5 # Zone d'entrée long (z < -entry) fig.add_hrect( y0=-seuil_stop, y1=-entry_threshold, row=2, col=1, fillcolor="green", opacity=0.15, line_width=0, ) # Zone d'entrée short (z > +entry) fig.add_hrect( y0=entry_threshold, y1=seuil_stop, row=2, col=1, fillcolor="red", opacity=0.15, line_width=0, ) # Zone neutre / exit fig.add_hrect( y0=-exit_threshold, y1=exit_threshold, row=2, col=1, fillcolor="gray", opacity=0.1, line_width=0, ) # Lignes de seuils for seuil in [entry_threshold, -entry_threshold]: fig.add_hline( y=seuil, row=2, col=1, line=dict(color="yellow", dash="dot", width=1), ) for seuil in [exit_threshold, -exit_threshold]: fig.add_hline( y=seuil, row=2, col=1, line=dict(color="gray", dash="dot", width=1), ) # Ligne zéro fig.add_hline(y=0, row=2, col=1, line=dict(color="gray", width=0.5)) fig.update_layout( template="plotly_dark", hovermode="x unified", height=600, showlegend=False, ) return fig def plot_equity_curve(equity: pd.Series) -> go.Figure: """ Affiche la courbe d'equity du backtest. Parameters ---------- equity : pd.Series Courbe d'equity cumulée (P&L cumulé). Returns ------- go.Figure Graphique Plotly interactif. """ # Coloration : vert quand positif, rouge quand négatif couleurs = ["#00d4aa" if v >= 0 else "#ff6b6b" for v in equity] fig = go.Figure() fig.add_trace(go.Scatter( x=equity.index, y=equity, name="Equity", fill="tozeroy", line=dict(color="#00d4aa", width=2), fillcolor="rgba(0, 212, 170, 0.1)", )) fig.add_hline(y=0, line=dict(color="gray", dash="dash", width=1)) fig.update_layout( title="Courbe d'Equity (P&L cumulé)", xaxis_title="Date", yaxis_title="P&L cumulé ($)", template="plotly_dark", hovermode="x unified", height=400, ) return fig