Team Ai
Apppublic

diegobeyl/backtesting

sourceHugging Faceupdated 9mo agoView on Hugging Face
2likes
data_loader.py.bak723 linesDownload Raw Back to core
1"""2Cargador de datos multi-fuente3Soporta: Yahoo Finance4"""5 6import pandas as pd7import time8from typing import Optional, List, Dict9from datetime import datetime, timedelta10import streamlit as st11 12import yfinance as yf13 14 15# Fuentes de datos disponibles16DATA_SOURCES = {17    'yahoo': 'Yahoo Finance',18}19 20# Mapeo de símbolos por fuente21SYMBOL_MAPPING = {}22 23# Mapeo de timeframes por fuente24TIMEFRAME_MAPPING = {25    'yahoo': {26        '1m': '1m', '5m': '5m', '15m': '15m', '30m': '30m',27        '1h': '1h', '4h': '4h', '1d': '1d', '1wk': '1wk', '1mo': '1mo'28    },29}30 31 32# Símbolos disponibles organizados por categoría33AVAILABLE_SYMBOLS: Dict[str, Dict[str, str]] = {34    '🇺🇸 USA - Large Cap (Dow Jones, S&P 500, Nasdaq €30M+ mensual)': {35        'AAPL': 'Apple',36        'MSFT': 'Microsoft',37        'GOOGL': 'Alphabet Google',38        'AMZN': 'Amazon',39        'META': 'Meta',40        'NVDA': 'NVIDIA',41        'TSLA': 'Tesla',42        'BRK.B': 'Berkshire Hathaway',43        'JNJ': 'Johnson & Johnson',44        'V': 'Visa',45        'WMT': 'Walmart',46        'JPM': 'JPMorgan',47        'MCD': 'McDonald\'s',48        'INTC': 'Intel',49        'GOOG': 'Alphabet',50        'NFLX': 'Netflix',51            # 'BRK.B' removed per user request52        'AMD': 'AMD',53        'PYPL': 'PayPal',54        'AVGO': 'Broadcom',55        'ADBE': 'Adobe',56        'CRM': 'Salesforce',57        'ACN': 'Accenture',58        'CSCO': 'Cisco',59        'INTC': 'Intel',60        'QCOM': 'Qualcomm',61        'TXN': 'Texas Instruments',62        'MU': 'Micron',63        'LRCX': 'Lam Research',64        'AMAT': 'Applied Materials',65        'KLAC': 'KLA',66        'MRVL': 'Marvell',67        # 'ANSS' removed per user request68        'ASML': 'ASML Holdings',69        'NXPI': 'NXP',70        'SANM': 'Sanmina',71        'SMCI': 'Super Micro',72        'TSM': 'TSMC',73        'VLO': 'Valero',74        'WMB': 'Williams',75        'OKE': 'ONEOK',76        'KMI': 'Kinder Morgan',77        'SPY': 'S&P 500 ETF',78        'QQQ': 'Nasdaq 100 ETF',79        'DIA': 'Dow Jones ETF',80        'IWM': 'Russell 2000 ETF',81        'EEM': 'Emerging Markets ETF',82        'EFA': 'Int\'l Developed ETF',83        'AGG': 'Bond ETF',84        'BND': 'Total Bond ETF',85        'BAC': 'Bank of America',86        'WFC': 'Wells Fargo',87        'C': 'Citigroup',88        'USB': 'US Bancorp',89        'PNC': 'PNC Financial',90        'TFC': 'Truist',91        'COF': 'Capital One',92        'GS': 'Goldman Sachs',93        'MS': 'Morgan Stanley',94        'BX': 'Blackstone',95        'KKR': 'KKR',96        'APO': 'Apollo',97        'SCHW': 'Schwab',98        'BLK': 'BlackRock',99        'VANG': 'Vanguard',100        'SPLG': 'SPDR Portfolio',101        'XLV': 'Healthcare ETF',102        'XLK': 'Tech ETF',103        'XLY': 'Consumer ETF',104        'XLE': 'Energy ETF',105        'XLRE': 'Real Estate ETF',106        'XLP': 'Staples ETF',107        'XLI': 'Industrial ETF',108        'XLUF': 'Utility ETF',109        'XLNX': 'Xilinx',110        'CRH': 'CRH',111        'NRG': 'NRG Energy',112        'EXC': 'Exelon',113        114        'SO': 'Southern Company',115        'DUK': 'Duke Energy',116        'EDF': 'EDF Energy',117        'EOAN': 'E.ON',118        'RWE': 'RWE',119        'AEP': 'American Electric Power',120        'XEL': 'Xcel Energy',121        'CMS': 'CMS Energy',122        'DTE': 'DTE Energy',123        'AWK': 'American Water',124        'WTR': 'Water Tech',125        'NEE': 'NextEra',126        'LIN': 'Linde',127        'APD': 'Air Products',128        'ECL': 'Ecolab',129        'HLT': 'Hilton',130        'RHT': 'Red Hat',131        'IBM': 'IBM',132        'X': 'US Steel',133        'MT': 'Mittal Steel',134        'CLF': 'Cleveland-Cliffs',135        'RX': 'Rexahn Pharmaceuticals',136        'NRG': 'NRG Energy',137        'EXC': 'Exelon',138        'SO': 'Southern Company',139        'DUK': 'Duke Energy',140        'AEP': 'American Electric Power',141        'XEL': 'Xcel Energy',142        'CMS': 'CMS Energy',143        'DTE': 'DTE Energy',144        'AWK': 'American Water',145        'NEE': 'NextEra Energy',146        'LIN': 'Linde',147        'APD': 'Air Products',148        'ECL': 'Ecolab',149        'HLT': 'Hilton Hotels',150        'MAR': 'Marriott',151        'RCL': 'Royal Caribbean',152        'LUV': 'Southwest Airlines',153        'UAL': 'United Airlines',154        'DAL': 'Delta Air Lines',155        'ALK': 'Alaska Air Group',156        'RBRK': 'Rubrik',157        'ASTS': 'AST SpaceMobile',158        'OKLO': 'Oklo Inc.',159        'NNE': 'Nano Nuclear Energy Inc.',160        'SOUN': 'Soundtrack',161        'SOFI': 'SoFi Technologies',162        'UPST': 'Upstart',163        'DKNG': 'DraftKings',164        'PENN': 'Penn Entertainment',165        'MSGS': 'MSG Sports',166        'MSGS': 'MSG Entertainment',167        'NCLH': 'Norwegian Cruise Line',168        'CCL': 'Carnival Corporation',169        'F': 'Ford',170        'GM': 'General Motors',171        'TM': 'Toyota',172        'HMC': 'Honda',173        'VWAGY': 'Volkswagen',174        'GELYY': 'Geely',175        'NIO': 'NIO',176        'LI': 'Li Auto',177        'XPEV': 'XPeng',178            # 'LEOV' removed per user request179        'TSP': 'Tusimple',180        'PRPL': 'Purple Innovation',181        'BBBY': 'Bed Bath Beyond',182        'PTON': 'Peloton',183        'LULU': 'Lululemon',184        'GIII': 'G-III Apparel',185        'AEO': 'American Eagle',186        'GPS': 'Gap Inc',187        'KSS': 'Kohl\'s',188        'M': 'Macy\'s',189            # 'JWN' removed per user request190        'DDS': 'Dillard\'s',191        'DICT': 'Dictate Inc',192        'DECK': 'Deckers Outdoor',193        'SKX': 'Skechers',194        'CROX': 'Crocs',195        'DNOW': 'DistributionNOW',196        'DXCM': 'Dexcom',197        'INMD': 'Inmode',198        'INTU': 'Intuit',199        'QUAD': 'Quadratec',200        'QUBT': 'Quantum Computing',201        'RIOT': 'Riot Blockchain',202        'MARA': 'Marathon Digital',203        'CLSK': 'CleanSpark',204        'HUT': 'Hut 8 Mining',205        'COIN': 'Coinbase',206        'MSTR': 'Microstrategy',207        'SQ': 'Block Inc',208        'AFRM': 'Affirm',209            # 'IPOE' removed per user request210        'OPEN': 'Opendoor',211        'RBLX': 'Roblox',212        'U': 'Unity Software',213        'TTD': 'Trade Desk',214        'CTV': 'Connectwise',215        'ESTC': 'Elastic',216        'DDOG': 'Datadog',217        'OKTA': 'Okta',218        'NET': 'Cloudflare',219        'FIVN': 'Five9',220        'ZM': 'Zoom',221        'CRWD': 'CrowdStrike',222        'CHKP': 'Check Point',223        224        'CVLT': 'Cavalier Therapeutics',225        'VEEV': 'Veeva Systems',226        227        'NOW': 'ServiceNow',228        'ORCL': 'Oracle',229        'SAP': 'SAP SE',230        'ADSK': 'Autodesk',231        # 'ANSS' removed per user request232        'CADM': 'Cadence',233        'SYNA': 'Synaptics',234        'MANH': 'Manhattan Associates',235        'WDAY': 'Workday',236        'SNOW': 'Snowflake',237        'DBX': 'Dropbox',238        'GDDY': 'GoDaddy',239        'MNST': 'Monster Beverage',240        'KO': 'Coca-Cola',241        'PEP': 'PepsiCo',242        'KMB': 'Kimberly-Clark',243        'CL': 'Colgate-Palmolive',244        'PG': 'Procter & Gamble',245        'EL': 'Estée Lauder',246        'UL': 'Unilever',247        'CLX': 'Clorox',248        'HSII': 'Heidrick & Struggles',249        'CONE': 'Cone Midstream Partners',250        'MO': 'Altria',251        'PM': 'Philip Morris',252        'BTI': 'British American Tobacco',253        'SMPL': 'Sample Holdings',254        'SPLK': 'Splunk',255        'SUMO': 'Sumo Logic',256        'CRSR': 'Corsair',257        258        'RIOT': 'Riot Blockchain',259        'TLRY': 'Tilray',260        'SNDL': 'Sundial Growers',261        'ACB': 'Aurora Cannabis',262        'CGC': 'Canopy Growth',263        'CURLF': 'Curaleaf',264        'TRSSF': 'Trulieve',265        'TCNNF': 'Tetra Bio-Pharma',266        'HITI': 'High Tide',267        'SNDL': 'Sundial Growers',268        'VAPER': 'Vapor Hub',269        'VECO': 'Veeco Instruments',270        'WING': 'Wingman Inc',271        'XBIT': 'Xbit Holdings',272        'YEXT': 'Yext',273        'ZASH': 'Zash Global Media',274        'ZETA': 'Zeta Global',275        'ZIGM': 'Zigmark Holdings',276        'ZION': 'Zion Bancorp',277    },278    '🇪🇸 España': {279        'SAN.MC': 'Banco Santander',280        'BBVA.MC': 'BBVA',281        'IBE.MC': 'Iberdrola',282        'ELE.MC': 'Endesa',283        'TEF.MC': 'Telefónica',284        'ITX.MC': 'Inditex',285        'ACS.MC': 'ACS',286        'REP.MC': 'Repsol',287        'AENA.MC': 'AENA',288        'MAP.MC': 'Mapfre',289        # 'ACE.MC' removed per user request290        'AMS.MC': 'Amstrad',291        'ANA.MC': 'Acciona',292        'ANE.MC': 'Acciona Energía',293        # 'ARCE.MC' removed per user request294        'CLNX.MC': 'Cellnex',295        'COL.MC': 'Colombier',296        'CRIS.MC': 'Cristalería Española',297        'DIA.MC': 'Distribuidora Internacional de Alimentación',298        'ENG.MC': 'Enagás',299        'EVE.MC': 'Evelop Airlines',300        'FCC.MC': 'Fomento de Construcciones',301        'FER.MC': 'Ferrovial',302        'GRF.MC': 'Grifols',303        'IAG.MC': 'International Airlines Group',304        'IDR.MC': 'Indra Sistemas',305        # 'INE.MC' removed per user request306        # 'MAS.MC' removed per user request307        'MEL.MC': 'Meliá Hotels',308        'MDF.MC': 'Modefinance',309        310        311        312        313        'PVA.MC': 'Proa Ventures',314        'RTVE.MC': 'RTVE',315        'SLR.MC': 'Solaria',316        'VIS.MC': 'Vistaprint',317        'CABK.MC': 'CaixaBank',318        'SCYR.MC': 'Scyres',319        'TKFT.MC': 'Tekfor',320        'UNI.MC': 'Unipago',321    },322    '🇩🇪 Alemania - DAX': {323        'SAP.DE': 'SAP',324        'SIE.DE': 'Siemens',325        'ALV.DE': 'Allianz',326        'MUV2.DE': 'Munich Re',327        'LIN.DE': 'Linde',328        'IFX.DE': 'Infineon',329        'DB1.DE': 'Deutsche Börse',330        'RWE.DE': 'RWE',331        'VOW3.DE': 'Volkswagen',332        'BMW.DE': 'BMW',333        # 'BAY.DE' removed per user request334        'BAYN.DE': 'Bayer (Non-Voting)',335        'MRK.DE': 'Merck KGaA',336        'HEI.DE': 'Heidelberg Cement',337        'FRE.DE': 'Fresenius',338        'FME.DE': 'Fresenius Medical Care',339        'HNR1.DE': 'Henkel',340        'ENR.DE': 'Eon SE',341        'EXS1.DE': 'Exel Industries',342        'PUM.DE': 'Puma SE',343        'TKA.DE': 'ThyssenKrupp',344        'LHA.DE': 'Deutsche Lufthansa',345        'VNA.DE': 'Vonovia SE',346        'ZAL.DE': 'Zalando SE',347        'EOAN.DE': 'E.ON SE',348        'DAI.DE': 'Daimler',349        'BMW.DE': 'BMW Group',350        'BAS.DE': 'BASF',351        'CBK.DE': 'Commerzbank',352        'DB.DE': 'Deutsche Bank',353        'DTE.DE': 'Deutsche Telekom',354        'DRI.DE': 'Drägerwerk',355        'EVD.DE': 'Evotec SE',356        'FLT.DE': 'Flextronics',357        'GEI.DE': 'Geberit',358        'GFT.DE': 'GFT Technologies',359        'HAM.DE': 'Hamborner',360        'HAS.DE': 'Hauck + Aufhäuser',361        'HDM.DE': 'Heidelberg Printing',362        'HYQ.DE': 'Hyundai Engineering',363        'IXX.DE': 'Ixx',364    },365    '🇩🇪 Alemania - SDAX (Mid-cap, €30M+ mensual)': {366        'NEM.DE': 'Nemetschek SE',367        368        'SRT.DE': 'Sartorius AG',369        'TAL.DE': 'Talanx AG',370        'VAR1.DE': 'Varta AG',371        'OHB.DE': 'OHB SE',372        'SNG.DE': 'Singulus Technologies',373        'AAD.DE': 'Amadeus Fire',374        'STD.DE': 'Stabilus',375        'KSC.DE': 'K+S AG',376        'MVV1.DE': 'MVV Energie',377        'TDG.DE': 'Telefónica Deutschland',378        'ZAL.DE': 'Zalando SE',379        'MUV2.DE': 'Munich Re',380        'HNR.DE': 'Hannover Re',381        'EOAN.DE': 'E.ON SE',382        'ENR.DE': 'Encavis AG',383        'HEI.DE': 'Heidelberg Materials',384        'VNA.DE': 'Vonovia SE',385        'LEG.DE': 'LEG Immobilien SE',386        'PUM.DE': 'Puma SE',387        'QIA.DE': 'Qiagen NV',388        'RIB.DE': 'RIB Software SE',389        'RUS.DE': 'Rheinmetall AG',390        'SDF.DE': 'Südzucker AG',391        'SYMV.DE': 'Symrise AG',392        'DRI.DE': 'Drägerwerk AG',393        'GFT.DE': 'GFT Technologies',394        'HAM.DE': 'Hamborner REIT',395    },396    '🇩🇪 Alemania - TecDAX (Tech €30M+ mensual)': {397        'IFX.DE': 'Infineon Technologies',398        'SYZ.DE': 'Sartorius Stedim Biotech',399        # 'LEO.DE' removed per user request400        'EVD.DE': 'Evotec SE',401        # 'KBC.DE' removed per user request402        # 'KKC.DE' removed per user request403        'HEX.DE': 'Hecla Mining',404        'VBK.DE': 'Villeroy & Boch',405    },406    '🇩🇪 Alemania - SETRA (Small-cap €30M+ mensual)': {407        'ADS.DE': 'Adidas AG',408        'BAS.DE': 'BASF SE',409        'BMW.DE': 'BMW AG',410        'DBK.DE': 'Deutsche Bank AG',411        'DPW.DE': 'Deutsche Post AG',412        'DTE.DE': 'Deutsche Telekom AG',413        'FRE.DE': 'Fresenius SE & Co. KGaA',414        'FME.DE': 'Fresenius Medical Care AG & Co. KGaA',415        'IFX.DE': 'Infineon Technologies AG',416        'LIN.DE': 'Linde plc',417        'RWE.DE': 'RWE AG',418        'SIE.DE': 'Siemens AG',419        'VOW3.DE': 'Volkswagen AG',420        'VNA.DE': 'Vonovia SE',421        'ZAL.DE': 'Zalando SE',422        'PUM.DE': 'Puma SE',423        'HEI.DE': 'Heidelberg Materials',424        425        'NEM.DE': 'Nemetschek SE',426        'RCG.DE': 'Rational AG',427        'SRT.DE': 'Sartorius AG',428        'TAL.DE': 'Talanx AG',429        'VAR1.DE': 'Varta AG',430        'OHB.DE': 'OHB SE',431        'RIB.DE': 'RIB Software SE',432        'RUS.DE': 'Rheinmetall AG',433        'SDF.DE': 'Südzucker AG',434        'SYMV.DE': 'Symrise AG',435        'NXT.DE': 'Nemetschek (alternate)',436        'CON.DE': 'Continental AG',437        'OTH.DE': 'Other German Smallcap',438    },439    '🇫🇷 Francia - CAC 40': {440        'MC.PA': 'LVMH Moët Hennessy',441        'OR.PA': 'L\'Oréal',442        'TTE.PA': 'TotalEnergies',443        'SAN.PA': 'Sanofi',444        'BNP.PA': 'BNP Paribas',445        'RMS.PA': 'Hermès International',446        'DSY.PA': 'Dassault Systèmes',447        'AIR.PA': 'Airbus',448        'SAF.PA': 'Safran',449        'KER.PA': 'Kering',450        'LR.PA': 'Legrand',451        'SU.PA': 'Schneider Electric',452        'ENGI.PA': 'ENGIE',453        'EDF.PA': 'EDF',454        'CA.PA': 'Carrefour',455        'CAP.PA': 'Capgemini',456        'MT.PA': 'ArcelorMittal',457        'VIE.PA': 'Veolia',458        'FP.PA': 'Accor',459        'VIV.PA': 'Vivendi',460        'SGO.PA': 'Saint-Gobain',461        'RI.PA': 'Pernod Ricard',462        'HO.PA': 'Thales',463        'PUB.PA': 'Publicis Groupe',464        'NOKIA.PA': 'Nokia',465        'ML.PA': 'Michelin',466        'NOKIA.PA': 'Nokia OYJ',467        # 'ASML.PA' removed per user request468        'FR.PA': 'Accor',469        'GLE.PA': 'Société Générale',470        'ACA.PA': 'Crédit Agricole',471        'ORANGE.PA': 'Orange',472        'NOKIA.PA': 'Nokia',473        'UNIBAIL.PA': 'Unibail-Rodamco-Westfield',474        'BOUYGUES.PA': 'Bouygues',475        'NESTLE.PA': 'Nestlé',476        'ROCHE.PA': 'Roche Holding',477        'VALEO.PA': 'Valeo',478        'GDF.PA': 'GDF Suez',479        'RENAULT.PA': 'Renault',480    },481    '🇬🇧 UK': {482        'SHEL.L': 'Shell',483        'BP.L': 'BP',484        'HSBA.L': 'HSBC',485        'GSK.L': 'GSK',486        'AZN.L': 'AstraZeneca',487        'RIO.L': 'Rio Tinto',488        'ULVR.L': 'Unilever',489        'LLOY.L': 'Lloyds Banking Group',490        'VOD.L': 'Vodafone',491        'TSCO.L': 'Tesco',492        'BARC.L': 'Barclays',493        'PRU.L': 'Prudential',494    },495    '🇨🇦 Canadá': {496        'TD.TO': 'Toronto-Dominion Bank',497        'RY.TO': 'Royal Bank of Canada',498        'BNS.TO': 'Bank of Nova Scotia',499        'BCE.TO': 'BCE Inc',500        'ENB.TO': 'Enbridge',501        'SU.TO': 'Suncor Energy',502        'CNQ.TO': 'Canadian Natural Resources',503        'SHOP.TO': 'Shopify',504        'BMO.TO': 'Bank of Montreal',505    },506    '🇦🇺 Australia': {507        'CBA.AX': 'Commonwealth Bank',508        'ANZ.AX': 'ANZ Group',509        'NAB.AX': 'National Australia Bank',510        'WBC.AX': 'Westpac Banking',511        'WES.AX': 'Wesfarmers',512        'CSL.AX': 'CSL Ltd',513        'BHP.AX': 'BHP Group',514        'TLS.AX': 'Telstra',515        'WOW.AX': 'Woolworths Group',516    },517    '🇨🇳 China - ADR': {518        'BABA': 'Alibaba',519        'TCEHY': 'Tencent',520        'BIDU': 'Baidu',521        'JD': 'JD.com',522        'NTES': 'NetEase',523        'XPEV': 'XPeng',524        'NIO': 'NIO',525        'LI': 'Li Auto',526        'PDD': 'Pinduoduo',527        'BILI': 'Bilibili',528        'IQ': 'iQIYI',529    },530}531 532 533def get_all_symbols() -> List[str]:534    """Retorna lista plana de todos los símbolos disponibles"""535    all_symbols = []536    for category_symbols in AVAILABLE_SYMBOLS.values():537        all_symbols.extend(category_symbols.keys())538    return all_symbols539 540 541def get_available_sources_for_symbol(symbol: str) -> List[str]:542    """Retorna las fuentes disponibles para un símbolo específico"""543    return ['yahoo']  # Solo Yahoo Finance disponible544 545 546# Timeframes disponibles547AVAILABLE_TIMEFRAMES = {548    '1m': '1 Minuto',549    '2m': '2 Minutos',550    '5m': '5 Minutos',551    '15m': '15 Minutos',552    '30m': '30 Minutos',553    '1h': '1 Hora',554    '4h': '4 Horas',555    '1d': 'Diario',556    '1wk': 'Semanal',557    '1mo': 'Mensual',558}559 560 561class DataLoader:562    """563    Cargador de datos históricos564    Soporta: Yahoo Finance565    """566    567    def __init__(self):568        self._cache = {}569    570    @staticmethod571    def download(572        symbol: str,573        interval: str = '1d',574        start_date: str = None,575        end_date: str = None,576        source: str = 'yahoo'577    ) -> pd.DataFrame:578        """579        Descarga datos históricos de la fuente especificada580        581        Args:582            symbol: Símbolo del activo (ej: 'BTC-USD', 'AAPL')583            interval: Intervalo de las velas584            start_date: Fecha de inicio (YYYY-MM-DD)585            end_date: Fecha de fin (YYYY-MM-DD)586            source: Fuente de datos ('yahoo', 'binance', 'coinbase', etc.)587            588        Returns:589            DataFrame con columnas OHLCV590        """591        if source == 'yahoo':592            return DataLoader._download_yahoo(symbol, interval, start_date, end_date)593        else:594            st.error(f"Fuente no soportada: {source}")595            return pd.DataFrame()596    597    @staticmethod598    @st.cache_data(ttl=3600)599    def _download_yahoo(600        symbol: str,601        interval: str,602        start_date: str,603        end_date: str604    ) -> pd.DataFrame:605        """Descarga datos de Yahoo Finance"""606        try:607            if interval == '4h':608                df = DataLoader._download_and_resample_4h_yahoo(symbol, start_date, end_date)609            elif interval == '1mo':610                df = DataLoader._download_and_resample_1mo_yahoo(symbol, start_date, end_date)611            else:612                ticker = yf.Ticker(symbol)613                df = ticker.history(start=start_date, end=end_date, interval=interval)614            615            if df.empty:616                return pd.DataFrame()617            618            # Renombrar columnas619            df = df.rename(columns={620                'Open': 'open', 'High': 'high', 'Low': 'low',621                'Close': 'close', 'Volume': 'volume'622            })623            624            cols_to_keep = ['open', 'high', 'low', 'close', 'volume']625            df = df[[col for col in cols_to_keep if col in df.columns]]626            627            # Remover timezone info para evitar problemas en Plotly628            if hasattr(df.index, 'tz') and df.index.tz is not None:629                df.index = df.index.tz_localize(None)630            631            return df632            633        except Exception as e:634            st.error(f"Error Yahoo Finance ({symbol}): {e}")635            return pd.DataFrame()636    637    @staticmethod638    def _download_and_resample_4h_yahoo(symbol: str, start_date: str, end_date: str) -> pd.DataFrame:639        """Descarga datos de 1h y los convierte a 4h"""640        try:641            ticker = yf.Ticker(symbol)642            df = ticker.history(start=start_date, end=end_date, interval='1h')643            644            if df.empty:645                return pd.DataFrame()646            647            df = df.rename(columns={648                'Open': 'open', 'High': 'high', 'Low': 'low',649                'Close': 'close', 'Volume': 'volume'650            })651            652            cols_to_keep = ['open', 'high', 'low', 'close', 'volume']653            df = df[[col for col in cols_to_keep if col in df.columns]]654            655            # Resamplear a 4h656            df_4h = df.resample('4h').agg({657                'open': 'first',658                'high': 'max',659                'low': 'min',660                'close': 'last',661                'volume': 'sum'662            }).dropna()663            664            # Remover timezone info para evitar problemas en Plotly665            if df_4h.index.tz is not None:666                df_4h.index = df_4h.index.tz_localize(None)667            668            return df_4h669        670        except Exception as e:671            st.error(f"Error resampleando 4h: {e}")672            return pd.DataFrame()673    674    @staticmethod675    def _download_and_resample_1mo_yahoo(symbol: str, start_date: str, end_date: str) -> pd.DataFrame:676        """Descarga datos de 1d y los convierte a 1mo (Month End)"""677        try:678            ticker = yf.Ticker(symbol)679            df = ticker.history(start=start_date, end=end_date, interval='1d')680            681            if df.empty:682                return pd.DataFrame()683            684            df = df.rename(columns={685                'Open': 'open', 'High': 'high', 'Low': 'low',686                'Close': 'close', 'Volume': 'volume'687            })688            689            cols_to_keep = ['open', 'high', 'low', 'close', 'volume']690            df = df[[col for col in cols_to_keep if col in df.columns]]691            692            # Resamplear a Month End (ME)693            df_monthly = df.resample('ME').agg({694                'open': 'first',695                'high': 'max',696                'low': 'min',697                'close': 'last',698                'volume': 'sum'699            }).dropna()700            701            # Remover timezone info para evitar problemas en Plotly702            if hasattr(df_monthly.index, 'tz') and df_monthly.index.tz is not None:703                df_monthly.index = df_monthly.index.tz_localize(None)704            705            return df_monthly706        707        except Exception as e:708            st.error(f"Error resampleando 1mo: {e}")709            return pd.DataFrame()710    711    @staticmethod712    @st.cache_data(ttl=3600)713    714    @staticmethod715    def get_available_sources() -> Dict[str, str]:716        """Retorna fuentes disponibles"""717        return {'yahoo': 'Yahoo Finance'}718    719    @staticmethod720    def is_crypto(symbol: str) -> bool:721        """Verifica si el símbolo es criptomoneda"""722        return False  # Ya no tenemos criptomonedas en AVAILABLE_SYMBOLS723