javitechjkd/backtestingv2
0
1"""2Visualización de gráficos - Estilo TradingView3"""4 5import plotly.graph_objects as go6from plotly.subplots import make_subplots7import pandas as pd8import numpy as np9from typing import List, Optional, Dict, Any10import sys11from pathlib import Path12 13# Add parent directory for imports14sys.path.insert(0, str(Path(__file__).parent.parent.parent))15 16from backtesting_app.algorithms.base import AlgorithmResult, TrendState17 18 19# Colores estilo TradingView20COLORS = {21 'background': '#131722',22 'grid': '#1e222d',23 'text': '#787b86',24 'bullish': '#26a69a',25 'bearish': '#ef5350',26 'bullish_bg': 'rgba(38, 166, 154, 0.15)',27 'bearish_bg': 'rgba(239, 83, 80, 0.15)',28 'support': '#26a69a', # Verde como en TradingView29 'resistance': '#ef5350', # Rojo como en TradingView30 'signal_long': '#00e676',31 'signal_short': '#ff1744',32 'equity': '#42a5f5',33}34 35 36class ChartBuilder:37 """Constructor de gráficos estilo TradingView"""38 39 def __init__(self, title: str = ""):40 self.title = title41 self.fig = None42 43 def create_candlestick_chart(44 self,45 df: pd.DataFrame,46 algo_result: Optional[AlgorithmResult] = None,47 show_signals: bool = True,48 show_levels: bool = True,49 show_background: bool = True,50 height: int = 70051 ) -> go.Figure:52 """53 Crea gráfico de velas con indicadores del algoritmo54 """55 # Crear figura56 self.fig = go.Figure()57 58 # Fondos de tendencia (primero para que quede detrás)59 if show_background and algo_result:60 self._add_trend_backgrounds(df, algo_result)61 62 # Velas63 self._add_candlesticks(df)64 65 # Niveles de soporte/resistencia66 if show_levels and algo_result:67 self._add_support_resistance(df, algo_result)68 69 # Señales70 if show_signals and algo_result:71 self._add_signals(df, algo_result)72 73 # Layout74 self._apply_dark_theme(height)75 76 return self.fig77 78 def _add_candlesticks(self, df: pd.DataFrame):79 """Añade las velas al gráfico"""80 self.fig.add_trace(go.Candlestick(81 x=df.index,82 open=df['open'],83 high=df['high'],84 low=df['low'],85 close=df['close'],86 name='OHLC',87 increasing=dict(line=dict(color=COLORS['bullish'], width=1), 88 fillcolor=COLORS['bullish']),89 decreasing=dict(line=dict(color=COLORS['bearish'], width=1), 90 fillcolor=COLORS['bearish']),91 showlegend=False92 ))93 94 def _add_trend_backgrounds(self, df: pd.DataFrame, algo_result: AlgorithmResult):95 """Añade fondos de color según la tendencia - rectángulo por cada vela para evitar huecos"""96 97 if not algo_result.states:98 return99 100 y_min = df['low'].min() * 0.99101 y_max = df['high'].max() * 1.01102 103 # Crear un rectángulo por cada vela para evitar huecos104 for i, state in enumerate(algo_result.states):105 if i >= len(df):106 break107 108 color = COLORS['bullish_bg'] if state.trend == TrendState.BULLISH else COLORS['bearish_bg']109 110 # Para el eje X, usamos índices numéricos si es necesario111 if i == 0:112 x0 = df.index[0]113 else:114 # Punto medio entre la vela anterior y la actual115 x0 = df.index[i]116 117 if i == len(df) - 1:118 x1 = df.index[-1]119 else:120 x1 = df.index[min(i + 1, len(df) - 1)]121 122 self.fig.add_vrect(123 x0=x0,124 x1=x1,125 fillcolor=color,126 layer="below",127 line_width=0128 )129 130 def _add_support_resistance(self, df: pd.DataFrame, algo_result: AlgorithmResult):131 """Añade líneas de soporte y resistencia - formato original con step"""132 133 # Construir arrays de soporte y resistencia134 supports = []135 resistances = []136 137 for i, state in enumerate(algo_result.states):138 if i < len(df):139 supports.append(state.support)140 resistances.append(state.resistance)141 142 # Rellenar None con el valor anterior (forward fill manual)143 last_sup = None144 last_res = None145 for i in range(len(supports)):146 if supports[i] is not None:147 last_sup = supports[i]148 else:149 supports[i] = last_sup150 151 if resistances[i] is not None:152 last_res = resistances[i]153 else:154 resistances[i] = last_res155 156 # Soporte - línea verde con diagonales (como TradingView)157 self.fig.add_trace(go.Scatter(158 x=df.index[:len(supports)],159 y=supports,160 mode='lines',161 name='Soporte',162 line=dict(color=COLORS['support'], width=2),163 hovertemplate='Soporte: %{y:.2f}<extra></extra>'164 ))165 166 # Resistencia - línea roja con diagonales (como TradingView)167 self.fig.add_trace(go.Scatter(168 x=df.index[:len(resistances)],169 y=resistances,170 mode='lines',171 name='Resistencia',172 line=dict(color=COLORS['resistance'], width=2),173 hovertemplate='Resistencia: %{y:.2f}<extra></extra>'174 ))175 176 def _add_signals(self, df: pd.DataFrame, algo_result: AlgorithmResult):177 """Añade marcadores de señales de compra/venta"""178 179 long_x, long_y = [], []180 short_x, short_y = [], []181 182 for i, state in enumerate(algo_result.states):183 if i < len(df) and state.signal:184 if state.signal.signal_type == 'LONG':185 long_x.append(df.index[i])186 long_y.append(df.iloc[i]['low'] * 0.998)187 elif state.signal.signal_type == 'SHORT':188 short_x.append(df.index[i])189 short_y.append(df.iloc[i]['high'] * 1.002)190 191 # Señales LONG192 if long_x:193 self.fig.add_trace(go.Scatter(194 x=long_x,195 y=long_y,196 mode='markers',197 name='LONG',198 marker=dict(199 symbol='triangle-up',200 size=15,201 color=COLORS['signal_long'],202 line=dict(width=1, color='white')203 ),204 hovertemplate='LONG<extra></extra>'205 ))206 207 # Señales SHORT208 if short_x:209 self.fig.add_trace(go.Scatter(210 x=short_x,211 y=short_y,212 mode='markers',213 name='SHORT',214 marker=dict(215 symbol='triangle-down',216 size=15,217 color=COLORS['signal_short'],218 line=dict(width=1, color='white')219 ),220 hovertemplate='SHORT<extra></extra>'221 ))222 223 def _apply_dark_theme(self, height: int):224 """Aplica tema oscuro estilo TradingView"""225 self.fig.update_layout(226 title=dict(227 text=self.title,228 font=dict(color='white', size=16),229 x=0.5230 ),231 template='plotly_dark',232 paper_bgcolor=COLORS['background'],233 plot_bgcolor=COLORS['background'],234 height=height,235 margin=dict(l=50, r=50, t=50, b=50),236 xaxis=dict(237 gridcolor=COLORS['grid'],238 showgrid=True,239 rangeslider=dict(visible=False),240 type='date'241 ),242 yaxis=dict(243 gridcolor=COLORS['grid'],244 showgrid=True,245 side='right'246 ),247 legend=dict(248 yanchor="top",249 y=0.99,250 xanchor="left",251 x=0.01,252 bgcolor='rgba(0,0,0,0.5)',253 font=dict(color='white')254 ),255 hovermode='x unified'256 )257 258 259def create_candlestick_chart(260 df: pd.DataFrame,261 algo_result: Optional[AlgorithmResult] = None,262 title: str = "",263 show_signals: bool = True,264 show_levels: bool = True,265 show_background: bool = True,266 height: int = 700267) -> go.Figure:268 """269 Función helper para crear gráfico de velas270 """271 builder = ChartBuilder(title)272 return builder.create_candlestick_chart(273 df, algo_result, show_signals, show_levels, show_background, height274 )275 276 277def create_equity_chart(278 equity_curve: pd.Series,279 title: str = "Curva de Equity",280 height: int = 400281) -> go.Figure:282 """283 Crea gráfico de curva de equity284 """285 fig = go.Figure()286 287 # Línea de equity288 fig.add_trace(go.Scatter(289 x=equity_curve.index if hasattr(equity_curve, 'index') else list(range(len(equity_curve))),290 y=equity_curve.values,291 mode='lines',292 name='Equity',293 line=dict(color=COLORS['equity'], width=2),294 fill='tozeroy',295 fillcolor='rgba(66, 165, 245, 0.2)'296 ))297 298 # Línea de capital inicial299 initial = equity_curve.iloc[0] if len(equity_curve) > 0 else 0300 fig.add_hline(301 y=initial,302 line_dash="dash",303 line_color="gray",304 annotation_text=f"Capital inicial: ${initial:,.0f}",305 annotation_position="top left"306 )307 308 # Layout309 fig.update_layout(310 title=dict(text=title, font=dict(color='white', size=14), x=0.5),311 template='plotly_dark',312 paper_bgcolor=COLORS['background'],313 plot_bgcolor=COLORS['background'],314 height=height,315 margin=dict(l=50, r=50, t=50, b=30),316 xaxis=dict(gridcolor=COLORS['grid'], showgrid=True),317 yaxis=dict(318 gridcolor=COLORS['grid'],319 showgrid=True,320 side='right',321 tickformat='$,.0f'322 ),323 showlegend=False,324 hovermode='x unified'325 )326 327 return fig328 329 330def create_drawdown_chart(331 equity_curve: pd.Series,332 title: str = "Drawdown",333 height: int = 200334) -> go.Figure:335 """336 Crea gráfico de drawdown337 """338 # Calcular drawdown339 rolling_max = equity_curve.expanding().max()340 drawdown = ((equity_curve - rolling_max) / rolling_max) * 100341 342 fig = go.Figure()343 344 fig.add_trace(go.Scatter(345 x=drawdown.index if hasattr(drawdown, 'index') else list(range(len(drawdown))),346 y=drawdown.values,347 mode='lines',348 name='Drawdown',349 line=dict(color=COLORS['bearish'], width=1),350 fill='tozeroy',351 fillcolor='rgba(239, 83, 80, 0.3)'352 ))353 354 fig.update_layout(355 title=dict(text=title, font=dict(color='white', size=12), x=0.5),356 template='plotly_dark',357 paper_bgcolor=COLORS['background'],358 plot_bgcolor=COLORS['background'],359 height=height,360 margin=dict(l=50, r=50, t=40, b=20),361 xaxis=dict(gridcolor=COLORS['grid'], showgrid=True),362 yaxis=dict(363 gridcolor=COLORS['grid'],364 showgrid=True,365 side='right',366 tickformat='.1f',367 ticksuffix='%'368 ),369 showlegend=False370 )371 372 return fig373 374 375def create_multi_asset_comparison_chart(376 results: Dict[str, Any],377 metric: str = 'total_return_percent',378 title: str = "Comparación Multi-Activo",379 height: int = 400380) -> go.Figure:381 """382 Crea gráfico de barras comparando múltiples activos383 """384 symbols = list(results.keys())385 values = [getattr(results[s], metric) for s in symbols]386 387 colors = [COLORS['bullish'] if v >= 0 else COLORS['bearish'] for v in values]388 389 fig = go.Figure()390 391 fig.add_trace(go.Bar(392 x=symbols,393 y=values,394 marker_color=colors,395 text=[f"{v:.1f}%" if 'percent' in metric else f"${v:,.0f}" for v in values],396 textposition='outside'397 ))398 399 fig.update_layout(400 title=dict(text=title, font=dict(color='white', size=14), x=0.5),401 template='plotly_dark',402 paper_bgcolor=COLORS['background'],403 plot_bgcolor=COLORS['background'],404 height=height,405 margin=dict(l=50, r=50, t=50, b=50),406 xaxis=dict(gridcolor=COLORS['grid']),407 yaxis=dict(gridcolor=COLORS['grid'], tickformat='.1f' if 'percent' in metric else ',.0f'),408 showlegend=False409 )410 411 return fig412 