Quant0 commited on
Commit
5aa014a
·
verified ·
1 Parent(s): db57de7

Create analytics.py

Browse files
Files changed (1) hide show
  1. analytics.py +177 -0
analytics.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ============================================================
2
+ # analytics.py — Cálculo de correlaciones entre activos y BTC
3
+ # ------------------------------------------------------------
4
+ # NOTA PARA AUDITORÍA:
5
+ # - Este módulo expone la función `evolucion_correlacion`, encargada de
6
+ # calcular la correlación rolling entre el precio de cierre de un
7
+ # activo (empresa/ETF) y BTC-USD.
8
+ # - La función:
9
+ # 1) Obtiene (o descarga si falta) los históricos con yfinance.
10
+ # 2) Alinea por fecha y calcula rendimientos diarios (% change).
11
+ # 3) Aplica correlación móvil (rolling window) de tamaño
12
+ # `window_months * 30` días.
13
+ # 4) Devuelve una Serie con la evolución o, si step=0, el último
14
+ # valor de correlación como serie de un solo punto.
15
+ # - Dependencias externas: yfinance, pandas, datetime.
16
+ # - Caché en memoria: `all_historical_data` (dict[str, DataFrame]).
17
+ # Este diccionario debe existir en tiempo de ejecución. Por defecto,
18
+ # se intenta importar desde `data_sources` (propuesta de modularización)
19
+ # o, si no existe, desde `processes` (compatibilidad con tu estructura
20
+ # actual). Si no se encuentra, se crea un dict vacío.
21
+ # - Recomendación futura: sustituir `print` por `logging`.
22
+ # ============================================================
23
+
24
+ from __future__ import annotations
25
+
26
+ from datetime import datetime, timedelta
27
+ from typing import Optional
28
+
29
+ import pandas as pd
30
+ import yfinance as yf
31
+
32
+ # ------------------------------------------------------------
33
+ # Importación del caché global de históricos
34
+ # - Primero intenta el módulo recomendado `data_sources`
35
+ # - Si no existe, recurre al módulo histórico `processes`
36
+ # - Como último recurso, define un dict vacío (seguirá funcionando,
37
+ # pero descargará siempre con yfinance).
38
+ # ------------------------------------------------------------
39
+ try:
40
+ # Proyecto modularizado (recomendado)
41
+ from data_sources import all_historical_data # type: ignore
42
+ except Exception:
43
+ try:
44
+ # Compatibilidad con el proyecto actual
45
+ from processes import all_historical_data # type: ignore
46
+ except Exception:
47
+ # Fallback mínimo
48
+ all_historical_data = {} # type: ignore
49
+ print(
50
+ "[analytics] Advertencia: no se encontró 'all_historical_data' en "
51
+ "'data_sources' ni en 'processes'. Se usará un diccionario vacío."
52
+ )
53
+
54
+ __all__ = ["evolucion_correlacion"]
55
+
56
+
57
+ def evolucion_correlacion(
58
+ ticker_company: str,
59
+ step: int,
60
+ ticker_btc: str = "BTC-USD",
61
+ window_months: int = 12,
62
+ ) -> Optional[pd.Series]:
63
+ """
64
+ Calcula la evolución de la correlación (rolling) entre un activo (empresa/ETF)
65
+ y BTC-USD, usando precios de CIERRE diarios.
66
+
67
+ Parámetros
68
+ ----------
69
+ ticker_company : str
70
+ Ticker del activo a analizar (p. ej., 'MSTR', 'TSLA', 'COIN').
71
+ step : int
72
+ Salto en días para muestrear la serie de correlación resultante.
73
+ - Si `step == 0`: devuelve SOLO el último valor de correlación
74
+ (una Serie con un único punto indexado en `end_date`).
75
+ - Si `step > 0`: devuelve la serie completa de correlación rolling
76
+ muestreada cada `step` filas (conservando el offset original).
77
+ ticker_btc : str, opcional
78
+ Ticker de BTC (por defecto: "BTC-USD").
79
+ Debe existir o poder descargarse con yfinance.
80
+ window_months : int, opcional
81
+ Tamaño de la ventana móvil en meses (por defecto: 12).
82
+ Internamente se convierte a días: `window_months * 30`.
83
+
84
+ Devolución
85
+ ----------
86
+ pandas.Series | None
87
+ Serie temporal con la evolución de la correlación rolling.
88
+ Devuelve `None` si faltan datos o no se pudieron descargar.
89
+
90
+ Notas de implementación
91
+ -----------------------
92
+ - Lógica conservada tal cual respecto a tu implementación original:
93
+ * Descarga condicional con yfinance si el ticker no está en `all_historical_data`.
94
+ * Cálculo de rendimientos con `pct_change()` y `dropna()`.
95
+ * Ventana rolling expresada como texto con sufijo 'D' (días).
96
+ * Cuando `step > 0`, se devuelve `dynamic_corr.iloc[9::step]`, manteniendo
97
+ el mismo offset de tu código.
98
+ - Recomendación: sustituir prints por `logging` y añadir control de reintentos.
99
+ """
100
+
101
+ # Rango temporal de trabajo: ~10 años atrás hasta hoy (misma lógica)
102
+ end_date = datetime.now()
103
+ start_date = end_date - timedelta(days=10 * 365)
104
+
105
+ # ------------------------------------------------------------
106
+ # Descarga condicional para el ticker de la empresa
107
+ # ------------------------------------------------------------
108
+ if ticker_company not in all_historical_data:
109
+ print(f"Descargando datos para {ticker_company}...")
110
+ data_company = yf.download(ticker_company, start=start_date, end=end_date)
111
+ if not data_company.empty:
112
+ all_historical_data[ticker_company] = data_company
113
+ print(f"Datos de {ticker_company} descargados con éxito.")
114
+ else:
115
+ print(f"Error al descargar datos para {ticker_company}.")
116
+ return None
117
+
118
+ # ------------------------------------------------------------
119
+ # Descarga condicional para BTC-USD (o el ticker_btc especificado)
120
+ # ------------------------------------------------------------
121
+ if ticker_btc not in all_historical_data:
122
+ print(f"Descargando datos para {ticker_btc}...")
123
+ data_btc = yf.download(ticker_btc, start=start_date, end=end_date)
124
+ if not data_btc.empty:
125
+ all_historical_data[ticker_btc] = data_btc
126
+ print(f"Datos de {ticker_btc} descargados con éxito.")
127
+ else:
128
+ print(f"Error al descargar datos para {ticker_btc}.")
129
+ return None
130
+
131
+ # ------------------------------------------------------------
132
+ # Extracción de cierres y alineación por fechas
133
+ # ------------------------------------------------------------
134
+ company_data: pd.DataFrame = all_historical_data.get(ticker_company, pd.DataFrame())
135
+ btc_data: pd.DataFrame = all_historical_data.get(ticker_btc, pd.DataFrame())
136
+
137
+ if company_data.empty or btc_data.empty:
138
+ print(
139
+ f"No podemos calcular la correlación dinámica: "
140
+ f"No hay datos disponibles para {ticker_company} o {ticker_btc}."
141
+ )
142
+ return None
143
+
144
+ company_prices = company_data["Close"]
145
+ btc_prices = btc_data["Close"]
146
+
147
+ # Alinear por índice (fecha) y eliminar faltantes
148
+ combined_prices = pd.concat([company_prices, btc_prices], axis=1).dropna()
149
+ combined_prices.columns = [ticker_company, ticker_btc]
150
+
151
+ if combined_prices.empty:
152
+ print(
153
+ f"No hay datos históricos en común entre {ticker_company} y {ticker_btc}."
154
+ )
155
+ return None
156
+
157
+ # ------------------------------------------------------------
158
+ # Cálculo de rendimientos y correlación rolling
159
+ # ------------------------------------------------------------
160
+ company_returns = combined_prices[ticker_company].pct_change().dropna()
161
+ btc_returns = combined_prices[ticker_btc].pct_change().dropna()
162
+
163
+ window_days = window_months * 30 # Mantener equivalencia con el código original
164
+
165
+ if step == 0:
166
+ # Correlación en la última fecha disponible (Serie 1x1)
167
+ dynamic_corr_last = (
168
+ company_returns.rolling(window=f"{window_days}D").corr(btc_returns).iloc[-1]
169
+ )
170
+ # Devolvemos una Serie con índice en la fecha actual (misma firma de retorno)
171
+ return pd.Series([dynamic_corr_last], index=[end_date])
172
+
173
+ # Serie completa de correlación rolling, muestreada cada `step` elementos
174
+ dynamic_corr = company_returns.rolling(window=f"{window_days}D").corr(btc_returns).dropna()
175
+
176
+ # Conserva el muestreo con desplazamiento como en tu implementación (iloc[9::step])
177
+ return dynamic_corr.iloc[9::step]