diegobeyl/backtesting
2
1"""2Cargador de datos desde yfinance3Soporta criptomonedas, acciones, ETFs e índices4"""5 6import yfinance as yf7import pandas as pd8from typing import Optional, List, Dict9from datetime import datetime, timedelta10import streamlit as st11 12 13# Símbolos disponibles organizados por categoría14# Formato: {categoría: {símbolo: nombre_descriptivo}}15AVAILABLE_SYMBOLS: Dict[str, Dict[str, str]] = {16 'Criptomonedas': {17 'BTC-USD': 'Bitcoin',18 'ETH-USD': 'Ethereum',19 'SOL-USD': 'Solana',20 'BNB-USD': 'Binance Coin',21 'XRP-USD': 'Ripple',22 'DOGE-USD': 'Dogecoin',23 'ADA-USD': 'Cardano',24 'AVAX-USD': 'Avalanche',25 'DOT-USD': 'Polkadot',26 'MATIC-USD': 'Polygon',27 'LINK-USD': 'Chainlink',28 'UNI-USD': 'Uniswap',29 'ATOM-USD': 'Cosmos',30 'LTC-USD': 'Litecoin',31 },32 'Acciones Tech': {33 'AAPL': 'Apple',34 'MSFT': 'Microsoft',35 'GOOGL': 'Google',36 'AMZN': 'Amazon',37 'META': 'Meta (Facebook)',38 'NVDA': 'NVIDIA',39 'TSLA': 'Tesla',40 'AMD': 'AMD',41 'INTC': 'Intel',42 'CRM': 'Salesforce',43 'ADBE': 'Adobe',44 'NFLX': 'Netflix',45 'PYPL': 'PayPal',46 'SHOP': 'Shopify',47 },48 'Acciones Crypto-Related': {49 'MSTR': 'MicroStrategy',50 'COIN': 'Coinbase',51 'MARA': 'Marathon Digital',52 'RIOT': 'Riot Platforms',53 'CLSK': 'CleanSpark',54 'HUT': 'Hut 8 Mining',55 },56 'Acciones Financieras': {57 'JPM': 'JP Morgan',58 'BAC': 'Bank of America',59 'WFC': 'Wells Fargo',60 'GS': 'Goldman Sachs',61 'MS': 'Morgan Stanley',62 'V': 'Visa',63 'MA': 'Mastercard',64 },65 'ETFs': {66 'SPY': 'S&P 500 ETF',67 'QQQ': 'Nasdaq 100 ETF',68 'IWM': 'Russell 2000 ETF',69 'DIA': 'Dow Jones ETF',70 'VTI': 'Total Stock Market',71 'GLD': 'Gold ETF',72 'SLV': 'Silver ETF',73 'USO': 'Oil ETF',74 'IBIT': 'iShares Bitcoin Trust',75 'FBTC': 'Fidelity Bitcoin',76 },77 'Índices': {78 '^GSPC': 'S&P 500 Index',79 '^DJI': 'Dow Jones',80 '^IXIC': 'Nasdaq Composite',81 '^RUT': 'Russell 2000',82 '^VIX': 'VIX Volatility',83 },84 'Forex': {85 'EURUSD=X': 'EUR/USD',86 'GBPUSD=X': 'GBP/USD',87 'USDJPY=X': 'USD/JPY',88 'AUDUSD=X': 'AUD/USD',89 'USDCAD=X': 'USD/CAD',90 },91 'Commodities': {92 'GC=F': 'Gold Futures',93 'SI=F': 'Silver Futures',94 'CL=F': 'Crude Oil Futures',95 'NG=F': 'Natural Gas Futures',96 }97}98 99 100# Lista plana de todos los símbolos101def get_all_symbols() -> List[str]:102 """Retorna lista plana de todos los símbolos disponibles"""103 all_symbols = []104 for category_symbols in AVAILABLE_SYMBOLS.values():105 all_symbols.extend(category_symbols.keys())106 return all_symbols107 108 109# Timeframes disponibles110AVAILABLE_TIMEFRAMES = {111 '1m': '1 Minuto',112 '2m': '2 Minutos',113 '5m': '5 Minutos',114 '15m': '15 Minutos',115 '30m': '30 Minutos',116 '1h': '1 Hora',117 '4h': '4 Horas', # Se convierte internamente118 '1d': 'Diario',119 '1wk': 'Semanal',120 '1mo': 'Mensual',121}122 123 124class DataLoader:125 """126 Cargador de datos históricos usando yfinance127 """128 129 def __init__(self):130 self._cache = {}131 132 @staticmethod133 @st.cache_data(ttl=3600)134 def download(135 symbol: str,136 start_date: str,137 end_date: str,138 interval: str = '1d'139 ) -> pd.DataFrame:140 """141 Descarga datos históricos de yfinance142 143 Args:144 symbol: Símbolo del activo (ej: 'BTC-USD', 'AAPL')145 start_date: Fecha de inicio (YYYY-MM-DD)146 end_date: Fecha de fin (YYYY-MM-DD)147 interval: Intervalo de las velas148 149 Returns:150 DataFrame con columnas OHLCV151 """152 try:153 # Convertir 4h a formato yfinance (60m * 4 = 240m, pero yfinance no lo soporta)154 # Para 4h, descargar 1h y resamplear155 if interval == '4h':156 df = DataLoader._download_and_resample_4h(symbol, start_date, end_date)157 else:158 ticker = yf.Ticker(symbol)159 df = ticker.history(start=start_date, end=end_date, interval=interval)160 161 if df.empty:162 return pd.DataFrame()163 164 # Renombrar columnas para consistencia165 df = df.rename(columns={166 'Open': 'open',167 'High': 'high',168 'Low': 'low',169 'Close': 'close',170 'Volume': 'volume'171 })172 173 # Eliminar columnas innecesarias174 cols_to_keep = ['open', 'high', 'low', 'close', 'volume']175 df = df[[col for col in cols_to_keep if col in df.columns]]176 177 return df178 179 except Exception as e:180 st.error(f"Error descargando datos para {symbol}: {e}")181 return pd.DataFrame()182 183 @staticmethod184 def _download_and_resample_4h(symbol: str, start_date: str, end_date: str) -> pd.DataFrame:185 """Descarga datos de 1h y los convierte a 4h"""186 try:187 ticker = yf.Ticker(symbol)188 df = ticker.history(start=start_date, end=end_date, interval='1h')189 190 if df.empty:191 return pd.DataFrame()192 193 # Renombrar primero194 df = df.rename(columns={195 'Open': 'open',196 'High': 'high', 197 'Low': 'low',198 'Close': 'close',199 'Volume': 'volume'200 })201 202 # Resamplear a 4 horas203 df_4h = df.resample('4h').agg({204 'open': 'first',205 'high': 'max',206 'low': 'min',207 'close': 'last',208 'volume': 'sum'209 }).dropna()210 211 return df_4h212 213 except Exception as e:214 return pd.DataFrame()215 216 @staticmethod217 def download_multiple(218 symbols: List[str],219 start_date: str,220 end_date: str,221 interval: str = '1d'222 ) -> Dict[str, pd.DataFrame]:223 """224 Descarga datos para múltiples símbolos225 226 Returns:227 Diccionario con símbolo como key y DataFrame como value228 """229 results = {}230 for symbol in symbols:231 df = DataLoader.download(symbol, start_date, end_date, interval)232 if not df.empty:233 results[symbol] = df234 return results235 236 @staticmethod237 def get_symbol_info(symbol: str) -> Optional[Dict]:238 """Obtiene información del símbolo"""239 try:240 ticker = yf.Ticker(symbol)241 info = ticker.info242 return {243 'name': info.get('longName', info.get('shortName', symbol)),244 'currency': info.get('currency', 'USD'),245 'exchange': info.get('exchange', 'Unknown'),246 'type': info.get('quoteType', 'Unknown'),247 'sector': info.get('sector', 'N/A'),248 'industry': info.get('industry', 'N/A'),249 }250 except:251 return None252 