Quant0 commited on
Commit
0ff5c02
路
verified 路
1 Parent(s): 4b54e83

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +585 -0
app.py ADDED
@@ -0,0 +1,585 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # @title Texto de t铆tulo predeterminado
2
+ # Instala la librer铆a gdown para descargar archivos de Google Drive
3
+ # Esto solo necesita ejecutarse una vez al inicio.
4
+ # Se incluye aqu铆 para que el c贸digo sea autocontenido.
5
+ import plotly.graph_objects as go
6
+ import gradio as gr
7
+ import pandas as pd
8
+ import yfinance as yf
9
+ import matplotlib.pyplot as plt
10
+ from datetime import datetime, timedelta
11
+ import os
12
+ from IPython.display import HTML
13
+ import numpy as np
14
+ import requests
15
+ from bs4 import BeautifulSoup
16
+ import re
17
+ from openpyxl.utils import get_column_letter
18
+ import shutil
19
+ import gdown
20
+ from processes import all_historical_data
21
+ from processes import data_option
22
+
23
+ # Diccionarios de datos y funciones auxiliares
24
+ tickers_to_ids = {
25
+ "MSTR": 1, "MARA": 4, "CEP": 436, "BSTR": 674, "RIOT": 7, "DJT": 671, "3350.T": 176, "GLXY": 10, "CLSK": 28, "TSLA": 2,
26
+ "HUT": 6, "COIN": 8, "XYZ": 5, "NXTT": 473, "CCCM": 595, "SMLR": 194, "GME": 517, "CANG": 337, "ADE.DE": 11, "0434.HK": 238,
27
+ "HOLO": 508, "SQNS": 644, "HIVE": 9, "EXOD": 168, "ALTBG.PA": 295, "3659.T": 14, "FUFU": 325, "BTDR": 324, "SWC.AQ": 440, "FLD": 256,
28
+ "CAN": 111, "BITF": 12, "CIFR": 82, "3825.T": 301, "KULR": 341, "NA": 331, "CORZ": 3, "MSW": 356, "SOS": 426, "BTCT.V": 608,
29
+ "AKER.OL": 15, "GS9": 491, "CASH3.SA": 395, "MELI": 182, "SRAG.DU": 279, "JAS.BK": 307, "DCC.AX": 24, "ARLP": 425, "377030.KQ": 401, "BTBT": 18,
30
+ "NDA.V": 32, "DDC": 500, "NTHOL.IS": 40, "DMGI.V": 23, "NCTY": 442, "VLCN": 679, "PNIX.AQ": 582, "ABT.DU": 21, "LQWD.V": 29, "CS.ST": 288,
31
+ "VIRT": 472, "112040.KQ": 403, "RUM": 402, "DEFI.NE": 254, "GNS": 294, "PRE": 573, "3189.T": 445, "BTCB.SE": 636, "LMFA": 91, "BMNR": 550,
32
+ "SIX.CN": 597, "BNXAF": 30, "PHX.AD": 449, "HKHC": 480, "042420.KQ": 406, "BTC.BK": 31, "K33.ST": 538, "COIN.AQ": 483, "BIGG.V": 17, "BTM": 372,
33
+ "KWM": 643, "BTC.L": 292, "8005.HK": 342, "MATA.V": 357, "VANA.MC": 535, "V3TC.AQ": 586, "SBC": 380, "FRMO": 35, "ATAI": 479, "CBIT.V": 27,
34
+ "MLGO": 511, "MOGO": 36, "8267.HK": 239, "WNDR.TO": 242, "SATO.V": 240, "9238.T": 434, "1723.HK": 384, "PXI.CN": 634, "SATS.L": 666, "ALBDM.PA": 580,
35
+ "IREN": 12, "WULF": 15, "GREE": 21, "EBON": 14, "DGHI": 25, "SDIG": 17, "MIGI": 16, "SLNH": 24, "ANY": 23, "APLD": 20,
36
+ "BTCS": 19, "RVYL": 30, "ICG": 29, "HOOD": 35, "PYPL": 2, "NVDA": 22, "CME": 26, "DEFI": 31, "IBIT": 119, "BTF": 33,
37
+ "SQ": 34, "BKKT": 37, "BTCM": 38, "MA": 39, "IBM": 40, "GBTC": 50, "BITO": 41, "BITB": 117, "BTC-USD": 0, "MTPLF": 176
38
+ }
39
+
40
+ # Diccionario global para almacenar los datos hist贸ricos
41
+
42
+
43
+ def evolucion_correlacion(ticker_company, step, ticker_btc="BTC-USD", window_months=12):
44
+ """
45
+ Calcula la evoluci贸n de la correlaci贸n entre el precio de las acciones de una empresa y Bitcoin.
46
+ Args:
47
+ ticker_company (str): El s铆mbolo de cotizaci贸n de la empresa.
48
+ step (int): El n煤mero de d铆as para el desplazamiento de la correlaci贸n.
49
+ ticker_btc (str): El s铆mbolo de cotizaci贸n de Bitcoin (por defecto: "BTC-USD").
50
+ window_months (int): El tama帽o de la ventana m贸vil en meses.
51
+ outputs:
52
+ pandas.Series: Una serie con la evoluci贸n de la correlaci贸n, o None si faltan datos.
53
+ """
54
+ end_date = datetime.now()
55
+ start_date = end_date - timedelta(days=10*365)
56
+
57
+ if ticker_company not in all_historical_data:
58
+ print(f"Descargando datos para {ticker_company}...")
59
+ data = yf.download(ticker_company, start=start_date, end=end_date)
60
+ if not data.empty:
61
+ all_historical_data[ticker_company] = data
62
+ print(f"Datos de {ticker_company} descargados con 茅xito.")
63
+ else:
64
+ print(f"Error al descargar datos para {ticker_company}.")
65
+ return None
66
+
67
+ if ticker_btc not in all_historical_data:
68
+ print(f"Descargando datos para {ticker_btc}...")
69
+ data = yf.download(ticker_btc, start=start_date, end=end_date)
70
+ if not data.empty:
71
+ all_historical_data[ticker_btc] = data
72
+ print(f"Datos de {ticker_btc} descargados con 茅xito.")
73
+ else:
74
+ print(f"Error al descargar datos para {ticker_btc}.")
75
+ return None
76
+
77
+ company_data = all_historical_data.get(ticker_company)
78
+ btc_data = all_historical_data.get(ticker_btc)
79
+
80
+ if company_data.empty or btc_data.empty:
81
+ print(f"No podemos calcular la correlaci贸n din谩mica: No hay datos disponibles para {ticker_company} o {ticker_btc}.")
82
+ return None
83
+
84
+ company_prices = company_data['Close']
85
+ btc_prices = btc_data['Close']
86
+
87
+ combined_prices = pd.concat([company_prices, btc_prices], axis=1).dropna()
88
+ combined_prices.columns = [ticker_company, ticker_btc]
89
+
90
+ if combined_prices.empty:
91
+ print(f"No hay datos hist贸ricos en com煤n entre {ticker_company} y {ticker_btc}.")
92
+ return None
93
+
94
+ company_returns = combined_prices[ticker_company].pct_change().dropna()
95
+ btc_returns = combined_prices[ticker_btc].pct_change().dropna()
96
+
97
+ window_days = window_months * 30
98
+ if step == 0:
99
+ dynamic_corr = company_returns.rolling(window=f"{window_days}D").corr(btc_returns).iloc[-1]
100
+ return pd.Series([dynamic_corr], index=[end_date])
101
+ else:
102
+ dynamic_corr = company_returns.rolling(window=f"{window_days}D").corr(btc_returns).dropna()
103
+ return dynamic_corr.iloc[9::step]
104
+
105
+
106
+ def limpiar_texto(texto):
107
+ """Elimina caracteres especiales y espacios extra."""
108
+ texto_limpio_parcial = re.sub(r'[^a-zA-Z0-9.,\s]', '', texto)
109
+ texto_final = texto_limpio_parcial.strip()
110
+ return texto_final
111
+
112
+ def expand_money(val):
113
+ """Convierte strings de dinero (ej. '$1.2B') a valores flotantes."""
114
+ if pd.isna(val):
115
+ return np.nan
116
+ val = str(val).replace('$', '').replace(',','').upper()
117
+ try:
118
+ if 'B' in val:
119
+ return float(val.replace('B', '')) * 1_000_000_000
120
+ elif 'M' in val:
121
+ return float(val.replace('M', '')) * 1_000_000
122
+ elif 'K' in val:
123
+ return float(val.replace('K', '')) * 1_000
124
+ else:
125
+ return float(val)
126
+ except:
127
+ return np.nan
128
+
129
+ def descargar_tabla_web(url, indice_tabla=0):
130
+ """Descarga una tabla de una URL."""
131
+ try:
132
+ # A帽adir headers con un User-Agent
133
+ headers = {
134
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'
135
+ }
136
+
137
+ response = requests.get(url, headers=headers)
138
+ response.raise_for_status()
139
+
140
+ soup = BeautifulSoup(response.text, 'html.parser')
141
+ tables = soup.find_all('table')
142
+
143
+ if len(tables) > indice_tabla:
144
+ table = tables[indice_tabla]
145
+ else:
146
+ print(f"No se encontr贸 la tabla con 铆ndice {indice_tabla} en la p谩gina: {url}.")
147
+ return None
148
+
149
+ headers = [th.text.strip() for th in table.find_all('th')]
150
+ data = []
151
+ for row in table.find_all('tr'):
152
+ cols = row.find_all('td')
153
+ cols = [ele.text.strip() for ele in cols]
154
+ if cols:
155
+ data.append(cols)
156
+
157
+ if headers and data and len(headers) == len(data[0]):
158
+ df = pd.DataFrame(data, columns=headers)
159
+ else:
160
+ df = pd.DataFrame(data)
161
+ return df
162
+
163
+ except requests.exceptions.RequestException as e:
164
+ print(f"Error al acceder a la URL: {e}")
165
+ return None
166
+ except Exception as e:
167
+ print(f"Ocurri贸 un error inesperado: {e}")
168
+ return None
169
+ def generar_tickers():
170
+ """
171
+ Genera una lista global de tickers a partir de las tablas de stocks y ETFs.
172
+ """
173
+ global tickers
174
+ tickers = []
175
+
176
+ # URLs de donde obtener los datos
177
+ url_stocks = "https://bitcointreasuries.net/"
178
+ url_etfs = "https://bitcointreasuries.net/etfs-and-exchanges"
179
+
180
+ # Descargar tablas web (se asume que estas funciones existen)
181
+ df_stocks = descargar_tabla_web(url_stocks, indice_tabla=3)
182
+ df_etfs = descargar_tabla_web(url_etfs, indice_tabla=3)
183
+
184
+ # Procesar Stocks si existen
185
+ if df_stocks is not None and not df_stocks.empty:
186
+ # Asumiendo que limpiar_texto ya est谩 definida
187
+ df_stocks[['Name', 'Ticker']] = df_stocks[2].apply(limpiar_texto).str.rsplit(' ', n=1, expand=True)
188
+ tickers.extend(df_stocks['Ticker'].dropna().tolist())
189
+
190
+ # Procesar ETFs si existen
191
+ if df_etfs is not None and not df_etfs.empty:
192
+ df_etfs[['Name', 'Ticker']] = df_etfs[2].apply(limpiar_texto).str.rsplit(' ', n=1, expand=True)
193
+ df_etfs['Ticker'].fillna(df_etfs['Name'], inplace=True)
194
+ tickers.extend(df_etfs['Ticker'].dropna().tolist())
195
+
196
+ generar_tickers()
197
+
198
+ def extract_file_id(url):
199
+ """Extrae el ID del archivo de una URL de Google Drive."""
200
+ match = re.search(r'/d/([a-zA-Z0-9_-]+)', url)
201
+ if match:
202
+ return match.group(1)
203
+ return None
204
+
205
+ def descargar_resumen_holding_excel(template_url):
206
+ """
207
+ Descarga los datos de holding de BTC para stocks y ETFs,
208
+ los procesa, calcula correlaciones y exporta a un archivo Excel
209
+ usando una plantilla de Google Drive.
210
+ """
211
+ try:
212
+ # Extraer el ID del archivo de la URL
213
+ file_id = extract_file_id(template_url)
214
+ if not file_id:
215
+ return None, "Error: No se pudo extraer el ID del archivo de la URL de Google Drive."
216
+
217
+ local_template_path = 'plantilla_temporal.xlsx'
218
+
219
+ # Descargar la plantilla de Google Drive
220
+ try:
221
+ gdown.download(id=file_id, output=local_template_path, quiet=False)
222
+ except Exception as e:
223
+ return None, f"Error al descargar la plantilla desde Google Drive: {e}"
224
+
225
+ url_stocks = "https://bitcointreasuries.net/"
226
+ url_ETFs = "https://bitcointreasuries.net/etfs-and-exchanges"
227
+
228
+ df_stocks = descargar_tabla_web(url_stocks, indice_tabla=3)
229
+ df_etfs = descargar_tabla_web(url_ETFs, indice_tabla=3)
230
+
231
+ if df_stocks is None and df_etfs is None:
232
+ return None, "Error: No se pudieron descargar los datos de holding."
233
+
234
+ # Procesar Stocks
235
+ if df_stocks is not None:
236
+ df_descargado_stocks = df_stocks[[2,3,4,5,12]].copy()
237
+ df_descargado_stocks.columns = ['Name','Bitcoin', 'BTC in USD', 'Marketcap', '% Total BTC 21M']
238
+ df_descargado_stocks[['Name','Ticker']] = df_descargado_stocks['Name'].apply(limpiar_texto).str.rsplit(' ', n=1, expand=True)
239
+
240
+ df_descargado_stocks['Bitcoin'] = df_descargado_stocks['Bitcoin'].apply(limpiar_texto)
241
+ df_descargado_stocks['Marketcap'] = df_descargado_stocks['Marketcap'].apply(limpiar_texto)
242
+ df_descargado_stocks['BTC in USD'] = df_descargado_stocks['BTC in USD'].apply(expand_money)
243
+ df_descargado_stocks['Marketcap'] = df_descargado_stocks['Marketcap'].apply(expand_money)
244
+ df_descargado_stocks['Ratio Holding/Mcap'] = df_descargado_stocks['BTC in USD'] / df_descargado_stocks['Marketcap']
245
+
246
+ corrls = []
247
+ for stock in df_descargado_stocks['Ticker']:
248
+ try:
249
+ corr_value = evolucion_correlacion(stock, step=0, window_months=12).iloc[0]
250
+ corrls.append(corr_value)
251
+ except Exception as e:
252
+ print(f"Error al calcular correlaci贸n para {stock}: {e}")
253
+ corrls.append(np.nan)
254
+ df_descargado_stocks['Correlaci贸n con BTC (12 meses)'] = pd.Series(corrls)
255
+ df_descargado_stocks.set_index('Ticker', inplace=True)
256
+ else:
257
+ df_descargado_stocks = pd.DataFrame()
258
+
259
+
260
+ # Procesar ETFs
261
+ if df_etfs is not None:
262
+ df_descargado_ETFs = df_etfs[[2,3,4,5]].copy()
263
+ df_descargado_ETFs.columns = ['Name','Bitcoin', 'BTC in USD','% Total BTC 21M']
264
+ df_descargado_ETFs[['Name','Ticker']] = df_descargado_ETFs['Name'].apply(limpiar_texto).str.rsplit(' ', n=1, expand=True)
265
+ df_descargado_ETFs['Ticker'].fillna(df_descargado_ETFs['Name'], inplace=True)
266
+ df_descargado_ETFs['BTC in USD'] = df_descargado_ETFs['BTC in USD'].apply(expand_money)
267
+ df_descargado_ETFs['Bitcoin'] = df_descargado_ETFs['Bitcoin'].apply(limpiar_texto).apply(expand_money)
268
+ df_descargado_ETFs.set_index('Ticker', inplace=True)
269
+ else:
270
+ df_descargado_ETFs = pd.DataFrame()
271
+
272
+ # A帽adir la columna de 'Options' como solicitaste
273
+ global tickers
274
+ tickers = []
275
+ if not df_descargado_stocks.empty:
276
+ tickers.extend(df_descargado_stocks.index.tolist())
277
+ if not df_descargado_ETFs.empty:
278
+ tickers.extend(df_descargado_ETFs.index.tolist())
279
+
280
+ options_bool = {}
281
+ for ticker in tickers:
282
+ if ticker is not None:
283
+ try:
284
+ options_bool[ticker] = False if not yf.Ticker(ticker).options else True
285
+ except:
286
+ options_bool[ticker] = False
287
+
288
+ options_series = pd.Series(options_bool)
289
+
290
+ if not df_descargado_stocks.empty:
291
+ df_descargado_stocks['Options'] = options_series
292
+ if not df_descargado_ETFs.empty:
293
+ df_descargado_ETFs['Options'] = options_series
294
+
295
+
296
+ # Exportaci贸n a Excel
297
+ if not df_descargado_stocks.empty or not df_descargado_ETFs.empty:
298
+ nuevo_archivo_path = 'resumen_datos_holding.xlsx'
299
+
300
+ # Copiar la plantilla descargada antes de escribir
301
+ try:
302
+ shutil.copyfile(local_template_path, nuevo_archivo_path)
303
+ except FileNotFoundError:
304
+ return None, f"Error: No se encontr贸 la plantilla en la ruta: {local_template_path}"
305
+
306
+ # Escribir en el archivo copiado
307
+ with pd.ExcelWriter(nuevo_archivo_path, engine='openpyxl', mode='a', if_sheet_exists='replace') as writer:
308
+ if not df_descargado_stocks.empty:
309
+ df_descargado_stocks.to_excel(writer, sheet_name='Stocks', index=True)
310
+ if not df_descargado_ETFs.empty:
311
+ df_descargado_ETFs.to_excel(writer, sheet_name='ETFs', index=True)
312
+
313
+ # Eliminar la plantilla temporal descargada para limpiar el entorno
314
+ os.remove(local_template_path)
315
+
316
+ return nuevo_archivo_path, "Archivo Excel generado con 茅xito."
317
+ else:
318
+ return None, "No se pudieron generar los datos para exportar."
319
+
320
+ except Exception as e:
321
+ return None, f"Error al exportar: {e}"
322
+
323
+
324
+ # Funciones para Gradio
325
+
326
+ def plot_evolucion_correlacion_gradio(ticker_company, step, window_months):
327
+
328
+ """
329
+ Genera el gr谩fico de la evoluci贸n de la correlaci贸n.
330
+ """
331
+ serie = evolucion_correlacion(ticker_company, step, window_months=12)
332
+
333
+ if serie is None or serie.empty:
334
+ return None, f"No hay datos de correlaci贸n disponibles para {ticker_company}."
335
+
336
+ # --- Create the Plotly graph ---
337
+ fig = go.Figure()
338
+
339
+ # Add the correlation series
340
+ fig.add_trace(go.Scatter(
341
+ x=serie.index,
342
+ y=serie.values,
343
+ mode='lines',
344
+ name='Correlaci贸n',
345
+ line=dict(color='#2263eb', width=2), # electric blue color for the line
346
+ fill='tozeroy', # Fill the area down to Y=0 axis
347
+ fillcolor='rgba(34,99,235,0.1)', # Very faint elctric blue shadow (10% opacity)
348
+ hovertemplate='Fecha: %{x|%Y-%m-%d}<br>Correlaci贸n: %{y:.4f}<extra></extra>'
349
+ ))
350
+
351
+ # --- Configure axes and range selector ---
352
+ fig.update_layout(
353
+ title_text=f'<b>Evoluci贸n de la correlaci贸n entre {ticker_company} y BTC-USD<b>',
354
+ title_x=0, # Align title to the left
355
+ height=500, # Graph height
356
+ template="plotly_white", # Graph theme (clean and modern)
357
+ margin=dict(l=20, r=20, t=60, b=20), # Graph margins
358
+ showlegend=False, # No legend needed if only one main series
359
+
360
+ xaxis=go.layout.XAxis(
361
+ rangeslider=dict(
362
+ visible=True,
363
+ thickness=0.08, # Rangeslider thickness
364
+ bgcolor="rgba(50, 0, 128, 0.1)", # Very transparent purple for rangeslider
365
+ bordercolor="rgba(50, 0, 128, 0.2)", # Slightly more visible border
366
+ borderwidth=1,
367
+ range=[serie.index.min(), serie.index.max()] # Initial slider range
368
+ ),
369
+ rangeselector=dict(
370
+ buttons=list([
371
+ dict(count=1, label="1m", step="month", stepmode="backward"),
372
+ dict(count=3, label="3m", step="month", stepmode="backward"),
373
+ dict(count=6, label="6m", step="month", stepmode="backward"),
374
+ dict(step="year", count=1, label="YTD", stepmode="todate"), # YTD
375
+ dict(count=1, label="1y", step="year", stepmode="backward"),
376
+ dict(step="all", label="All")
377
+ ]),
378
+ bgcolor="rgba(247,247,247,255)", # Transparent background for buttons
379
+ bordercolor="rgba(0,0,0,0)", # Transparent border
380
+ borderwidth=0,
381
+ activecolor="rgba(230,233,255,255)", # Active button color (slightly darker purple)
382
+ font=dict(color="#000000", size=10), # Button font style
383
+ x=0.9, y=1.1, xanchor='right', yanchor='top' # Button position
384
+ ),
385
+ type="date",
386
+ domain=[0, 1], # Occupy full graph width
387
+ rangeslider_yaxis_rangemode="auto", # Rangeslider Y-axis range adjusts automatically
388
+ showgrid=False,
389
+ gridcolor='#e0e0e0',
390
+ zeroline=False
391
+ ),
392
+ yaxis=go.layout.YAxis(
393
+ title_text='Correlaci贸n',
394
+ side='left',
395
+ showgrid=True,
396
+ gridcolor='#e0e0e0',
397
+ zeroline=False,
398
+ tickformat=".2f", # 2 decimal places format
399
+ range=[-1.0, 1.0] # Fixed range for correlation
400
+ ),
401
+ hovermode="x unified", # Unify hover to show data for the series
402
+ paper_bgcolor='rgba(255,255,255,1)', # Transparent background for notebook integration
403
+ plot_bgcolor='rgba(255,255,255,1)'
404
+ )
405
+
406
+ # --- Add annotation for max value (similar to Matplotlib) ---
407
+ max_value = serie.max()
408
+ max_index = serie.idxmax()
409
+ fig.add_annotation(
410
+ x=max_index,
411
+ y=max_value,
412
+ text=f'Corr_max: {max_value:.4f}<br>Fecha: {max_index.date()}',
413
+ showarrow=True,
414
+ arrowhead=2,
415
+ ax=0,
416
+ ay=-40, # Adjust vertical position of the annotation
417
+ bgcolor="rgba(255, 255, 255, 0.8)", # Semi-transparent white background
418
+ bordercolor="#c7c7c7",
419
+ borderwidth=1,
420
+ borderpad=4,
421
+ font=dict(size=10, color="#333"),
422
+ # You can adjust text position with xanchor/yanchor if needed
423
+ )
424
+
425
+ return fig, "Gr谩fico de correlaci贸n generado."
426
+
427
+ """
428
+ if serie is None or serie.empty:
429
+ return plt.figure(), f"No hay datos de correlaci贸n disponibles para {ticker_company}."
430
+ max_value = serie.max()
431
+ min_value = serie.min()
432
+ max_index = serie.idxmax()
433
+ plt.figure(figsize=(10, 6))
434
+ plt.plot(serie)
435
+ plt.xlabel('Fecha')
436
+ plt.ylabel('Correlaci贸n')
437
+ plt.title(f'Evoluci贸n de la correlaci贸n entre {ticker_company} y BTC-USD')
438
+ plt.plot(max_index, max_value, 'ro')
439
+ plt.annotate(f' Corr_max: {max_value:.4f} \n Fecha: {max_index.date()}', (max_index, max_value), xytext=(max_index, max_value - (max_value - min_value) * 0.15))
440
+ lower_margin = min_value - 0.1 * abs(min_value)
441
+ upper_margin = max_value + 0.05 * abs(max_value)
442
+ plt.ylim(lower_margin, upper_margin)
443
+ plt.tight_layout()
444
+ return plt.gcf(), "Gr谩fico de correlaci贸n generado."
445
+ """
446
+
447
+ def exportar_correlacion(ticker_company, step, window_months):
448
+
449
+ """
450
+ Exporta la serie de correlaci贸n a un archivo Excel.
451
+ """
452
+ try:
453
+ serie = evolucion_correlacion(ticker_company, step, window_months=window_months)
454
+ if serie is not None and not serie.empty:
455
+ nombre_archivo = f'{ticker_company}_evolucion_correlacion.xlsx'
456
+ serie.to_excel(nombre_archivo)
457
+ return nombre_archivo, "Archivo Excel generado con 茅xito."
458
+ else:
459
+ return None, "No se pudieron generar los datos para exportar."
460
+ except Exception as e:
461
+ return None, f"Error al exportar: {e}"
462
+
463
+ def get_iframe_stock_vs_btc(ticker):
464
+ """
465
+ Genera el c贸digo HTML para el iframe de Stock vs Bitcoin.
466
+ """
467
+ try:
468
+ entity_id = tickers_to_ids[ticker]
469
+ iframe_code = f"""
470
+ <iframe src="https://bitcointreasuries.net/embed?component=StockVsBitcoinChart&entityId={entity_id}"
471
+ title="Bitcoin Treasuries" credentialless style="width: 100%; height: 660px; border: none;"></iframe>
472
+ """
473
+ return iframe_code
474
+ except KeyError:
475
+ return f"No hay datos de holding de Bitcoin para {ticker}."
476
+
477
+ def get_iframe_holdings(ticker):
478
+ """
479
+ Genera el c贸digo HTML para el iframe de Bitcoin Holdings.
480
+ """
481
+ try:
482
+ entity_id = tickers_to_ids[ticker]
483
+ iframe_code = f"""
484
+ <iframe src="https://bitcointreasuries.net/embed?component=BitcoinHoldingsChart&entityId={entity_id}"
485
+ title="Bitcoin Holdings Chart" credentialless style="width: 100%; height: 660px; border: none;"></iframe>
486
+ """
487
+ return iframe_code
488
+ except KeyError:
489
+ return f"No hay datos de holding de Bitcoin para {ticker}."
490
+
491
+
492
+
493
+
494
+ ### CONFIGURACION DE GRADIO:
495
+
496
+ with gr.Blocks(title="An谩lisis de Correlaci贸n") as demo:
497
+ gr.Image("https://cdn.prod.website-files.com/67174dcb1d43b06a0eae317e/672bbbc7a2b88045e5661994_logo-afi.svg", show_label=False)
498
+ gr.Markdown("# Analizador de Correlaci贸n de Bitcoin")
499
+ gr.Markdown("Explora la correlaci贸n de diferentes activos con Bitcoin y sus holdings.")
500
+
501
+ with gr.Row():
502
+ input_ticker = gr.Dropdown(
503
+ label="Ticker de la Empresa",
504
+ choices= tickers, ### hacer que la lista sea dinamica, luego miraremos el tema de los ids
505
+ value="MSTR",
506
+ allow_custom_value=True
507
+ )
508
+
509
+ with gr.Tab("Evoluci贸n de Correlaci贸n"):
510
+ gr.Markdown("### Evoluci贸n de la correlaci贸n")
511
+ with gr.Row():
512
+ input_step = gr.Slider(minimum=1, maximum=30, value=1, step=1, label="Indica cada cu谩ntos d铆as se calcula la correlaci贸n")
513
+ input_window = gr.Slider(minimum=1, maximum=60, value=12, step=1, label="Ventana rolling (meses)")
514
+ btn_plot = gr.Button("Mostrar Gr谩fico")
515
+ output_plot = gr.Plot(label="Gr谩fico de Evoluci贸n")
516
+ btn_excel = gr.Button("Descargar en Excel")
517
+
518
+
519
+
520
+
521
+
522
+ output_file = gr.File(label="Archivo Excel")
523
+ output_msg = gr.Label()
524
+
525
+ btn_plot.click(
526
+ fn=plot_evolucion_correlacion_gradio,
527
+ inputs=[input_ticker, input_step, input_window],
528
+ outputs=[output_plot, output_msg]
529
+ )
530
+ btn_excel.click(
531
+ fn=exportar_correlacion,
532
+ inputs=[input_ticker, input_step, input_window],
533
+ outputs=[output_file, output_msg]
534
+ )
535
+
536
+ with gr.Tab("Stock vs BTC"):
537
+ gr.Markdown("### Gr谩fico de Stock vs Bitcoin")
538
+ btn_stock_btc = gr.Button("Cargar Gr谩fico")
539
+ gr.Markdown("**NAV Multiplier:**\n\nMuestra la relaci贸n entre el precio de la acci贸n de la empresa y el valor de sus activos, principalmente Bitcoin.\nUn valor superior a 1 indica que el mercado est谩 pagando una prima por la empresa.\nUn valor cercano a 1 sugiere que la valoraci贸n de la empresa se acerca al valor de sus activos.")
540
+ output_iframe_stock = gr.HTML(label="Stock vs Bitcoin Chart")
541
+
542
+ btn_stock_btc.click(
543
+ fn=get_iframe_stock_vs_btc,
544
+ inputs=[input_ticker],
545
+ outputs=[output_iframe_stock]
546
+ )
547
+
548
+ with gr.Tab("Holding de Bitcoin"):
549
+ gr.Markdown("### Gr谩fico de Holding de Bitcoin")
550
+ btn_holdings = gr.Button("Cargar Gr谩fico")
551
+ output_iframe_holdings = gr.HTML(label="Bitcoin Holdings Chart")
552
+
553
+ btn_holdings.click(
554
+ fn=get_iframe_holdings,
555
+ inputs=[input_ticker],
556
+ outputs=[output_iframe_holdings]
557
+ )
558
+
559
+ with gr.Tab("Resumen Holding"):
560
+ gr.Markdown("### Descargar resumen de holdings a Excel")
561
+ gr.Markdown("Esto descargar谩 los datos de holding de BTC de la web, los procesar谩 y crear谩 un archivo Excel a partir de una plantilla.")
562
+
563
+ # Agregado el input para la URL de la plantilla de Google Drive
564
+ input_template_url = gr.Textbox(
565
+ label="URL de la plantilla de Excel (Google Drive)",
566
+ value="https://docs.google.com/spreadsheets/d/1Ad3bbYQAAZO6ljkaCGTGSw5aG_FQF7Lg/edit?usp=sharing&ouid=109222615757657219494&rtpof=true&sd=true",
567
+ placeholder="Ej: https://docs.google.com/spreadsheets/d/ID_DEL_ARCHIVO/..."
568
+ )
569
+
570
+
571
+ btn_descargar_resumen = gr.Button("Descargar Resumen a Excel")
572
+ output_resumen_file = gr.File(label="Archivo de Resumen de Holdings")
573
+ output_resumen_msg = gr.Label()
574
+
575
+ btn_descargar_resumen.click(
576
+ fn=descargar_resumen_holding_excel,
577
+ inputs=[input_template_url], # Ahora pasamos la URL como input
578
+ outputs=[output_resumen_file, output_resumen_msg]
579
+ )
580
+
581
+
582
+
583
+
584
+ #Inicia la aplicaci贸n
585
+ demo.launch(share=True)