Download exports.py from Quant0/AFI: direct link, hf CLI and curl.
- Browser
- Download file 12.1 kB
-
https://huggingface.co/spaces/Quant0/AFI/resolve/main/exports.py
- Command line
-
hf download hf://spaces/Quant0/AFI/exports.py
-
curl -L -o exports.py https://huggingface.co/spaces/Quant0/AFI/resolve/main/exports.py
12.1 kB
| # ============================================================ | |
| # exports.py — Exportación a Excel (series y resúmenes) | |
| # ------------------------------------------------------------ | |
| # NOTA PARA AUDITORÍA: | |
| # - Este módulo encapsula las exportaciones a Excel que antes estaban | |
| # en la app. No ejecuta nada al importarlo (sin efectos secundarios). | |
| # - Mantiene la lógica y los textos de estado que usabas en app1 6.py. | |
| # - Depende de: | |
| # * analytics.evolucion_correlacion (cálculo de correlación) | |
| # * scraping.descargar_tabla_web, limpiar_texto, expand_money | |
| # * yfinance (para detectar disponibilidad de opciones) | |
| # * gdown/openpyxl/pandas (para la exportación a Excel) | |
| # ============================================================ | |
| from __future__ import annotations | |
| import os | |
| import re | |
| import shutil | |
| from typing import Optional, Tuple, Dict, List | |
| import numpy as np | |
| import pandas as pd | |
| import yfinance as yf | |
| from config import ( | |
| SCRAPERAPI_KEY, CRYPTOCOMPARE_API_KEY, TIMEZONE_TARGET, GRADIO_SHARE, | |
| CRYPTOCOMPARE_BASE_URL, BITCOINTREASURIES_STOCKS_URL, BITCOINTREASURIES_ETFS_URL, | |
| YF_SEARCH_ENDPOINT, TICKERS_TO_IDS | |
| ) | |
| from analytics import evolucion_correlacion | |
| from scraping import descargar_tabla_web, limpiar_texto, expand_money | |
| # gdown se usa para descargar la plantilla de Google Drive | |
| import gdown | |
| # ============================================================ | |
| # Utilidad: extraer ID de archivo de una URL de Google Drive | |
| # ============================================================ | |
| def extract_file_id(url: str) -> Optional[str]: | |
| """ | |
| Extrae el ID del archivo de una URL de Google Drive. | |
| Acepta formatos habituales: | |
| - https://drive.google.com/file/d/<ID>/view?usp=sharing | |
| - https://docs.google.com/spreadsheets/d/<ID>/edit | |
| - https://drive.google.com/open?id=<ID> | |
| Devuelve: | |
| - str con el ID si se reconoce; en caso contrario, None. | |
| """ | |
| if not url: | |
| return None | |
| # /d/<ID>/ | |
| m = re.search(r"/d/([a-zA-Z0-9_-]+)", url) | |
| if m: | |
| return m.group(1) | |
| # ?id=<ID> | |
| m = re.search(r"[?&]id=([a-zA-Z0-9_-]+)", url) | |
| if m: | |
| return m.group(1) | |
| return None | |
| # ============================================================ | |
| # Exportar: Serie de correlación a Excel | |
| # ============================================================ | |
| def exportar_correlacion( | |
| ticker_company: str, step: int, window_months: int | |
| ) -> Tuple[Optional[str], str]: | |
| """ | |
| Exporta a Excel la serie de correlación rolling entre `ticker_company` | |
| y BTC-USD (o el valor por defecto que usa `evolucion_correlacion`). | |
| Parámetros | |
| ---------- | |
| ticker_company : str | |
| Ticker de la empresa/ETF (p. ej. 'MSTR'). | |
| step : int | |
| Salto de muestreo en días (si 0, devuelve solo el último valor). | |
| window_months : int | |
| Ventana rolling en meses (p. ej., 12). | |
| Devuelve | |
| -------- | |
| (str | None, str) | |
| Ruta del archivo generado y un mensaje de estado. | |
| """ | |
| 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}" | |
| # ============================================================ | |
| # Exportar: Resumen holdings (Stocks / ETFs) a partir de plantilla | |
| # ============================================================ | |
| def descargar_resumen_holding_excel(template_url: str) -> Tuple[Optional[str], str]: | |
| """ | |
| Descarga una plantilla de Google Drive y genera un Excel con: | |
| - Hoja 'Stocks' (datos de bitcointreasuries.net, correlación 12m, opciones...) | |
| - Hoja 'ETFs' (datos de bitcointreasuries.net, opciones...) | |
| Lógica respetando tu implementación original: | |
| 1) Descargar tabla de stocks (índice 3) y ETFs (índice 3) con ScraperAPI. | |
| 2) Limpiar columnas, extraer Name y Ticker. | |
| 3) Convertir 'BTC in USD' y 'Marketcap' a float (expand_money). | |
| 4) Calcular ratio Holding/Mcap (stocks). | |
| 5) Calcular correlación 12m para cada ticker (stocks). | |
| 6) Detectar disponibilidad de opciones en yfinance (ambas hojas). | |
| 7) Escribir hojas en un Excel a partir de una plantilla descargada con gdown. | |
| Parámetros | |
| ---------- | |
| template_url : str | |
| URL de la plantilla en Google Drive (formato compartible). | |
| Devuelve | |
| -------- | |
| (str | None, str) | |
| Ruta del Excel generado y mensaje de estado. | |
| """ | |
| try: | |
| # -------------------------------------------------------- | |
| # 1) Descargar plantilla de Google Drive | |
| # -------------------------------------------------------- | |
| file_id = extract_file_id(template_url) | |
| if not file_id: | |
| return None, "Error: No se pudo extraer el ID del archivo de la URL de Google Drive." | |
| local_template_path = "plantilla_temporal.xlsx" | |
| try: | |
| gdown.download(id=file_id, output=local_template_path, quiet=False) | |
| except Exception as e: | |
| return None, f"Error al descargar la plantilla desde Google Drive: {e}" | |
| # -------------------------------------------------------- | |
| # 2) Descargar y construir DataFrames desde bitcointreasuries | |
| # -------------------------------------------------------- | |
| url_stocks = "https://bitcointreasuries.net/" | |
| url_etfs = "https://bitcointreasuries.net/etfs-and-exchanges" | |
| df_stocks_raw = descargar_tabla_web(url_stocks, indice_tabla=3) | |
| df_etfs_raw = descargar_tabla_web(url_etfs, indice_tabla=3) | |
| if df_stocks_raw is None and df_etfs_raw is None: | |
| # Limpiar plantilla temporal antes de salir | |
| try: | |
| os.remove(local_template_path) | |
| except Exception: | |
| pass | |
| return None, "Error: No se pudieron descargar los datos de holding." | |
| # -------------------------------------------------------- | |
| # 3) Procesar STOCKS | |
| # -------------------------------------------------------- | |
| if df_stocks_raw is not None: | |
| # Selección de columnas según tu código original: | |
| # [2,3,4,5,12] → 'Name','Bitcoin','BTC in USD','Marketcap','% Total BTC 21M' | |
| df_descargado_stocks = df_stocks_raw[[2, 3, 4, 5, 12]].copy() | |
| df_descargado_stocks.columns = ['Name', 'Bitcoin', 'BTC in USD', 'Marketcap', '% Total BTC 21M'] | |
| # Limpiar y separar Name y Ticker (por el último espacio) | |
| df_descargado_stocks[['Name', 'Ticker']] = ( | |
| df_descargado_stocks['Name'].apply(limpiar_texto).str.rsplit(' ', n=1, expand=True) | |
| ) | |
| # Limpiezas y conversiones monetarias | |
| df_descargado_stocks['Bitcoin'] = df_descargado_stocks['Bitcoin'].apply(limpiar_texto) | |
| df_descargado_stocks['Marketcap'] = df_descargado_stocks['Marketcap'].apply(limpiar_texto) | |
| df_descargado_stocks['BTC in USD'] = df_descargado_stocks['BTC in USD'].apply(expand_money) | |
| df_descargado_stocks['Marketcap'] = df_descargado_stocks['Marketcap'].apply(expand_money) | |
| # Ratio holding/mcap (puede dar NaN si faltan datos) | |
| df_descargado_stocks['Ratio Holding/Mcap'] = ( | |
| df_descargado_stocks['BTC in USD'] / df_descargado_stocks['Marketcap'] | |
| ) | |
| # Correlación 12 meses por ticker (manteniendo tu step=0) | |
| corrs: List[float] = [] | |
| for stock in df_descargado_stocks['Ticker']: | |
| try: | |
| serie = evolucion_correlacion(stock, step=0, window_months=12) | |
| corr_value = float(serie.iloc[0]) if (serie is not None and not serie.empty) else np.nan | |
| corrs.append(corr_value) | |
| except Exception: | |
| corrs.append(np.nan) | |
| df_descargado_stocks['Correlación con BTC (12 meses)'] = pd.Series(corrs) | |
| df_descargado_stocks.set_index('Ticker', inplace=True) | |
| else: | |
| df_descargado_stocks = pd.DataFrame() | |
| # -------------------------------------------------------- | |
| # 4) Procesar ETFs | |
| # -------------------------------------------------------- | |
| if df_etfs_raw is not None: | |
| # Selección de columnas según tu código original: | |
| # [2,3,4,5] → 'Name','Bitcoin','BTC in USD','% Total BTC 21M' | |
| df_descargado_ETFs = df_etfs_raw[[2, 3, 4, 5]].copy() | |
| df_descargado_ETFs.columns = ['Name', 'Bitcoin', 'BTC in USD', '% Total BTC 21M'] | |
| df_descargado_ETFs[['Name', 'Ticker']] = ( | |
| df_descargado_ETFs['Name'].apply(limpiar_texto).str.rsplit(' ', n=1, expand=True) | |
| ) | |
| # Si no hay ticker separado, usa Name como fallback (tal cual hacías) | |
| df_descargado_ETFs['Ticker'].fillna(df_descargado_ETFs['Name'], inplace=True) | |
| df_descargado_ETFs['BTC in USD'] = df_descargado_ETFs['BTC in USD'].apply(expand_money) | |
| df_descargado_ETFs['Bitcoin'] = df_descargado_ETFs['Bitcoin'].apply(limpiar_texto).apply(expand_money) | |
| df_descargado_ETFs.set_index('Ticker', inplace=True) | |
| else: | |
| df_descargado_ETFs = pd.DataFrame() | |
| # -------------------------------------------------------- | |
| # 5) Flag 'Options' (si yfinance expone cadena de opciones) | |
| # -------------------------------------------------------- | |
| tickers: List[str] = [] | |
| if not df_descargado_stocks.empty: | |
| tickers.extend(df_descargado_stocks.index.tolist()) | |
| if not df_descargado_ETFs.empty: | |
| tickers.extend(df_descargado_ETFs.index.tolist()) | |
| options_bool: Dict[str, bool] = {} | |
| for t in tickers: | |
| if t is None: | |
| continue | |
| try: | |
| options_bool[t] = False if not yf.Ticker(t).options else True | |
| except Exception: | |
| options_bool[t] = False | |
| options_series = pd.Series(options_bool) | |
| if not df_descargado_stocks.empty: | |
| df_descargado_stocks['Options'] = options_series | |
| if not df_descargado_ETFs.empty: | |
| df_descargado_ETFs['Options'] = options_series | |
| # -------------------------------------------------------- | |
| # 6) Escribir el Excel final a partir de la plantilla | |
| # -------------------------------------------------------- | |
| nuevo_archivo_path = "resumen_datos_holding.xlsx" | |
| try: | |
| shutil.copyfile(local_template_path, nuevo_archivo_path) | |
| except FileNotFoundError: | |
| # Limpiar plantilla temporal antes de salir | |
| try: | |
| os.remove(local_template_path) | |
| except Exception: | |
| pass | |
| return None, f"Error: No se encontró la plantilla en la ruta: {local_template_path}" | |
| # Escribir hojas (reemplaza si existen) | |
| with pd.ExcelWriter(nuevo_archivo_path, engine="openpyxl", mode="a", if_sheet_exists="replace") as writer: | |
| if not df_descargado_stocks.empty: | |
| df_descargado_stocks.to_excel(writer, sheet_name="Stocks", index=True) | |
| if not df_descargado_ETFs.empty: | |
| df_descargado_ETFs.to_excel(writer, sheet_name="ETFs", index=True) | |
| # Eliminar la plantilla temporal para limpiar entorno | |
| try: | |
| os.remove(local_template_path) | |
| except Exception: | |
| pass | |
| return nuevo_archivo_path, "Archivo Excel generado con éxito." | |
| except Exception as e: | |
| # En caso de error no previsto | |
| return None, f"Error al exportar: {e}" | |
| __all__ = [ | |
| "exportar_correlacion", | |
| "descargar_resumen_holding_excel", | |
| "extract_file_id", | |
| ] | |