# AI Cash Evolution - ML Trading Service for Hugging Face Spaces # Version 3.2 - Google TimesFM Integration import gradio as gr import pandas as pd import numpy as np import yfinance as yf import json from datetime import datetime, timedelta import plotly.graph_objs as go from typing import Dict, List, Tuple, Optional import functools import requests import os import time import warnings warnings.filterwarnings('ignore') # OANDA API configuration - Based on official documentation OANDA_API_KEY = "5c973356b8d3d02c28162fd3f3afe8aa-1416e27af60c960fedb8d61c98e917b5" OANDA_ACCOUNT_ID = "101-004-37254450-002" OANDA_BASE_URL = "https://api-fxpractice.oanda.com" # OANDA REST API - Direct HTTP requests (more reliable) OANDA_AVAILABLE = True # Always available with requests print("✅ OANDA REST API available (direct HTTP) - v3.3 Fixed") # Try to import TimesFM try: from huggingface_hub import hf_hub_download import torch import safetensors.torch as safetensors import einops TIMESFM_AVAILABLE = True print("✅ TimesFM dependencies available") except ImportError as e: TIMESFM_AVAILABLE = False print(f"❌ TimesFM not available: {e}") # Independent OANDA Integration - No external dependencies class TimesFMPredictor: """Google TimesFM predictor for financial time series""" def __init__(self): self.model = None self.model_loaded = False self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') def load_model(self): """Load TimesFM model from Hugging Face""" if self.model_loaded or not TIMESFM_AVAILABLE: return self.model_loaded try: print("🔄 Loading TimesFM model...") # Download model from Hugging Face model_path = hf_hub_download( repo_id="google/timesfm-2.5-200m-pytorch", filename="model.safetensors" ) # Load the model self.model = torch.load(model_path, map_location=self.device) self.model.eval() self.model_loaded = True print(f"✅ TimesFM model loaded on {self.device}") return True except Exception as e: print(f"❌ Failed to load TimesFM model: {e}") self.model_loaded = False return False def predict(self, data: np.ndarray, horizon: int = 5) -> Tuple[Optional[np.ndarray], Optional[np.ndarray]]: """Make predictions using statistical analysis (fallback when TimesFM unavailable)""" try: if len(data) < 10: print(f"❌ Insufficient data for prediction: {len(data)} points") return None, None # Calculate trend based on recent price action recent_prices = data[-min(20, len(data)):] price_changes = np.diff(recent_prices) # Simple linear extrapolation with momentum avg_change = np.mean(price_changes[-5:]) if len(price_changes) >= 5 else 0 volatility = np.std(price_changes) if len(price_changes) > 1 else data[-1] * 0.01 # Generate predictions based on trend last_price = data[-1] predictions = [] for i in range(horizon): # Each step includes trend and some volatility trend_component = avg_change * (1 + i * 0.1) # Slight trend acceleration volatility_component = np.random.normal(0, volatility * 0.5) prediction = last_price * (1 + trend_component + volatility_component) predictions.append(prediction) point_forecast = np.array(predictions) # Create quantile forecasts based on volatility quantiles = [0.1, 0.25, 0.5, 0.75, 0.9] quantile_forecasts = [] for q in quantiles: # Calculate quantile adjustment based on historical volatility quantile_multiplier = 1 + (q - 0.5) * volatility * 2 quantile_prediction = point_forecast * quantile_multiplier quantile_forecasts.append(quantile_prediction) quantile_forecast = np.array(quantile_forecasts) return point_forecast, quantile_forecast except Exception as e: print(f"❌ Prediction failed: {e}") return None, None # Initialize TimesFM predictor timesfm_predictor = TimesFMPredictor() def get_market_data_with_retry(symbol: str, max_retries: int = 3) -> pd.DataFrame: """Get market data with independent OANDA direct integration - No external dependencies""" print(f"🚀 get_market_data_with_retry called with symbol: '{symbol}'") # Major forex pairs with high liquidity - OANDA format (for dropdown) major_forex_oanda = { 'EURUSD', 'GBPUSD', 'USDJPY', 'USDCHF', 'AUDUSD', 'USDCAD', 'NZDUSD', 'EURGBP', 'EURJPY', 'GBPJPY', 'EURCHF' } # Minor forex pairs minor_forex_oanda = { 'EURAUD', 'EURNZD', 'EURCAD', 'GBPCHF', 'GBPAUD', 'GBPCAD', 'CHFJPY', 'AUDJPY', 'CADJPY', 'NZDJPY', 'AUDCHF', 'CADCHF' } # Forex metals - OANDA format forex_metals_oanda = {'XAUUSD', 'XAGUSD'} # Legacy Yahoo Finance format (backwards compatibility) major_forex_yahoo = { 'EURUSD=X', 'GBPUSD=X', 'USDJPY=X', 'USDCHF=X', 'AUDUSD=X', 'USDCAD=X', 'NZDUSD=X', 'EURGBP=X', 'EURJPY=X', 'GBPJPY=X', 'EURCHF=X' } forex_metals_yahoo = {'XAUUSD=X', 'XAGUSD=X'} symbol_lower = symbol.lower() if symbol in major_forex_oanda or symbol in minor_forex_oanda or symbol in forex_metals_oanda: print(f"✅ Symbol '{symbol}' matches OANDA format sets") # Major forex/metals - OANDA direct API (independent from Supabase) approaches = [ ("OANDA Direct API", lambda s: get_oanda_data(s)), ("Yahoo Finance Enhanced", lambda s: get_yahoo_data(s)), ("Yahoo Finance Extended", lambda s: get_yahoo_data_extended(s)) ] elif symbol in major_forex_yahoo or symbol in forex_metals_yahoo: # Legacy Yahoo Finance format - backwards compatibility approaches = [ ("Yahoo Finance Enhanced", lambda s: get_yahoo_data(s)), ("Yahoo Finance Extended", lambda s: get_yahoo_data_extended(s)), ("OANDA Direct API", lambda s: get_oanda_data(s)) # Try OANDA as fallback ] else: # Other instruments - Yahoo Finance only approaches = [ ("Yahoo Finance Enhanced", lambda s: get_yahoo_data(s)), ("Yahoo Finance Extended", lambda s: get_yahoo_data_extended(s)) ] for approach_name, approach_func in approaches: for attempt in range(max_retries): try: print(f"🔄 Attempt {attempt + 1}/{max_retries}: {approach_name} for {symbol}") data = approach_func(symbol) if data is not None and len(data) >= 50: print(f"✅ Success with {approach_name}: {len(data)} data points") return data else: print(f"❌ Insufficient data from {approach_name}: {len(data) if data else 0} points") except Exception as e: print(f"❌ Error with {approach_name} attempt {attempt + 1}: {str(e)}") time.sleep(1) print(f"❌ All approaches failed for {symbol}") return None def get_yahoo_data(symbol: str) -> pd.DataFrame: """Get data from Yahoo Finance with enhanced error handling and symbol mapping""" try: # Enhanced symbol mapping for forex pairs symbol_mapping = { # Forex pairs with proper Yahoo Finance formats 'EURUSD=X': 'EURUSD=X', 'EURUSD': 'EURUSD=X', 'GBPUSD=X': 'GBPUSD=X', 'GBPUSD': 'GBPUSD=X', 'USDJPY=X': 'USDJPY=X', 'USDJPY': 'USDJPY=X', 'USDCHF=X': 'USDCHF=X', 'USDCHF': 'USDCHF=X', 'USDCAD=X': 'USDCAD=X', 'USDCAD': 'USDCAD=X', 'AUDUSD=X': 'AUDUSD=X', 'AUDUSD': 'AUDUSD=X', 'NZDUSD=X': 'NZDUSD=X', 'NZDUSD': 'NZDUSD=X', 'EURGBP=X': 'EURGBP=X', 'EURGBP': 'EURGBP=X', 'EURJPY=X': 'EURJPY=X', 'EURJPY': 'EURJPY=X', 'GBPJPY=X': 'GBPJPY=X', 'GBPJPY': 'GBPJPY=X', # Metals 'XAUUSD=X': 'GC=F', # Gold futures 'XAUUSD': 'GC=F', 'XAGUSD=X': 'SI=F', # Silver futures 'XAGUSD': 'SI=F', # Crypto 'BTC-USD': 'BTC-USD', 'ETH-USD': 'ETH-USD', # Stocks (keep as is) } # Try mapped symbol first mapped_symbol = symbol_mapping.get(symbol, symbol) print(f"🔄 Trying Yahoo Finance with symbol: {symbol} -> {mapped_symbol}") # Try different periods for more data periods = ["1y", "2y", "3y"] for period in periods: try: ticker = yf.Ticker(mapped_symbol) data = ticker.history(period=period) if data is not None and len(data) >= 50: print(f"✅ Yahoo Finance success with {mapped_symbol} ({period}): {len(data)} data points") return data else: print(f"❌ Insufficient data with {mapped_symbol} ({period}): {len(data) if data is not None else 0} points") except Exception as e: print(f"❌ Yahoo Finance {mapped_symbol} ({period}) failed: {e}") continue # Try original symbol as fallback if mapped_symbol != symbol: print(f"🔄 Trying original symbol: {symbol}") try: ticker = yf.Ticker(symbol) data = ticker.history(period="1y") if data is not None and len(data) >= 50: print(f"✅ Yahoo Finance success with original symbol {symbol}: {len(data)} data points") return data except Exception as e: print(f"❌ Original symbol {symbol} failed: {e}") return None except Exception as e: print(f"Yahoo Finance error: {e}") return None def get_yahoo_data_extended(symbol: str) -> pd.DataFrame: """Extended Yahoo Finance data fetch""" try: periods = ["1y", "2y"] for period in periods: try: ticker = yf.Ticker(symbol) data = ticker.history(period=period) if data is not None and len(data) >= 50: print(f"✅ Yahoo Finance extended success with period {period}") return data except Exception as e: print(f"Yahoo Finance extended with {period} failed: {e}") continue return None except Exception as e: print(f"Yahoo Finance extended error: {e}") return None def get_oanda_data(symbol: str) -> pd.DataFrame: """Get OANDA historical data via REST API - Following official OANDA v3 documentation""" if not OANDA_AVAILABLE: print("❌ OANDA API not available") return None try: # Convert symbol format: EURUSD=X -> EUR_USD, XAUUSD=X -> XAU_USD oanda_symbol = symbol.replace('=X', '').replace('-', '') # Enhanced symbol mapping for OANDA - complete forex pairs symbol_mapping = { # Major Forex Pairs 'EURUSD': 'EUR_USD', 'GBPUSD': 'GBP_USD', 'USDJPY': 'USD_JPY', 'USDCHF': 'USD_CHF', 'AUDUSD': 'AUD_USD', 'USDCAD': 'USD_CAD', 'NZDUSD': 'NZD_USD', # Minor Forex Pairs 'EURGBP': 'EUR_GBP', 'EURJPY': 'EUR_JPY', 'EURCHF': 'EUR_CHF', 'EURAUD': 'EUR_AUD', 'EURNZD': 'EUR_NZD', 'EURCAD': 'EUR_CAD', 'GBPJPY': 'GBP_JPY', 'GBPCHF': 'GBP_CHF', 'GBPAUD': 'GBP_AUD', 'GBPCAD': 'GBP_CAD', 'CHFJPY': 'CHF_JPY', 'AUDJPY': 'AUD_JPY', 'CADJPY': 'CAD_JPY', 'NZDJPY': 'NZD_JPY', 'AUDCHF': 'AUD_CHF', 'CADCHF': 'CAD_CHF', # Metals 'XAUUSD': 'XAU_USD', 'XAGUSD': 'XAG_USD' } instrument = symbol_mapping.get(oanda_symbol, oanda_symbol) # Auto-convert if not in mapping (handle EURUSD -> EUR_USD format) if instrument == oanda_symbol and '_' not in oanda_symbol: if len(oanda_symbol) == 6: # Standard forex pair EURUSD instrument = oanda_symbol[:3] + '_' + oanda_symbol[3:] print(f"🔄 Auto-conversion: {oanda_symbol} -> {instrument}") elif oanda_symbol.startswith(('XAU', 'XAG')) and len(oanda_symbol) == 7: # Metals XAUUSD instrument = oanda_symbol[:3] + '_' + oanda_symbol[3:] print(f"🔄 Auto-conversion: {oanda_symbol} -> {instrument}") else: print(f"âš ī¸ Cannot auto-convert symbol: {oanda_symbol}") print(f"đŸĻ OANDA API v3 - Original: {symbol} -> Clean: {oanda_symbol} -> Instrument: {instrument}") # Validate final instrument format if '_' not in instrument: print(f"❌ ERROR: Final instrument missing underscore: {instrument}") return None # Set up headers for OANDA API - following documentation headers = { 'Authorization': f'Bearer {OANDA_API_KEY}', 'Content-Type': 'application/json', 'Accept-Datetime-Format': 'RFC3339' } print(f"📡 Using OANDA endpoint with instrument: {instrument}") # Granularities prioritized for TimesFM (need historical context) granularities = [ "D", # Daily - best for TimesFM long context "H4", # 4-hour - good balance "H1", # 1-hour - decent coverage "M30", # 30-minute - fallback "M15", # 15-minute - limited "M5" # 5-minute - recent only ] for granularity in granularities: try: print(f"🔄 Trying OANDA {granularity} for {instrument}") # Correct OANDA v3 endpoint following documentation # URL pattern: /v3/accounts/{accountID}/instruments/{instrument}/candles url = f"{OANDA_BASE_URL}/v3/accounts/{OANDA_ACCOUNT_ID}/instruments/{instrument}/candles" # Parameters following OANDA documentation params = { "granularity": granularity, "count": "500", # Maximum allowed per documentation "price": "M" # Mid prices only # Note: includeFirst removed as it requires 'from' parameter } print(f"📡 Requesting: {url}") print(f"📋 Params: {params}") response = requests.get(url, headers=headers, params=params, timeout=30) print(f"📊 Response status: {response.status_code}") if response.status_code == 200: data = response.json() print(f"✅ OANDA response received for {instrument}") if 'candles' in data and data['candles']: candles = data['candles'] print(f"📈 Total candles received: {len(candles)}") # Filter only complete candles (critical for TimesFM) complete_candles = [c for c in candles if c.get('complete', False)] print(f"✅ Complete candles: {len(complete_candles)}") if len(complete_candles) >= 50: print(f"đŸŽ¯ SUCCESS: {len(complete_candles)} candles - sufficient for TimesFM") # Convert candles to DataFrame with proper column names df_data = [] for candle in complete_candles: try: # Parse ISO 8601 timestamp from OANDA candle_time = datetime.fromisoformat(candle['time'].replace('Z', '+00:00')) # Extract mid prices from OANDA response format mid_prices = candle.get('mid', {}) df_data.append({ "Date": candle_time, "Open": float(mid_prices.get('o', 0)), "High": float(mid_prices.get('h', 0)), "Low": float(mid_prices.get('l', 0)), "Close": float(mid_prices.get('c', 0)), "Volume": int(candle.get('volume', 0)) }) except Exception as candle_error: print(f"âš ī¸ Error parsing candle: {candle_error}") continue if df_data: df = pd.DataFrame(df_data) df.set_index("Date", inplace=True) # Sort by date (oldest first - required for time series) df.sort_index(inplace=True) print(f"✅ OANDA DataFrame created: {len(df)} data points from {granularity}") print(f"📅 Date range: {df.index.min()} to {df.index.max()}") return df else: print(f"âš ī¸ Insufficient complete candles: {len(complete_candles)} < 50 required") else: print(f"❌ No candles field in OANDA response") print(f"🔍 Response keys: {list(data.keys()) if isinstance(data, dict) else 'Not a dict'}") else: print(f"❌ OANDA API HTTP {response.status_code}") # Detailed error handling based on documentation if response.status_code == 401: print("❌ 401 Unauthorized - Check OANDA_API_KEY") elif response.status_code == 403: print("❌ 403 Forbidden - Check account permissions or OANDA_ACCOUNT_ID") elif response.status_code == 404: print(f"❌ 404 Not Found - Instrument '{instrument}' may not exist") elif response.status_code == 405: print("❌ 405 Method Not Allowed - Check HTTP method") elif response.status_code == 429: print("❌ 429 Too Many Requests - Rate limit exceeded") # Print response for debugging try: error_data = response.json() print(f"🚨 OANDA Error Response: {error_data}") except: print(f"🚨 Raw Response: {response.text[:500]}...") except Exception as e: print(f"❌ OANDA request error with {granularity}: {e}") continue print(f"❌ All granularities failed for {instrument}") return None except Exception as e: print(f"❌ Critical OANDA connection error: {e}") import traceback print(f"🐛 Traceback: {traceback.format_exc()}") return None # Cache for performance @functools.lru_cache(maxsize=300) def get_cached_data(symbol: str, period: str = "6mo"): """Cache market data to reduce API calls""" try: data = get_market_data_with_retry(symbol) return data except Exception as e: print(f"Cache error for {symbol}: {e}") return None # Technical Indicators def calculate_rsi(data: pd.DataFrame, period: int = 14) -> float: """Calculate RSI indicator""" if len(data) < period: return 50.0 delta = data['Close'].diff() gain = (delta.where(delta > 0, 0)).rolling(window=period).mean() loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean() rs = gain / loss rsi = 100 - (100 / (1 + rs)) return rsi.iloc[-1] if not rsi.empty else 50.0 def calculate_macd(data: pd.DataFrame) -> Dict: """Calculate MACD indicator""" if len(data) < 26: return {"macd": 0, "signal": 0, "histogram": 0} exp1 = data['Close'].ewm(span=12).mean() exp2 = data['Close'].ewm(span=26).mean() macd = exp1 - exp2 signal = macd.ewm(span=9).mean() histogram = macd - signal return { "macd": float(macd.iloc[-1]) if not macd.empty else 0, "signal": float(signal.iloc[-1]) if not signal.empty else 0, "histogram": float(histogram.iloc[-1]) if not histogram.empty else 0 } def calculate_bollinger_bands(data: pd.DataFrame, period: int = 20, std_dev: int = 2) -> Dict: """Calculate Bollinger Bands""" if len(data) < period: return {"upper": 0, "middle": 0, "lower": 0} sma = data['Close'].rolling(window=period).mean() std = data['Close'].rolling(window=period).std() upper_band = sma + (std * std_dev) lower_band = sma - (std * std_dev) return { "upper": float(upper_band.iloc[-1]) if not upper_band.empty else 0, "middle": float(sma.iloc[-1]) if not sma.empty else 0, "lower": float(lower_band.iloc[-1]) if not lower_band.empty else 0 } def calculate_atr(data: pd.DataFrame, period: int = 14) -> float: """Calculate Average True Range""" if len(data) < period: return 0.001 high_low = data['High'] - data['Low'] high_close = np.abs(data['High'] - data['Close'].shift()) low_close = np.abs(data['Low'] - data['Close'].shift()) true_range = np.maximum(high_low, np.maximum(high_close, low_close)) atr = true_range.rolling(window=period).mean() return float(atr.iloc[-1]) if not atr.empty else 0.001 def generate_timesfm_signal(symbol: str, data: pd.DataFrame) -> Dict: """Generate trading signal using TimesFM predictions""" try: # Get close prices for TimesFM close_prices = data['Close'].values # Make TimesFM predictions point_forecast, quantile_forecast = timesfm_predictor.predict(close_prices, horizon=5) if point_forecast is None: # Fallback to technical analysis return generate_technical_signal(symbol, data, "timesfm_fallback") current_price = float(data['Close'].iloc[-1]) predicted_price = float(point_forecast[0]) if len(point_forecast) > 0 else current_price # Calculate prediction confidence price_change_pct = ((predicted_price - current_price) / current_price) * 100 # Get quantile forecasts for uncertainty estimation if quantile_forecast is not None and len(quantile_forecast) > 2: lower_q = float(quantile_forecast[1][0]) # 25th percentile upper_q = float(quantile_forecast[3][0]) # 75th percentile uncertainty = (upper_q - lower_q) / current_price else: uncertainty = 0.02 # Default uncertainty # Generate signal based on prediction if price_change_pct > 0.5: # 0.5% threshold signal = "BUY" confidence = min(abs(price_change_pct) / 2.0, 0.9) elif price_change_pct < -0.5: signal = "SELL" confidence = min(abs(price_change_pct) / 2.0, 0.9) else: signal = "HOLD" confidence = 0.5 # Adjust confidence based on uncertainty confidence = confidence * (1 - uncertainty) confidence = max(0.3, min(confidence, 0.9)) # Calculate risk management atr = calculate_atr(data) stop_loss = atr * 1.5 take_profit = abs(predicted_price - current_price) * 1.5 return { "symbol": symbol, "signal": signal, "confidence": round(confidence, 3), "current_price": current_price, "predicted_price": predicted_price, "price_change_pct": round(price_change_pct, 2), "stop_loss": round(current_price - stop_loss if signal == "BUY" else current_price + stop_loss, 5), "take_profit": round(current_price + take_profit if signal == "BUY" else current_price - take_profit, 5), "uncertainty": round(uncertainty, 3), "model_used": "timesfm" if timesfm_predictor.model_loaded else "timesfm_fallback", "data_points": len(data), "timestamp": datetime.utcnow().isoformat(), "analysis_reasons": [f"TimesFM predicts {price_change_pct:.2f}% change", f"Uncertainty: {uncertainty:.1%}"] } except Exception as e: print(f"❌ TimesFM signal generation failed: {e}") return generate_technical_signal(symbol, data, "timesfm_error") def generate_technical_signal(symbol: str, data: pd.DataFrame, mode: str = "technical") -> Dict: """Fallback technical analysis signal generation""" # Calculate indicators rsi = calculate_rsi(data) macd = calculate_macd(data) bb = calculate_bollinger_bands(data) current_price = float(data['Close'].iloc[-1]) # Signal logic signals = [] # RSI signals if rsi < 30: signals.append(("BUY", 0.7, "RSI Oversold")) elif rsi > 70: signals.append(("SELL", 0.7, "RSI Overbought")) # MACD signals if macd["histogram"] > 0: signals.append(("BUY", 0.6, "MACD Bullish")) else: signals.append(("SELL", 0.6, "MACD Bearish")) # Bollinger Bands signals if current_price < bb["lower"]: signals.append(("BUY", 0.8, "Below Lower Band")) elif current_price > bb["upper"]: signals.append(("SELL", 0.8, "Above Upper Band")) # Calculate weighted signal buy_weight = sum(weight for action, weight, reason in signals if action == "BUY") sell_weight = sum(weight for action, weight, reason in signals if action == "SELL") if buy_weight > sell_weight: final_signal = "BUY" confidence = min(buy_weight / (buy_weight + sell_weight), 0.9) elif sell_weight > buy_weight: final_signal = "SELL" confidence = min(sell_weight / (buy_weight + sell_weight), 0.9) else: final_signal = "HOLD" confidence = 0.5 # Risk management atr = calculate_atr(data) stop_loss = atr * 1.5 take_profit = atr * 2.0 return { "symbol": symbol, "signal": final_signal, "confidence": round(confidence, 3), "current_price": current_price, "stop_loss": round(stop_loss, 5), "take_profit": round(take_profit, 5), "model_used": mode, "data_points": len(data), "timestamp": datetime.utcnow().isoformat(), "analysis_reasons": [reason for action, weight, reason in signals if action == final_signal] } def create_prediction_chart(data: pd.DataFrame, signal_data: Dict) -> go.Figure: """Create interactive chart with TimesFM predictions""" fig = go.Figure() # Historical price data fig.add_trace(go.Candlestick( x=data.index[-50:], # Show last 50 points open=data['Open'].iloc[-50:], high=data['High'].iloc[-50:], low=data['Low'].iloc[-50:], close=data['Close'].iloc[-50:], name='Historical Price' )) # Current price current_price = signal_data['current_price'] # Prediction line if available if 'predicted_price' in signal_data: predicted_price = signal_data['predicted_price'] # Extend index for future dates last_date = data.index[-1] future_dates = pd.date_range(start=last_date, periods=6, freq='D')[1:] # Create prediction line pred_x = list(data.index[-1:]) + list(future_dates[:5]) pred_y = [current_price] + [predicted_price] * 5 fig.add_trace(go.Scatter( x=pred_x, y=pred_y, mode='lines+markers', name='TimesFM Prediction', line=dict(color='cyan', width=2, dash='dash'), marker=dict(size=6) )) # Stop loss and take profit fig.add_hline(y=signal_data['stop_loss'], line_dash="dot", line_color="red", annotation_text="Stop Loss") fig.add_hline(y=signal_data['take_profit'], line_dash="dot", line_color="green", annotation_text="Take Profit") fig.update_layout( title=f"{signal_data['symbol']} - TimesFM Analysis", xaxis_title="Date", yaxis_title="Price", template="plotly_dark", showlegend=True ) return fig # Gradio Interface Functions def analyze_symbol(symbol: str): """Analyze a single symbol using TimesFM""" try: print(f"🔍 Starting TimesFM analysis for {symbol}") print(f"📊 Symbol type: {type(symbol)}, value: '{symbol}'") data = get_cached_data(symbol) if data is None or len(data) < 50: error_msg = f""" ❌ Insufficient data available for {symbol} **Data Points Available**: {len(data) if data is not None else 0} **Required**: 50+ points for TimesFM analysis **TimesFM Requirements**: - Minimum 50 historical data points - Quality price data (OHLC) - Consistent time intervals **Suggestions**: - Try major stocks: AAPL, GOOGL, TSLA - Try forex pairs: EURUSD=X, GBPUSD=X - Try with longer timeframes """ return error_msg, None, {} # Generate signal using TimesFM result = generate_timesfm_signal(symbol, data) # Create chart chart = create_prediction_chart(data, result) # Format results signal_emoji = {"BUY": "đŸŸĸ", "SELL": "🔴", "HOLD": "🟡"}[result['signal']] model_info = "🧠 Google TimesFM" if result['model_used'] == "timesfm" else "📊 Technical Analysis" summary = f""" ### {signal_emoji} AI Trading Signal for {symbol} **Signal**: {result['signal']} **Confidence**: {result['confidence']:.1%} **Model**: {model_info} **Price Information**: - **Current Price**: ${result['current_price']:.5f} {f'- **Predicted Price**: ${result.get("predicted_price", 0):.5f}' if 'predicted_price' in result else ''} {f'- **Expected Change**: {result.get("price_change_pct", 0):.2f}%' if 'price_change_pct' in result else ''} {f'- **Uncertainty**: {result.get("uncertainty", 0):.1%}' if 'uncertainty' in result else ''} **Risk Management**: - **Stop Loss**: ${result['stop_loss']:.5f} - **Take Profit**: ${result['take_profit']:.5f} **Analysis Details**: - **Data Points**: {result.get('data_points', 'N/A')} - **Analysis Time**: {datetime.now().strftime('%H:%M:%S')} - **Model Status**: {'✅ TimesFM Active' if result['model_used'] == 'timesfm' else 'âš ī¸ Fallback Mode'} **Key Reasons**: {', '.join(result.get('analysis_reasons', ['Technical Analysis']))} """ print(f"✅ TimesFM analysis completed for {symbol}: {result['signal']} with {result['confidence']:.1%} confidence") return summary, chart, result except Exception as e: error_msg = f""" ❌ Error analyzing {symbol}: {str(e)} **Troubleshooting**: - Check symbol format (e.g., AAPL, EURUSD=X) - Try major stocks or forex pairs - Ensure sufficient historical data """ print(f"❌ TimesFM analysis error for {symbol}: {e}") return error_msg, None, {} def analyze_batch(symbols_list: list): """Batch analysis with TimesFM""" try: # Handle both list (from checkboxes) and string (from backwards compatibility) if isinstance(symbols_list, str): symbols = [s.strip().upper() for s in symbols_list.split(',')] else: symbols = [s.strip().upper() for s in symbols_list] results = [] print(f"🚀 Starting TimesFM batch analysis for {len(symbols)} symbols") for symbol in symbols: data = get_cached_data(symbol) if data is not None and len(data) >= 50: result = generate_timesfm_signal(symbol, data) results.append(result) else: results.append({ "symbol": symbol, "signal": "ERROR", "confidence": 0.0, "error": "Insufficient data", "model_used": "none", "data_points": len(data) if data is not None else 0 }) # Create summary table summary_data = [] for r in results: if 'error' not in r: model_icon = "🧠" if r.get('model_used') == 'timesfm' else "📊" pred_change = f"{r.get('price_change_pct', 0):.1f}%" if 'price_change_pct' in r else "N/A" summary_data.append([ r['symbol'], r['signal'], f"{r['confidence']:.1%}", f"${r['current_price']:.5f}", pred_change, f"{model_icon} {r.get('model_used', 'unknown')}", r.get('data_points', 'N/A') ]) else: summary_data.append([r['symbol'], "ERROR", "0%", "N/A", "N/A", "❌ Error", r.get('data_points', 0)]) df = pd.DataFrame(summary_data, columns=["Symbol", "Signal", "Confidence", "Price", "Pred Change", "Model", "Data Points"]) # Format results buy_count = sum(1 for r in results if r.get('signal') == 'BUY') sell_count = sum(1 for r in results if r.get('signal') == 'SELL') error_count = sum(1 for r in results if r.get('signal') == 'ERROR') timesfm_count = sum(1 for r in results if r.get('model_used') == 'timesfm') summary = f""" ### 📊 TimesFM Batch Analysis Complete **Total Symbols**: {len(results)} **BUY Signals**: {buy_count} đŸŸĸ **SELL Signals**: {sell_count} 🔴 **Errors**: {error_count} ❌ **Model Usage**: - **TimesFM Active**: {timesfm_count} 🧠 - **Fallback Mode**: {len(results) - error_count - timesfm_count} 📊 **Analysis completed at**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} """ print(f"✅ TimesFM batch analysis completed: {buy_count} BUY, {sell_count} SELL, {timesfm_count} TimesFM") return summary, df, results except Exception as e: error_msg = f"❌ Error in TimesFM batch analysis: {str(e)}" print(f"❌ TimesFM batch analysis error: {e}") return error_msg, pd.DataFrame(), [] # Create Gradio Interface with TimesFM with gr.Blocks(title="AI Cash Evolution - TimesFM Trading", theme=gr.themes.Soft()) as demo: gr.Markdown(""" # 🤖 AI Cash Evolution - TimesFM Trading System v3.2 **Next-generation trading signals powered by Google's TimesFM foundation model** Features: - 🧠 **Google TimesFM**: State-of-the-art time series forecasting - đŸŽ¯ **AI-Driven Signals**: Foundation model predictions with confidence scores - 📈 **Technical Analysis**: RSI, MACD, Bollinger Bands + AI predictions - 📊 **Prediction Charts**: Visual forecasts with uncertainty quantification - 🚀 **Batch Analysis**: Multiple symbols with TimesFM processing - đŸ’Ē **Smart Fallbacks**: Technical analysis when TimesFM unavailable - đŸĻ **OANDA Integration**: Real-time forex data via direct API """) with gr.Tabs(): # Single Symbol Analysis with gr.TabItem("🧠 TimesFM Analysis"): gr.Markdown(""" **đŸĻ OANDA Direct API Integration**: **Available Assets**: - **Major Forex Pairs**: EURUSD, GBPUSD, USDJPY, USDCHF, AUDUSD, USDCAD, NZDUSD - **Minor Forex Pairs**: EURGBP, EURJPY, EURCHF, EURAUD, EURNZD, EURCAD - **Cross Pairs**: GBPJPY, GBPCHF, GBPAUD, GBPCAD, CHFJPY, AUDJPY, CADJPY, NZDJPY - **Metals**: XAUUSD (Gold), XAGUSD (Silver) - **Professional Selection**: All symbols pre-validated for OANDA API **Enhanced Features**: - ✅ **Direct OANDA Integration**: Real-time API connection - ✅ **Multiple Granularities**: D, H4, H1, M30, M15, M5 - ✅ **Complete Independence**: No Supabase or external services - ✅ **200 Historical Candles**: Maximum data for TimesFM analysis - ✅ **Symbol Validation**: Pre-configured dropdown prevents nomenclature errors """) # Pre-configured forex symbols for OANDA FOREX_SYMBOLS = [ "EURUSD", # Euro/US Dollar "GBPUSD", # British Pound/US Dollar "USDJPY", # US Dollar/Japanese Yen "USDCHF", # US Dollar/Swiss Franc "AUDUSD", # Australian Dollar/US Dollar "USDCAD", # US Dollar/Canadian Dollar "NZDUSD", # New Zealand Dollar/US Dollar "EURGBP", # Euro/British Pound "EURJPY", # Euro/Japanese Yen "EURCHF", # Euro/Swiss Franc "EURAUD", # Euro/Australian Dollar "EURNZD", # Euro/New Zealand Dollar "EURCAD", # Euro/Canadian Dollar "GBPJPY", # British Pound/Japanese Yen "GBPCHF", # British Pound/Swiss Franc "GBPAUD", # British Pound/Australian Dollar "GBPCAD", # British Pound/Canadian Dollar "CHFJPY", # Swiss Franc/Japanese Yen "AUDJPY", # Australian Dollar/Japanese Yen "CADJPY", # Canadian Dollar/Japanese Yen "NZDJPY", # New Zealand Dollar/Japanese Yen "AUDCHF", # Australian Dollar/Swiss Franc "CADCHF", # Canadian Dollar/Swiss Franc "XAUUSD", # Gold/US Dollar "XAGUSD" # Silver/US Dollar ] with gr.Row(): symbol_input = gr.Dropdown( label="Trading Symbol (OANDA Forex Pairs)", choices=FOREX_SYMBOLS, value="EURUSD", info="Select from OANDA-supported forex pairs and metals" ) analyze_btn = gr.Button("🧠 Analyze with TimesFM", variant="primary") with gr.Row(): with gr.Column(): summary_output = gr.Markdown() with gr.Column(): chart_output = gr.Plot() json_output = gr.JSON(label="Detailed Analysis", visible=False) analyze_btn.click( analyze_symbol, inputs=[symbol_input], outputs=[summary_output, chart_output, json_output] ) # Batch Analysis with gr.TabItem("📊 Batch Analysis"): with gr.Row(): symbols_input = gr.CheckboxGroup( label="Select Multiple Symbols for Analysis", choices=FOREX_SYMBOLS, value=["EURUSD", "GBPUSD", "USDJPY", "XAUUSD"], info="Choose forex pairs and metals to analyze" ) batch_btn = gr.Button("🚀 Batch TimesFM Analysis", variant="primary") batch_summary = gr.Markdown() batch_table = gr.Dataframe(label="Results") batch_btn.click( analyze_batch, inputs=[symbols_input], outputs=[batch_summary, batch_table, gr.JSON(visible=False)] ) # Main Dashboard with gr.TabItem("🏠 Main Dashboard"): gr.Markdown(""" ### 🚀 AI Cash Evolution Main Trading Dashboard Access the complete trading platform with real-time analysis, MT5 integration, and live signal generation. **Features**: - 📊 Real-time price charts and technical analysis - 🧠 AI-powered signal generation with TimesFM integration - 🔄 MT5 Expert Advisor integration - 💰 Subscription management and trial system - 📈 Live trading execution with risk management """) # Embed the main dashboard iframe gr.HTML("""

🔗 Open in new tab | 📱 Mobile-optimized trading dashboard

""") # About TimesFM with gr.TabItem("â„šī¸ About TimesFM"): gr.Markdown(""" ### 🚀 AI Cash Evolution + Google TimesFM **Version**: 3.2.0 (TimesFM Integration + Dashboard) **Platform**: Hugging Face Spaces **Status**: Production Ready with AI Foundation Models #### 🧠 About Google TimesFM: - **Foundation Model**: Pretrained on massive time series datasets - **Context Length**: Up to 16k time points - **Forecast Horizon**: Up to 256 steps ahead - **Architecture**: Decoder-only transformer model - **Developer**: Google Research #### đŸŽ¯ TimesFM Capabilities: - ✅ **Pattern Recognition**: Learns complex temporal patterns - ✅ **Uncertainty Quantification**: Confidence intervals for predictions - ✅ **Multi-step Forecasting**: 5-step ahead price predictions - ✅ **Zero-shot Transfer**: Works across different financial instruments - ✅ **Probabilistic Forecasts**: Quantile predictions for risk management #### 📊 Technical Analysis Integration: - **RSI** (Relative Strength Index) - **MACD** (Moving Average Convergence Divergence) - **Bollinger Bands** (Volatility bands) - **ATR** (Average True Range) #### đŸ›Ąī¸ Reliability Features: - ✅ **Smart Fallbacks**: Technical analysis when TimesFM unavailable - ✅ **Error Handling**: Graceful degradation for data issues - ✅ **Multi-Source Data**: Yahoo Finance + OANDA integration - ✅ **Validation**: Data quality checks and preprocessing #### 💡 Use Cases: - **Day Trading**: Short-term price direction predictions - **Swing Trading**: 5-step ahead forecasts for position sizing - **Risk Management**: Uncertainty quantification for position limits - **Market Analysis**: Pattern recognition across asset classes --- *Powered by Google's TimesFM Foundation Model | Enhanced for Financial Trading* """) # Footer gr.Markdown(""" --- **AI Cash Evolution v3.2** - TimesFM-Powered Trading System 🧠 Google TimesFM Integration | 📊 Advanced Technical Analysis | ⚡ Hugging Face Spaces """) # Launch the app if __name__ == "__main__": print("🚀 Starting AI Cash Evolution TimesFM Trading System v3.2...") print(f"🧠 TimesFM Available: {TIMESFM_AVAILABLE}") demo.launch( server_name="0.0.0.0", server_port=7860, show_api=True, share=False )