Download app.py from Quant0/AFI: direct link, hf CLI and curl.
- Browser
- Download file 10.9 kB
-
https://huggingface.co/spaces/Quant0/AFI/resolve/main/app.py
- Command line
-
hf download hf://spaces/Quant0/AFI/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Quant0/AFI/resolve/main/app.py
10.9 kB
| # ============================================================ | |
| # app.py — Interfaz de usuario (Gradio) y visualizaciones (Plotly) | |
| # ------------------------------------------------------------ | |
| # - Capa de presentación: | |
| # * UI con Gradio (componentes y callbacks). | |
| # * Plot interactivo de la evolución de la correlación (Plotly). | |
| # * Iframes de bitcointreasuries. | |
| # * Botones de exportación (delegan en exports.py). | |
| # - La lógica y datos están en: | |
| # analytics.py, scraping.py, matching.py, data_sources.py, exports.py, config.py | |
| # - Sin estado persistente; la caché en memoria vive en data_sources.py. | |
| # ============================================================ | |
| from __future__ import annotations | |
| # --- Librerías UI & Visualización | |
| import gradio as gr | |
| import plotly.graph_objects as go | |
| # --- Núcleo de negocio y datos (módulos del proyecto) | |
| from analytics import evolucion_correlacion | |
| from matching import find_company_online | |
| from scraping import generate_tickers_from_bitcointreasuries | |
| from exports import exportar_correlacion, descargar_resumen_holding_excel | |
| from config import TICKERS_TO_IDS, GRADIO_SHARE | |
| from data_sources import get_data # precarga opcional de BTC-USD | |
| # ============================================================ | |
| # Helpers específicos de la UI | |
| # ============================================================ | |
| def get_iframe_stock_vs_btc(ticker: str) -> str: | |
| """ | |
| HTML del iframe 'Stock vs Bitcoin' para un ticker, usando el mapeo TICKERS_TO_IDS. | |
| """ | |
| try: | |
| entity_id = TICKERS_TO_IDS[ticker] | |
| return f""" | |
| https://bitcointreasuries.net/embed?component=StockVsBitcoinChart&entityId={entity_id} | |
| """ | |
| except KeyError: | |
| return f"No hay datos de holding de Bitcoin para {ticker}." | |
| def get_iframe_holdings(ticker: str) -> str: | |
| """ | |
| HTML del iframe 'Bitcoin Holdings' para un ticker, usando TICKERS_TO_IDS. | |
| """ | |
| try: | |
| entity_id = TICKERS_TO_IDS[ticker] | |
| return f""" | |
| https://bitcointreasuries.net/embed?component=BitcoinHoldingsChart&entityId={entity_id} | |
| """ | |
| except KeyError: | |
| return f"No hay datos de holding de Bitcoin para {ticker}." | |
| def plot_evolucion_correlacion_gradio( | |
| ticker_company: str, step: int, window_months: int | |
| ): | |
| """ | |
| Genera un gráfico Plotly con la evolución de la correlación entre `ticker_company` y BTC-USD. | |
| """ | |
| serie = evolucion_correlacion(ticker_company, step=step, window_months=window_months) | |
| if serie is None or serie.empty: | |
| return None, f"No hay datos de correlación disponibles para {ticker_company}." | |
| # --- Configuración del gráfico Plotly (estética y controles) | |
| fig = go.Figure() | |
| fig.add_trace( | |
| go.Scatter( | |
| x=serie.index, | |
| y=serie.values, | |
| mode="lines", | |
| name="Correlación", | |
| line=dict(color="#2263eb", width=2), | |
| fill="tozeroy", | |
| fillcolor="rgba(34,99,235,0.1)", | |
| hovertemplate="Fecha: %{x|%Y-%m-%d}<br>Correlación: %{y:.4f}<extra></extra>", | |
| ) | |
| ) | |
| # Layout y controles de rango | |
| fig.update_layout( | |
| title_text=f"<b>Evolución de la correlación entre {ticker_company} y BTC-USD<b>", | |
| title_x=0, | |
| height=500, | |
| template="plotly_white", | |
| margin=dict(l=20, r=20, t=60, b=20), | |
| showlegend=False, | |
| xaxis=go.layout.XAxis( | |
| rangeslider=dict( | |
| visible=True, | |
| thickness=0.08, | |
| bgcolor="rgba(50, 0, 128, 0.1)", | |
| bordercolor="rgba(50, 0, 128, 0.2)", | |
| borderwidth=1, | |
| range=[serie.index.min(), serie.index.max()], | |
| ), | |
| 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"), | |
| dict(count=1, label="1y", step="year", stepmode="backward"), | |
| dict(step="all", label="All"), | |
| ] | |
| ), | |
| bgcolor="rgba(247,247,247,255)", | |
| bordercolor="rgba(0,0,0,0)", | |
| borderwidth=0, | |
| activecolor="rgba(230,233,255,255)", | |
| font=dict(color="#000000", size=10), | |
| x=0.9, | |
| y=1.1, | |
| xanchor="right", | |
| yanchor="top", | |
| ), | |
| type="date", | |
| domain=[0, 1], | |
| rangeslider_yaxis_rangemode="auto", | |
| showgrid=False, | |
| gridcolor="#e0e0e0", | |
| zeroline=False, | |
| ), | |
| yaxis=go.layout.YAxis( | |
| title_text="Correlación", | |
| side="left", | |
| showgrid=True, | |
| gridcolor="#e0e0e0", | |
| zeroline=False, | |
| tickformat=".2f", | |
| range=[-1.0, 1.0], | |
| ), | |
| hovermode="x unified", | |
| paper_bgcolor="rgba(255,255,255,1)", | |
| plot_bgcolor="rgba(255,255,255,1)", | |
| ) | |
| # Anotación en el máximo | |
| max_value = float(serie.max()) | |
| max_index = serie.idxmax() | |
| date_str = max_index.date() if hasattr(max_index, "date") else str(max_index) | |
| fig.add_annotation( | |
| x=max_index, | |
| y=max_value, | |
| text=f"Corr_max: {max_value:.4f}<br>Fecha: {date_str}", | |
| showarrow=True, | |
| arrowhead=2, | |
| ax=0, | |
| ay=-40, | |
| bgcolor="rgba(255, 255, 255, 0.8)", | |
| bordercolor="#c7c7c7", | |
| borderwidth=1, | |
| borderpad=4, | |
| font=dict(size=10, color="#333"), | |
| ) | |
| return fig, "Gráfico de correlación generado." | |
| # ============================================================ | |
| # Datos iniciales para la UI | |
| # ============================================================ | |
| # Lista inicial del Dropdown desde bitcointreasuries (equivale a tu generar_tickers()). | |
| tickers_dropdown = generate_tickers_from_bitcointreasuries() | |
| # Precarga rápida de BTC-USD en caché con yfinance (opcional, sin clave). | |
| get_data("BTC-USD", silent=True) | |
| # ============================================================ | |
| # Construcción de la interfaz (Gradio Blocks) | |
| # ============================================================ | |
| with gr.Blocks(title="Análisis de Correlación") as demo: | |
| # --- Cabecera y branding | |
| gr.Image( | |
| "https://cdn.prod.website-files.com/67174dcb1d43b06a0eae317e/672bbbc7a2b88045e5661994_logo-afi.svg", | |
| show_label=False, | |
| ) | |
| gr.Markdown("# Analizador de Correlación de Bitcoin") | |
| gr.Markdown("Explora la correlación de diferentes activos con Bitcoin y sus holdings.") | |
| # --- Buscador de empresa/ticker | |
| with gr.Row(): | |
| input_company = gr.Dropdown( | |
| label="Buscar por Nombre (se usarán los primeros 6 caracteres)", | |
| choices=tickers_dropdown, | |
| value="MicroStrategy", | |
| allow_custom_value=True, | |
| interactive=True, | |
| ) | |
| texto_info1 = gr.Textbox(label="Nombre Completo:", interactive=False) | |
| # Estado del TICKER resuelto (lo usamos en los callbacks) | |
| texto_info2 = gr.Textbox(label="Ticker:", interactive=False) | |
| # Resolver (Nombre, Ticker) al cambiar la selección | |
| input_company.change( | |
| fn=lambda texto: find_company_online(texto[:6]), | |
| inputs=input_company, | |
| outputs=[texto_info1, texto_info2], | |
| ) | |
| # Resolver también al cargar la app para mostrar valores iniciales | |
| demo.load( | |
| fn=lambda texto: find_company_online(texto[:6]), | |
| inputs=input_company, | |
| outputs=[texto_info1, texto_info2], | |
| ) | |
| # --- Tabs de la aplicación | |
| with gr.Tab("Evolución de Correlación"): | |
| with gr.Row(): | |
| input_step = gr.Slider( | |
| minimum=1, | |
| maximum=30, | |
| value=1, | |
| step=1, | |
| label="Indica cada cuántos días se calcula la correlación", | |
| ) | |
| input_window = gr.Slider( | |
| minimum=1, | |
| maximum=60, | |
| value=12, | |
| step=1, | |
| label="Ventana rolling (meses)", | |
| ) | |
| btn_plot = gr.Button("Mostrar Gráfico") | |
| output_plot = gr.Plot(label="Gráfico de Evolución") | |
| btn_excel = gr.Button("Descargar en Excel") | |
| output_file = gr.File(label="Archivo Excel") | |
| output_msg = gr.Label() | |
| # Graficar (usa el ticker resuelto en texto_info2) | |
| btn_plot.click( | |
| fn=plot_evolucion_correlacion_gradio, | |
| inputs=[texto_info2, input_step, input_window], | |
| outputs=[output_plot, output_msg], | |
| ) | |
| # Exportar a Excel (serie de correlación) | |
| btn_excel.click( | |
| fn=exportar_correlacion, | |
| inputs=[texto_info2, input_step, input_window], | |
| outputs=[output_file, output_msg], | |
| ) | |
| with gr.Tab("Stock vs BTC"): | |
| btn_stock_btc = gr.Button("Cargar Gráfico") | |
| output_iframe_stock = gr.HTML(label="Stock vs Bitcoin Chart") | |
| btn_stock_btc.click( | |
| fn=get_iframe_stock_vs_btc, inputs=[texto_info2], outputs=[output_iframe_stock] | |
| ) | |
| with gr.Tab("Holding de Bitcoin"): | |
| btn_holdings = gr.Button("Cargar Gráfico") | |
| output_iframe_holdings = gr.HTML(label="Bitcoin Holdings Chart") | |
| btn_holdings.click( | |
| fn=get_iframe_holdings, inputs=[texto_info2], outputs=[output_iframe_holdings] | |
| ) | |
| with gr.Tab("Exportar Resumen Holding"): | |
| input_template_url = gr.Textbox( | |
| label="URL de la plantilla de Excel (Google Drive)", | |
| value=( | |
| "https://docs.google.com/spreadsheets/d/1Ad3bbYQAAZO6ljkaCGTGSw5aG_FQF7Lg/" | |
| "edit?usp=sharing&ouid=109222615757657219494&rtpof=true&sd=true" | |
| ), | |
| placeholder="Ej: https://docs.google.com/spreadsheets/d/ID_DEL_ARCHIVO/...", | |
| ) | |
| btn_descargar_resumen = gr.Button("Descargar Resumen a Excel") | |
| output_resumen_file = gr.File(label="Archivo de Resumen de Holdings") | |
| output_resumen_msg = gr.Label() | |
| btn_descargar_resumen.click( | |
| fn=descargar_resumen_holding_excel, | |
| inputs=[input_template_url], | |
| outputs=[output_resumen_file, output_resumen_msg], | |
| ) | |
| # ============================================================ | |
| # Lanzamiento de la app | |
| # ============================================================ | |
| if __name__ == "__main__": | |
| # GRADIO_SHARE controla si se expone URL pública (útil para demos/HF Spaces) | |
| demo.launch(share=GRADIO_SHARE) | |