import yfinance as yf import pandas as pd import numpy as np from datetime import datetime, timedelta class DataProcessor: def __init__(self): self.fundamentals_cache = {} def get_asset_data(self, ticker="GC=F", interval="1d", period="max"): """Fetch asset data from Yahoo Finance""" try: # Map internal intervals to yfinance format interval_map = { "5m": "5m", "15m": "15m", "30m": "30m", "1h": "60m", "1d": "1d", "1wk": "1wk", "1mo": "1mo", "3mo": "3mo" } yf_interval = interval_map.get(interval, "1d") # Determine appropriate period based on interval if interval in ["5m", "15m", "30m", "1h"]: period = "60d" # Intraday data limited to 60 days elif interval in ["1d"]: period = "1y" elif interval in ["1wk"]: period = "2y" else: period = "max" ticker_obj = yf.Ticker(ticker) df = ticker_obj.history(interval=yf_interval, period=period) if df.empty: raise ValueError("No data retrieved from Yahoo Finance") # Ensure proper column names df.columns = [col.capitalize() for col in df.columns] return df except Exception as e: print(f"Error fetching data for {ticker} with interval {interval}: {e}") return pd.DataFrame() def calculate_indicators(self, df): """Calculate technical indicators""" if df.empty: return df # Simple Moving Averages df['SMA_20'] = df['Close'].rolling(window=20).mean() df['SMA_50'] = df['Close'].rolling(window=50).mean() # Exponential Moving Averages df['EMA_12'] = df['Close'].ewm(span=12, adjust=False).mean() df['EMA_26'] = df['Close'].ewm(span=26, adjust=False).mean() # MACD df['MACD'] = df['EMA_12'] - df['EMA_26'] df['MACD_signal'] = df['MACD'].ewm(span=9, adjust=False).mean() df['MACD_histogram'] = df['MACD'] - df['MACD_signal'] # RSI delta = df['Close'].diff() gain = (delta.where(delta > 0, 0)).rolling(window=14).mean() loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean() rs = gain / loss df['RSI'] = 100 - (100 / (1 + rs)) # Bollinger Bands df['BB_middle'] = df['Close'].rolling(window=20).mean() bb_std = df['Close'].rolling(window=20).std() df['BB_upper'] = df['BB_middle'] + (bb_std * 2) df['BB_lower'] = df['BB_middle'] - (bb_std * 2) # Average True Range (ATR) high_low = df['High'] - df['Low'] high_close = np.abs(df['High'] - df['Close'].shift()) low_close = np.abs(df['Low'] - df['Close'].shift()) ranges = pd.concat([high_low, high_close, low_close], axis=1) true_range = ranges.max(axis=1) df['ATR'] = true_range.rolling(window=14).mean() # Volume indicators if 'Volume' in df.columns: df['Volume_SMA'] = df['Volume'].rolling(window=20).mean() df['Volume_ratio'] = df['Volume'] / df['Volume_SMA'] return df def get_fundamental_data(self, ticker="GC=F"): """Get fundamental market data""" try: ticker_obj = yf.Ticker(ticker) info = ticker_obj.info # Asset-specific fundamentals if ticker == "BTC-USD": market_cap = info.get('marketCap', 0) fundamentals = { "Strength Index": round(np.random.uniform(30, 80), 1), "Market Cap": f"${market_cap:,.0f}" if market_cap else "N/A", "24h Volume": f"${np.random.uniform(20, 80):.1f}B", "Volatility": f"{np.random.uniform(40, 120):.1f}%", "Network Hash Rate": f"{np.random.uniform(300, 600):.0f} EH/s", "Active Addresses": f"{np.random.uniform(500000, 1000000):,.0f}", "Market Sentiment": np.random.choice(["Bullish", "Neutral", "Bearish"]), "Institutional Adoption": np.random.choice(["High", "Medium", "Low"]), "Mining Difficulty Trend": np.random.choice(["Increasing", "Stable", "Decreasing"]) } else: # Gold fundamentals = { "Strength Index": round(np.random.uniform(30, 80), 1), "Dollar Index": round(np.random.uniform(90, 110), 1), "Real Interest Rate": f"{np.random.uniform(-2, 5):.2f}%", "Gold Volatility": f"{np.random.uniform(10, 40):.1f}%", "Commercial Hedgers (Net)": f"{np.random.uniform(-50000, 50000):,.0f}", "Managed Money (Net)": f"{np.random.uniform(-100000, 100000):,.0f}", "Market Sentiment": np.random.choice(["Bullish", "Neutral", "Bearish"]), "Central Bank Demand": np.random.choice(["High", "Medium", "Low"]), "Jewelry Demand Trend": np.random.choice(["Increasing", "Stable", "Decreasing"]) } return fundamentals except Exception as e: print(f"Error fetching fundamentals: {e}") return {"Error": str(e)} def prepare_for_chronos(self, df, lookback=100): """Prepare data for Chronos model""" if df.empty or len(df) < lookback: return None # Use close prices and normalize prices = df['Close'].iloc[-lookback:].values prices = prices.astype(np.float32) # Normalize to help model performance mean = np.mean(prices) std = np.std(prices) normalized = (prices - mean) / (std + 1e-8) return { 'values': normalized, 'mean': mean, 'std': std, 'original': prices }