import pandas as pd import yfinance as yf from datetime import datetime, timedelta from data import all_historical_data, top_holding from config import tickers_to_ids import plotly.graph_objects as go # ----------------------------------------------- # Función de Correlación # ----------------------------------------------- def evolucion_correlacion(ticker_company, step, ticker_btc="BTC-USD", window_months=12): """ Calcula la evolución de la correlación entre el precio de las acciones de una empresa y Bitcoin. Args: ticker_company (str): El símbolo de cotización de la empresa. step (int): El número de días para el desplazamiento de la correlación. ticker_btc (str): El símbolo de cotización de Bitcoin (por defecto: "BTC-USD"). window_months (int): El tamaño de la ventana móvil en meses. outputs: pandas.Series: Una serie con la evolución de la correlación, o None si faltan datos. """ end_date = datetime.now() start_date = end_date - timedelta(days=10*365) if ticker_company not in all_historical_data: print(f"Descargando datos para {ticker_company}...") data = yf.download(ticker_company, start=start_date, end=end_date) if not data.empty: all_historical_data[ticker_company] = data print(f"Datos de {ticker_company} descargados con éxito.") else: print(f"Error al descargar datos para {ticker_company}.") return None if ticker_btc not in all_historical_data: print(f"Descargando datos para {ticker_btc}...") data = yf.download(ticker_btc, start=start_date, end=end_date) if not data.empty: all_historical_data[ticker_btc] = data print(f"Datos de {ticker_btc} descargados con éxito.") else: print(f"Error al descargar datos para {ticker_btc}.") return None company_data = all_historical_data.get(ticker_company) btc_data = all_historical_data.get(ticker_btc) if company_data.empty or btc_data.empty: print(f"No podemos calcular la correlación dinámica: No hay datos disponibles para {ticker_company} o {ticker_btc}.") return None company_prices = company_data['Close'] btc_prices = btc_data['Close'] combined_prices = pd.concat([company_prices, btc_prices], axis=1).dropna() combined_prices.columns = [ticker_company, ticker_btc] if combined_prices.empty: print(f"No hay datos históricos en común entre {ticker_company} y {ticker_btc}.") return None company_returns = combined_prices[ticker_company].pct_change().dropna() btc_returns = combined_prices[ticker_btc].pct_change().dropna() window_days = window_months * 30 if step == 0: dynamic_corr = company_returns.rolling(window=f"{window_days}D").corr(btc_returns).iloc[-1] return pd.Series([dynamic_corr], index=[end_date]) else: dynamic_corr = company_returns.rolling(window=f"{window_days}D").corr(btc_returns).dropna() return dynamic_corr.iloc[9::step] # ----------------------------------------------- # PLOT DE LA CORRELACIÓN # ----------------------------------------------- def plot_evolucion_correlacion_gradio(ticker_company, step, window_months): """ Genera el gráfico de la evolución de la correlación. """ ticker_company = ticker_company.upper() serie = evolucion_correlacion(ticker_company, step, window_months=12) if serie is None or serie.empty: return None, f"No hay datos de correlación disponibles para {ticker_company}." # --- Create the Plotly graph --- fig = go.Figure() # Add the correlation series fig.add_trace(go.Scatter( x=serie.index, y=serie.values, mode='lines', name='Correlación', line=dict(color='#2263eb', width=2), # electric blue color for the line fill='tozeroy', # Fill the area down to Y=0 axis fillcolor='rgba(34,99,235,0.1)', # Very faint elctric blue shadow (10% opacity) hovertemplate='Fecha: %{x|%Y-%m-%d}
Correlación: %{y:.4f}' )) # --- Configure axes and range selector --- fig.update_layout( title_text=f'Evolución de la correlación entre {ticker_company} y BTC-USD', title_x=0, # Align title to the left height=500, # Graph height template="plotly_white", # Graph theme (clean and modern) margin=dict(l=20, r=20, t=60, b=20), # Graph margins showlegend=False, # No legend needed if only one main series xaxis=go.layout.XAxis( rangeslider=dict( visible=True, thickness=0.08, # Rangeslider thickness bgcolor="rgba(50, 0, 128, 0.1)", # Very transparent purple for rangeslider bordercolor="rgba(50, 0, 128, 0.2)", # Slightly more visible border borderwidth=1, range=[serie.index.min(), serie.index.max()] # Initial slider range ), rangeselector=dict( buttons=list([ dict(count=1, label="1m", step="month", stepmode="backward"), dict(count=3, label="3m", step="month", stepmode="backward"), dict(count=6, label="6m", step="month", stepmode="backward"), dict(step="year", count=1, label="YTD", stepmode="todate"), # YTD dict(count=1, label="1y", step="year", stepmode="backward"), dict(step="all", label="All") ]), bgcolor="rgba(247,247,247,255)", # Transparent background for buttons bordercolor="rgba(0,0,0,0)", # Transparent border borderwidth=0, activecolor="rgba(230,233,255,255)", # Active button color (slightly darker purple) font=dict(color="#000000", size=10), # Button font style x=0.9, y=1.1, xanchor='right', yanchor='top' # Button position ), type="date", domain=[0, 1], # Occupy full graph width rangeslider_yaxis_rangemode="auto", # Rangeslider Y-axis range adjusts automatically showgrid=False, gridcolor='#e0e0e0', zeroline=False ), yaxis=go.layout.YAxis( title_text='Correlación', side='left', showgrid=True, gridcolor='#e0e0e0', zeroline=False, tickformat=".2f", # 2 decimal places format range=[-1.0, 1.0] # Fixed range for correlation ), hovermode="x unified", # Unify hover to show data for the series paper_bgcolor='rgba(255,255,255,1)', # Transparent background for notebook integration plot_bgcolor='rgba(255,255,255,1)' ) # --- Add annotation for max value (similar to Matplotlib) --- max_value = serie.max() max_index = serie.idxmax() fig.add_annotation( x=max_index, y=max_value, text=f'Corr_max: {max_value:.4f}
Fecha: {max_index.date()}', showarrow=True, arrowhead=2, ax=0, ay=-40, # Adjust vertical position of the annotation bgcolor="rgba(255, 255, 255, 0.8)", # Semi-transparent white background bordercolor="#c7c7c7", borderwidth=1, borderpad=4, font=dict(size=10, color="#333"), # You can adjust text position with xanchor/yanchor if needed ) return fig, "Gráfico de correlación generado." # ----------------------------------------------- # EXPORTACIÓN DE LA CORRELACIÓN # ----------------------------------------------- def exportar_correlacion(ticker_company, step, window_months): """ Exporta la serie de correlación a un archivo Excel. """ try: serie = evolucion_correlacion(ticker_company, step, window_months=window_months) if serie is not None and not serie.empty: nombre_archivo = f'{ticker_company}_evolucion_correlacion.xlsx' serie.to_excel(nombre_archivo) return nombre_archivo, "Archivo Excel generado con éxito." else: return None, "No se pudieron generar los datos para exportar." except Exception as e: return None, f"Error al exportar: {e}" # ----------------------------------------------- # EMBED PLOT STOCK VS BTC DE BTC TREASURY # ----------------------------------------------- def get_iframe_stock_vs_btc(ticker): """ Genera el código HTML para el iframe de Stock vs Bitcoin. """ try: entity_id = tickers_to_ids[ticker] iframe_code = f""" """ return iframe_code except KeyError: return f"No hay datos de holding de Bitcoin para {ticker}." # ----------------------------------------------- # EMBED PLOT HOLDING BTC DE BTC TREASURY # ----------------------------------------------- def get_iframe_holdings(ticker): """ Genera el código HTML para el iframe de Bitcoin Holdings. """ try: entity_id = tickers_to_ids[ticker] iframe_code = f""" """ return iframe_code except KeyError: return f"No hay datos de holding de Bitcoin para {ticker}." # ----------------------------------------------- # TABLAS TOP HOLDING RESUMEN # ----------------------------------------------- df_descargado_ETFs, df_descargado_stocks = top_holding() df_descargado_ETFs = df_descargado_ETFs.reset_index() # Crear un DataFrame de ejemplo top5_ETFs = df_descargado_ETFs.copy().iloc[:,[1,2,3]].head() top5_ETFs = pd.DataFrame(top5_ETFs) top5_stocks = df_descargado_stocks.copy().iloc[:,[0,1,2]].head() top5_stocks = pd.DataFrame(top5_stocks) # Aplicar estilo con color de marca y bordes redondeados AZUL_BORDE = "#0085CA" GRIS_HEADER = "#F5F6F8" BLANCO = "#FFFFFF" styles = [ # Perímetro y comportamiento de bordes dict(selector="table", props=[ ("border", f"1.5px solid {AZUL_BORDE} !important"), ("border-radius", "11px"), ("border-collapse", "separate !important"), ("border-spacing", "0"), ("background-color", BLANCO) ]), # Eliminar TODOS los bordes internos dict(selector="th, td", props=[ ("border", "none !important"), ("background-color", BLANCO) ]), # Header gris muy claro dict(selector="thead th", props=[ ("background-color", GRIS_HEADER) ]), # Redondear esquinas visibles dict(selector="thead tr:first-child th:first-child", props=[ ("border-top-left-radius", "12px") ]), dict(selector="thead tr:first-child th:last-child", props=[ ("border-top-right-radius", "12px") ]), dict(selector="tbody tr:last-child td:first-child", props=[ ("border-bottom-left-radius", "12px") ]), dict(selector="tbody tr:last-child td:last-child", props=[ ("border-bottom-right-radius", "12px") ]), ] styled_stocks = top5_stocks.style.set_table_styles(styles).hide(axis="index") styled_stocks = styled_stocks.set_table_attributes( f'style="border:1.5px solid {AZUL_BORDE}; border-radius:11px; border-collapse:separate; border-spacing:0; background:#fff"' ) styled_ETFs = top5_ETFs.style.set_table_styles(styles).hide(axis="index") styled_ETFs = styled_ETFs.set_table_attributes( f'style="border:1.5px solid {AZUL_BORDE}; border-radius:11px; border-collapse:separate; border-spacing:0; background:#fff"' )