Tonic commited on
Commit
dfd9f16
·
unverified ·
1 Parent(s): e715fe9

attempts better api fetching and graceful fallbacks

Browse files
Files changed (1) hide show
  1. app.py +277 -200
app.py CHANGED
@@ -393,18 +393,28 @@ def retry_yfinance_request(func, max_retries=3, initial_delay=1):
393
  initial_delay: Initial delay in seconds before first retry
394
 
395
  Returns:
396
- Result of the function call if successful
397
  """
398
  for attempt in range(max_retries):
399
  try:
400
- return func()
 
 
 
 
 
 
 
 
 
 
401
  except Exception as e:
402
  error_str = str(e).lower()
403
 
404
  # Check if this is the last attempt
405
  if attempt == max_retries - 1:
406
  print(f"Final attempt failed after {max_retries} retries: {str(e)}")
407
- raise e
408
 
409
  # Determine delay based on error type and attempt number
410
  if "401" in error_str or "unauthorized" in error_str:
@@ -566,6 +576,7 @@ def cleanup_on_exit():
566
  def get_historical_data(symbol: str, timeframe: str = "1d", lookback_days: int = 365) -> pd.DataFrame:
567
  """
568
  Fetch historical data using yfinance with enhanced support for intraday data.
 
569
 
570
  Args:
571
  symbol (str): The stock symbol (e.g., 'AAPL')
@@ -583,191 +594,76 @@ def get_historical_data(symbol: str, timeframe: str = "1d", lookback_days: int =
583
 
584
  # Map timeframe to yfinance interval and adjust lookback period
585
  tf_map = {
586
- "1d": "1d",
587
- "1h": "1h",
588
- "15m": "15m"
589
  }
590
- interval = tf_map.get(timeframe, "1d")
591
 
592
- # Adjust lookback period based on timeframe and yfinance limits
593
- if timeframe == "1h":
594
- lookback_days = min(lookback_days, 60) # Yahoo allows up to 60 days for hourly data
595
- elif timeframe == "15m":
596
- lookback_days = min(lookback_days, 7) # Yahoo allows up to 7 days for 15m data
597
 
598
- # Calculate date range
599
- end_date = datetime.now()
600
- start_date = end_date - timedelta(days=lookback_days)
601
 
602
- # Fetch data using yfinance with retry mechanism
603
  ticker = yf.Ticker(symbol)
604
 
 
 
 
 
 
 
 
 
 
605
  def fetch_history():
606
  return ticker.history(
607
- start=start_date,
608
- end=end_date,
609
- interval=interval,
610
- prepost=True, # Include pre/post market data for intraday
611
- actions=True, # Include dividends and splits
612
- auto_adjust=True, # Automatically adjust for splits
613
- back_adjust=True, # Back-adjust data for splits
614
- repair=True # Repair missing data points
 
 
 
 
 
615
  )
616
 
617
  df = retry_yfinance_request(fetch_history)
618
 
619
- if df.empty:
620
- raise Exception(f"No data available for {symbol} in {timeframe} timeframe")
621
 
622
- # Ensure all required columns are present and numeric
623
- required_columns = ['Open', 'High', 'Low', 'Close', 'Volume']
624
- for col in required_columns:
625
- if col not in df.columns:
626
- raise Exception(f"Missing required column: {col}")
627
- df[col] = pd.to_numeric(df[col], errors='coerce')
628
 
629
- # Get additional info for structured products with retry mechanism
630
- def fetch_info():
631
- info = ticker.info
632
- if info is None:
633
- raise Exception(f"Could not fetch company info for {symbol}")
634
- return info
635
-
636
- try:
637
- info = retry_yfinance_request(fetch_info)
638
- df['Market_Cap'] = float(info.get('marketCap', 0))
639
- df['Sector'] = info.get('sector', 'Unknown')
640
- df['Industry'] = info.get('industry', 'Unknown')
641
- df['Dividend_Yield'] = float(info.get('dividendYield', 0))
642
-
643
- # Add additional company metrics
644
- df['Enterprise_Value'] = float(info.get('enterpriseValue', 0))
645
- df['P/E_Ratio'] = float(info.get('trailingPE', 0))
646
- df['Forward_P/E'] = float(info.get('forwardPE', 0))
647
- df['PEG_Ratio'] = float(info.get('pegRatio', 0))
648
- df['Price_to_Book'] = float(info.get('priceToBook', 0))
649
- df['Price_to_Sales'] = float(info.get('priceToSalesTrailing12Months', 0))
650
- df['Return_on_Equity'] = float(info.get('returnOnEquity', 0))
651
- df['Return_on_Assets'] = float(info.get('returnOnAssets', 0))
652
- df['Debt_to_Equity'] = float(info.get('debtToEquity', 0))
653
- df['Current_Ratio'] = float(info.get('currentRatio', 0))
654
- df['Quick_Ratio'] = float(info.get('quickRatio', 0))
655
- df['Gross_Margin'] = float(info.get('grossMargins', 0))
656
- df['Operating_Margin'] = float(info.get('operatingMargins', 0))
657
- df['Net_Margin'] = float(info.get('netIncomeToCommon', 0))
658
-
659
- except Exception as e:
660
- print(f"Warning: Could not fetch company info for {symbol}: {str(e)}")
661
- # Set default values for missing info
662
- df['Market_Cap'] = 0.0
663
- df['Sector'] = 'Unknown'
664
- df['Industry'] = 'Unknown'
665
- df['Dividend_Yield'] = 0.0
666
- df['Enterprise_Value'] = 0.0
667
- df['P/E_Ratio'] = 0.0
668
- df['Forward_P/E'] = 0.0
669
- df['PEG_Ratio'] = 0.0
670
- df['Price_to_Book'] = 0.0
671
- df['Price_to_Sales'] = 0.0
672
- df['Return_on_Equity'] = 0.0
673
- df['Return_on_Assets'] = 0.0
674
- df['Debt_to_Equity'] = 0.0
675
- df['Current_Ratio'] = 0.0
676
- df['Quick_Ratio'] = 0.0
677
- df['Gross_Margin'] = 0.0
678
- df['Operating_Margin'] = 0.0
679
- df['Net_Margin'] = 0.0
680
-
681
- # Calculate technical indicators with adjusted windows based on timeframe
682
- if timeframe == "1d":
683
- sma_window_20 = 20
684
- sma_window_50 = 50
685
- sma_window_200 = 200
686
- vol_window = 20
687
- elif timeframe == "1h":
688
- sma_window_20 = 20 * 6 # 5 trading days
689
- sma_window_50 = 50 * 6 # ~10 trading days
690
- sma_window_200 = 200 * 6 # ~40 trading days
691
- vol_window = 20 * 6
692
- else: # 15m
693
- sma_window_20 = 20 * 24 # 5 trading days
694
- sma_window_50 = 50 * 24 # ~10 trading days
695
- sma_window_200 = 200 * 24 # ~40 trading days
696
- vol_window = 20 * 24
697
 
698
- # Calculate technical indicators
699
- df['SMA_20'] = df['Close'].rolling(window=sma_window_20, min_periods=1).mean()
700
- df['SMA_50'] = df['Close'].rolling(window=sma_window_50, min_periods=1).mean()
701
- df['SMA_200'] = df['Close'].rolling(window=sma_window_200, min_periods=1).mean()
702
- df['RSI'] = calculate_rsi(df['Close'])
703
- df['MACD'], df['MACD_Signal'] = calculate_macd(df['Close'])
704
- df['BB_Upper'], df['BB_Middle'], df['BB_Lower'] = calculate_bollinger_bands(df['Close'])
705
 
706
  # Calculate returns and volatility
707
  df['Returns'] = df['Close'].pct_change()
708
- df['Volatility'] = df['Returns'].rolling(window=vol_window, min_periods=1).std()
709
- df['Annualized_Vol'] = df['Volatility'] * np.sqrt(252)
710
-
711
- # Calculate drawdown metrics
712
- df['Rolling_Max'] = df['Close'].rolling(window=len(df), min_periods=1).max()
713
- df['Drawdown'] = (df['Close'] - df['Rolling_Max']) / df['Rolling_Max']
714
- df['Max_Drawdown'] = df['Drawdown'].rolling(window=len(df), min_periods=1).min()
715
-
716
- # Calculate liquidity metrics
717
- df['Avg_Daily_Volume'] = df['Volume'].rolling(window=vol_window, min_periods=1).mean()
718
- df['Volume_Volatility'] = df['Volume'].rolling(window=vol_window, min_periods=1).std()
719
-
720
- # Calculate additional intraday metrics for shorter timeframes
721
- if timeframe in ["1h", "15m"]:
722
- # Intraday volatility
723
- df['Intraday_High_Low'] = (df['High'] - df['Low']) / df['Close']
724
- df['Intraday_Volatility'] = df['Intraday_High_Low'].rolling(window=vol_window, min_periods=1).mean()
725
-
726
- # Volume analysis
727
- df['Volume_Price_Trend'] = (df['Volume'] * df['Returns']).rolling(window=vol_window, min_periods=1).sum()
728
- df['Volume_SMA'] = df['Volume'].rolling(window=vol_window, min_periods=1).mean()
729
- df['Volume_Ratio'] = df['Volume'] / df['Volume_SMA']
730
-
731
- # Price momentum
732
- df['Price_Momentum'] = df['Close'].pct_change(periods=5)
733
- df['Volume_Momentum'] = df['Volume'].pct_change(periods=5)
734
-
735
- # Fill NaN values using forward fill then backward fill
736
- df = df.ffill().bfill()
737
-
738
- # Ensure we have enough data points
739
- min_required_points = 64 # Minimum required for Chronos
740
- if len(df) < min_required_points:
741
- # Try to fetch more historical data with retry mechanism
742
- extended_start_date = start_date - timedelta(days=min_required_points - len(df))
743
-
744
- def fetch_extended_history():
745
- return ticker.history(
746
- start=extended_start_date,
747
- end=start_date,
748
- interval=interval,
749
- prepost=True,
750
- actions=True,
751
- auto_adjust=True,
752
- back_adjust=True,
753
- repair=True
754
- )
755
-
756
- extended_df = retry_yfinance_request(fetch_extended_history)
757
- if not extended_df.empty:
758
- df = pd.concat([extended_df, df])
759
- df = df.ffill().bfill()
760
-
761
- if len(df) < 2:
762
- raise Exception(f"Insufficient data points for {symbol} in {timeframe} timeframe")
763
 
764
- # Final check for any remaining None values
765
- df = df.fillna(0)
766
 
 
767
  return df
768
 
769
  except Exception as e:
770
- raise Exception(f"Error fetching historical data for {symbol}: {str(e)}")
 
771
 
772
  def calculate_rsi(prices: pd.Series, period: int = 14) -> pd.Series:
773
  """Calculate Relative Strength Index"""
@@ -1001,6 +897,13 @@ def make_prediction_enhanced(symbol: str, timeframe: str = "1d", prediction_days
1001
  # Get historical data
1002
  df = get_historical_data(symbol, timeframe)
1003
 
 
 
 
 
 
 
 
1004
  # Collect enhanced covariate data
1005
  covariate_data = {}
1006
  market_conditions = {}
@@ -1020,10 +923,6 @@ def make_prediction_enhanced(symbol: str, timeframe: str = "1d", prediction_days
1020
  print("Calculating market sentiment...")
1021
  sentiment_data = calculate_market_sentiment(symbol, 30)
1022
 
1023
- # Initialize technical predictions variables
1024
- technical_predictions = {}
1025
- technical_uncertainties = {}
1026
-
1027
  # Detect market regime
1028
  regime_info = {}
1029
  if use_regime_detection:
@@ -1632,6 +1531,17 @@ def make_prediction_enhanced(symbol: str, timeframe: str = "1d", prediction_days
1632
  (technical_weight * final_uncertainty)**2 + (ensemble_weight * ensemble_uncertainty)**2
1633
  )
1634
 
 
 
 
 
 
 
 
 
 
 
 
1635
  print(f"Technical strategy completed: {len(final_pred)} predictions generated")
1636
 
1637
  except Exception as e:
@@ -1650,6 +1560,15 @@ def make_prediction_enhanced(symbol: str, timeframe: str = "1d", prediction_days
1650
  ensemble_pred = np.array([])
1651
  ensemble_uncertainty = np.array([])
1652
 
 
 
 
 
 
 
 
 
 
1653
  except Exception as fallback_error:
1654
  print(f"Fallback prediction error: {str(fallback_error)}")
1655
  raise
@@ -2025,6 +1944,7 @@ def calculate_trading_signals(df: pd.DataFrame) -> Dict:
2025
  def get_market_data(symbol: str = "^GSPC", lookback_days: int = 365) -> pd.DataFrame:
2026
  """
2027
  Fetch market data (S&P 500 by default) for correlation analysis and regime detection.
 
2028
 
2029
  Args:
2030
  symbol (str): Market index symbol (default: ^GSPC for S&P 500)
@@ -2042,22 +1962,35 @@ def get_market_data(symbol: str = "^GSPC", lookback_days: int = 365) -> pd.DataF
2042
 
2043
  try:
2044
  ticker = yf.Ticker(symbol)
2045
- end_date = datetime.now()
2046
- start_date = end_date - timedelta(days=lookback_days)
 
 
 
 
 
 
2047
 
2048
  def fetch_market_history():
2049
  return ticker.history(
2050
- start=start_date,
2051
- end=end_date,
2052
  interval="1d",
2053
  prepost=False,
2054
  actions=False,
2055
- auto_adjust=True
 
 
 
 
 
 
 
 
2056
  )
2057
 
2058
  df = retry_yfinance_request(fetch_market_history)
2059
 
2060
- if not df.empty:
2061
  df['Returns'] = df['Close'].pct_change()
2062
  df['Volatility'] = df['Returns'].rolling(window=20).std()
2063
 
@@ -2065,6 +1998,11 @@ def get_market_data(symbol: str = "^GSPC", lookback_days: int = 365) -> pd.DataF
2065
  market_data_cache[cache_key] = df
2066
  cache_expiry[cache_key] = current_time + CACHE_DURATION
2067
 
 
 
 
 
 
2068
  return df
2069
  except Exception as e:
2070
  print(f"Warning: Could not fetch market data for {symbol}: {str(e)}")
@@ -2132,13 +2070,61 @@ def detect_market_regime(returns: pd.Series, n_regimes: int = 3) -> Dict:
2132
  for attempt in range(3):
2133
  try:
2134
  if attempt == 0:
2135
- model = hmm.GaussianHMM(n_components=n_regimes, random_state=42, covariance_type="full", n_iter=100)
 
 
 
 
 
 
 
2136
  elif attempt == 1:
2137
- model = hmm.GaussianHMM(n_components=n_regimes, random_state=42, covariance_type="diag", n_iter=200)
 
 
 
 
 
 
 
2138
  else:
2139
- model = hmm.GaussianHMM(n_components=n_regimes, random_state=42, covariance_type="spherical", n_iter=300)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2140
 
2141
- model.fit(returns_array.reshape(-1, 1))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2142
 
2143
  # Get regime probabilities for the last observation
2144
  regime_probs = model.predict_proba(returns_array.reshape(-1, 1))
@@ -4550,7 +4536,7 @@ def calculate_regime_aware_uncertainty(quantiles: np.ndarray, regime_info: Dict,
4550
 
4551
  def get_market_info_from_yfinance(symbol: str) -> Dict:
4552
  """
4553
- Get market information from yfinance using the Market class and other features.
4554
 
4555
  Args:
4556
  symbol (str): Market symbol (e.g., '^GSPC', 'EURUSD=X', 'BTC-USD')
@@ -4561,16 +4547,21 @@ def get_market_info_from_yfinance(symbol: str) -> Dict:
4561
  try:
4562
  ticker = yf.Ticker(symbol)
4563
 
4564
- # Get basic info with retry
4565
- info = retry_yfinance_request(lambda: ticker.info)
4566
 
4567
- # Get current market data with retry
4568
- hist = retry_yfinance_request(lambda: ticker.history(period="1d"))
 
 
 
 
 
4569
 
4570
  # Get additional market data
4571
  market_data = {}
4572
 
4573
- if not hist.empty:
4574
  market_data.update({
4575
  'current_price': hist['Close'].iloc[-1],
4576
  'open_price': hist['Open'].iloc[-1],
@@ -4581,35 +4572,75 @@ def get_market_info_from_yfinance(symbol: str) -> Dict:
4581
  'change_percent': ((hist['Close'].iloc[-1] - hist['Open'].iloc[-1]) / hist['Open'].iloc[-1]) * 100
4582
  })
4583
 
4584
- # Get news if available with retry
4585
  try:
4586
- news = retry_yfinance_request(lambda: ticker.news)
4587
  market_data['news_count'] = len(news) if news else 0
4588
  except Exception as e:
4589
  print(f"Error fetching news for {symbol}: {str(e)}")
4590
  market_data['news_count'] = 0
4591
 
4592
- # Get recommendations if available with retry
4593
- try:
4594
- recommendations = retry_yfinance_request(lambda: ticker.recommendations)
4595
- market_data['recommendations'] = recommendations.tail(5).to_dict('records') if not recommendations.empty else []
4596
- except Exception as e:
4597
- print(f"Error fetching recommendations for {symbol}: {str(e)}")
4598
- market_data['recommendations'] = []
4599
 
4600
- # Get earnings info if available with retry
4601
- try:
4602
- earnings = retry_yfinance_request(lambda: ticker.earnings)
4603
- market_data['earnings'] = earnings.tail(4).to_dict('records') if not earnings.empty else []
4604
- except Exception as e:
4605
- print(f"Error fetching earnings for {symbol}: {str(e)}")
4606
  market_data['earnings'] = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4607
 
4608
  # Combine all data
4609
  result = {
4610
  'symbol': symbol,
4611
  'info': info,
4612
  'market_data': market_data,
 
4613
  'timestamp': datetime.now().isoformat()
4614
  }
4615
 
@@ -4702,6 +4733,52 @@ def check_market_status_simple(market_key: str) -> str:
4702
  except Exception as e:
4703
  return f"❌ Error checking market status: {str(e)}"
4704
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4705
  if __name__ == "__main__":
4706
  import signal
4707
  import atexit
 
393
  initial_delay: Initial delay in seconds before first retry
394
 
395
  Returns:
396
+ Result of the function call if successful, or None if all attempts fail
397
  """
398
  for attempt in range(max_retries):
399
  try:
400
+ result = func()
401
+ # Check if result is None (common with yfinance for unavailable data)
402
+ if result is None:
403
+ if attempt == max_retries - 1:
404
+ print(f"Function returned None after {max_retries} attempts")
405
+ return None
406
+ else:
407
+ print(f"Function returned None (attempt {attempt + 1}/{max_retries}), retrying...")
408
+ time.sleep(initial_delay * (2 ** attempt))
409
+ continue
410
+ return result
411
  except Exception as e:
412
  error_str = str(e).lower()
413
 
414
  # Check if this is the last attempt
415
  if attempt == max_retries - 1:
416
  print(f"Final attempt failed after {max_retries} retries: {str(e)}")
417
+ return None # Return None instead of raising to avoid crashes
418
 
419
  # Determine delay based on error type and attempt number
420
  if "401" in error_str or "unauthorized" in error_str:
 
576
  def get_historical_data(symbol: str, timeframe: str = "1d", lookback_days: int = 365) -> pd.DataFrame:
577
  """
578
  Fetch historical data using yfinance with enhanced support for intraday data.
579
+ Uses recommended API methods for better reliability.
580
 
581
  Args:
582
  symbol (str): The stock symbol (e.g., 'AAPL')
 
594
 
595
  # Map timeframe to yfinance interval and adjust lookback period
596
  tf_map = {
597
+ "1d": {"interval": "1d", "period": f"{lookback_days}d"},
598
+ "1h": {"interval": "1h", "period": f"{min(lookback_days * 24, 730)}h"}, # Max 730 hours (30 days)
599
+ "15m": {"interval": "15m", "period": f"{min(lookback_days * 96, 60)}d"} # Max 60 days for 15m
600
  }
 
601
 
602
+ if timeframe not in tf_map:
603
+ raise ValueError(f"Unsupported timeframe: {timeframe}. Supported: {list(tf_map.keys())}")
 
 
 
604
 
605
+ interval_config = tf_map[timeframe]
 
 
606
 
607
+ # Create ticker object
608
  ticker = yf.Ticker(symbol)
609
 
610
+ # Get history metadata first for better reliability
611
+ try:
612
+ hist_metadata = retry_yfinance_request(lambda: ticker.get_history_metadata())
613
+ if hist_metadata is None:
614
+ print(f"Warning: Could not get history metadata for {symbol}")
615
+ except Exception as e:
616
+ print(f"Warning: History metadata fetch failed for {symbol}: {str(e)}")
617
+
618
+ # Fetch historical data with retry mechanism
619
  def fetch_history():
620
  return ticker.history(
621
+ period=interval_config["period"],
622
+ interval=interval_config["interval"],
623
+ prepost=True,
624
+ actions=True,
625
+ auto_adjust=True,
626
+ back_adjust=True,
627
+ repair=True,
628
+ keepna=False,
629
+ threads=True,
630
+ proxy=None,
631
+ rounding=True,
632
+ timeout=30,
633
+ debug=False
634
  )
635
 
636
  df = retry_yfinance_request(fetch_history)
637
 
638
+ if df is None or df.empty:
639
+ raise Exception(f"No data returned for {symbol}")
640
 
641
+ # Validate data quality
642
+ if len(df) < 10:
643
+ raise Exception(f"Insufficient data for {symbol}: only {len(df)} data points")
 
 
 
644
 
645
+ # Check for missing values in critical columns
646
+ critical_columns = ['Open', 'High', 'Low', 'Close', 'Volume']
647
+ missing_data = df[critical_columns].isnull().sum()
648
+ if missing_data.sum() > len(df) * 0.1: # More than 10% missing data
649
+ print(f"Warning: Significant missing data for {symbol}: {missing_data.to_dict()}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
650
 
651
+ # Fill any remaining missing values with forward fill then backward fill
652
+ df = df.fillna(method='ffill').fillna(method='bfill')
 
 
 
 
 
653
 
654
  # Calculate returns and volatility
655
  df['Returns'] = df['Close'].pct_change()
656
+ df['Volatility'] = df['Returns'].rolling(window=20).std()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
657
 
658
+ # Calculate technical indicators
659
+ df = calculate_technical_indicators(df)
660
 
661
+ print(f"Successfully fetched {len(df)} data points for {symbol} ({timeframe})")
662
  return df
663
 
664
  except Exception as e:
665
+ print(f"Error fetching historical data for {symbol}: {str(e)}")
666
+ raise
667
 
668
  def calculate_rsi(prices: pd.Series, period: int = 14) -> pd.Series:
669
  """Calculate Relative Strength Index"""
 
897
  # Get historical data
898
  df = get_historical_data(symbol, timeframe)
899
 
900
+ # Initialize variables that might not be set in all strategy paths
901
+ advanced_uncertainties = {}
902
+ volume_pred = None
903
+ volume_uncertainty = None
904
+ technical_predictions = {}
905
+ technical_uncertainties = {}
906
+
907
  # Collect enhanced covariate data
908
  covariate_data = {}
909
  market_conditions = {}
 
923
  print("Calculating market sentiment...")
924
  sentiment_data = calculate_market_sentiment(symbol, 30)
925
 
 
 
 
 
926
  # Detect market regime
927
  regime_info = {}
928
  if use_regime_detection:
 
1531
  (technical_weight * final_uncertainty)**2 + (ensemble_weight * ensemble_uncertainty)**2
1532
  )
1533
 
1534
+ # Calculate advanced uncertainties for technical strategy
1535
+ historical_volatility = df['Volatility'].iloc[-1]
1536
+ # Create dummy quantiles for technical strategy (since we don't have quantiles from Chronos)
1537
+ dummy_quantiles = np.array([[
1538
+ [final_pred[i] - 2 * final_uncertainty[i], final_pred[i], final_pred[i] + 2 * final_uncertainty[i]]
1539
+ for i in range(len(final_pred))
1540
+ ]])
1541
+ advanced_uncertainties = calculate_advanced_uncertainty(
1542
+ dummy_quantiles, historical_volatility, market_conditions
1543
+ )
1544
+
1545
  print(f"Technical strategy completed: {len(final_pred)} predictions generated")
1546
 
1547
  except Exception as e:
 
1560
  ensemble_pred = np.array([])
1561
  ensemble_uncertainty = np.array([])
1562
 
1563
+ # Calculate advanced uncertainties for fallback case
1564
+ dummy_quantiles = np.array([[
1565
+ [final_pred[i] - 2 * final_uncertainty[i], final_pred[i], final_pred[i] + 2 * final_uncertainty[i]]
1566
+ for i in range(len(final_pred))
1567
+ ]])
1568
+ advanced_uncertainties = calculate_advanced_uncertainty(
1569
+ dummy_quantiles, volatility, market_conditions
1570
+ )
1571
+
1572
  except Exception as fallback_error:
1573
  print(f"Fallback prediction error: {str(fallback_error)}")
1574
  raise
 
1944
  def get_market_data(symbol: str = "^GSPC", lookback_days: int = 365) -> pd.DataFrame:
1945
  """
1946
  Fetch market data (S&P 500 by default) for correlation analysis and regime detection.
1947
+ Uses recommended yfinance API methods for better reliability.
1948
 
1949
  Args:
1950
  symbol (str): Market index symbol (default: ^GSPC for S&P 500)
 
1962
 
1963
  try:
1964
  ticker = yf.Ticker(symbol)
1965
+
1966
+ # Get history metadata first for better reliability
1967
+ try:
1968
+ hist_metadata = retry_yfinance_request(lambda: ticker.get_history_metadata())
1969
+ if hist_metadata is None:
1970
+ print(f"Warning: Could not get history metadata for {symbol}")
1971
+ except Exception as e:
1972
+ print(f"Warning: History metadata fetch failed for {symbol}: {str(e)}")
1973
 
1974
  def fetch_market_history():
1975
  return ticker.history(
1976
+ period=f"{lookback_days}d",
 
1977
  interval="1d",
1978
  prepost=False,
1979
  actions=False,
1980
+ auto_adjust=True,
1981
+ back_adjust=True,
1982
+ repair=True,
1983
+ keepna=False,
1984
+ threads=True,
1985
+ proxy=None,
1986
+ rounding=True,
1987
+ timeout=30,
1988
+ debug=False
1989
  )
1990
 
1991
  df = retry_yfinance_request(fetch_market_history)
1992
 
1993
+ if df is not None and not df.empty:
1994
  df['Returns'] = df['Close'].pct_change()
1995
  df['Volatility'] = df['Returns'].rolling(window=20).std()
1996
 
 
1998
  market_data_cache[cache_key] = df
1999
  cache_expiry[cache_key] = current_time + CACHE_DURATION
2000
 
2001
+ print(f"Successfully fetched market data for {symbol}: {len(df)} data points")
2002
+ else:
2003
+ print(f"Warning: No data returned for {symbol}")
2004
+ df = pd.DataFrame()
2005
+
2006
  return df
2007
  except Exception as e:
2008
  print(f"Warning: Could not fetch market data for {symbol}: {str(e)}")
 
2070
  for attempt in range(3):
2071
  try:
2072
  if attempt == 0:
2073
+ model = hmm.GaussianHMM(
2074
+ n_components=n_regimes,
2075
+ random_state=42,
2076
+ covariance_type="full",
2077
+ n_iter=500,
2078
+ tol=1e-4,
2079
+ init_params="stmc"
2080
+ )
2081
  elif attempt == 1:
2082
+ model = hmm.GaussianHMM(
2083
+ n_components=n_regimes,
2084
+ random_state=42,
2085
+ covariance_type="diag",
2086
+ n_iter=1000,
2087
+ tol=1e-3,
2088
+ init_params="stmc"
2089
+ )
2090
  else:
2091
+ model = hmm.GaussianHMM(
2092
+ n_components=n_regimes,
2093
+ random_state=42,
2094
+ covariance_type="spherical",
2095
+ n_iter=1500,
2096
+ tol=1e-2,
2097
+ init_params="stmc"
2098
+ )
2099
+
2100
+ # Add data preprocessing to improve convergence
2101
+ returns_clean = returns_array[~np.isnan(returns_array)]
2102
+ returns_clean = returns_clean[~np.isinf(returns_clean)]
2103
+
2104
+ # Remove outliers that might cause convergence issues
2105
+ q1, q3 = np.percentile(returns_clean, [25, 75])
2106
+ iqr = q3 - q1
2107
+ lower_bound = q1 - 1.5 * iqr
2108
+ upper_bound = q3 + 1.5 * iqr
2109
+ returns_filtered = returns_clean[(returns_clean >= lower_bound) & (returns_clean <= upper_bound)]
2110
 
2111
+ # Ensure we have enough data
2112
+ if len(returns_filtered) < 50:
2113
+ returns_filtered = returns_clean
2114
+
2115
+ # Fit the model with filtered data
2116
+ model.fit(returns_filtered.reshape(-1, 1))
2117
+
2118
+ # Check if model converged
2119
+ if model.monitor_.converged:
2120
+ print(f"HMM converged successfully with {model.covariance_type} covariance type")
2121
+ else:
2122
+ print(f"HMM did not converge with {model.covariance_type} covariance type, trying next configuration...")
2123
+ if attempt < 2: # Not the last attempt
2124
+ continue
2125
+ else:
2126
+ print("HMM failed to converge with all configurations, using fallback method")
2127
+ raise Exception("HMM convergence failed")
2128
 
2129
  # Get regime probabilities for the last observation
2130
  regime_probs = model.predict_proba(returns_array.reshape(-1, 1))
 
4536
 
4537
  def get_market_info_from_yfinance(symbol: str) -> Dict:
4538
  """
4539
+ Get market information from yfinance using the recommended API methods.
4540
 
4541
  Args:
4542
  symbol (str): Market symbol (e.g., '^GSPC', 'EURUSD=X', 'BTC-USD')
 
4547
  try:
4548
  ticker = yf.Ticker(symbol)
4549
 
4550
+ # Get basic info with retry - use get_info() method as recommended
4551
+ info = retry_yfinance_request(lambda: ticker.get_info())
4552
 
4553
+ # Get current market data with retry - use get_history_metadata() for better reliability
4554
+ try:
4555
+ hist_metadata = retry_yfinance_request(lambda: ticker.get_history_metadata())
4556
+ hist = retry_yfinance_request(lambda: ticker.history(period="1d"))
4557
+ except Exception as e:
4558
+ print(f"Error fetching history for {symbol}: {str(e)}")
4559
+ hist = None
4560
 
4561
  # Get additional market data
4562
  market_data = {}
4563
 
4564
+ if hist is not None and not hist.empty:
4565
  market_data.update({
4566
  'current_price': hist['Close'].iloc[-1],
4567
  'open_price': hist['Open'].iloc[-1],
 
4572
  'change_percent': ((hist['Close'].iloc[-1] - hist['Open'].iloc[-1]) / hist['Open'].iloc[-1]) * 100
4573
  })
4574
 
4575
+ # Get news if available with retry - use get_news() method
4576
  try:
4577
+ news = retry_yfinance_request(lambda: ticker.get_news())
4578
  market_data['news_count'] = len(news) if news else 0
4579
  except Exception as e:
4580
  print(f"Error fetching news for {symbol}: {str(e)}")
4581
  market_data['news_count'] = 0
4582
 
4583
+ # Skip earnings and recommendations for symbols that typically don't have them
4584
+ symbols_without_earnings = ['^', '=', 'F', 'X', 'USD', 'EUR', 'GBP', 'JPY', 'BTC', 'ETH', 'GC', 'SI', 'CL', 'NG']
4585
+ skip_earnings = any(symbol in symbol.upper() for symbol in symbols_without_earnings)
 
 
 
 
4586
 
4587
+ if skip_earnings:
 
 
 
 
 
4588
  market_data['earnings'] = []
4589
+ market_data['recommendations'] = []
4590
+ else:
4591
+ # Get recommendations if available with retry - use get_recommendations() method
4592
+ try:
4593
+ recommendations = retry_yfinance_request(lambda: ticker.get_recommendations())
4594
+ if recommendations is not None and hasattr(recommendations, 'empty') and not recommendations.empty:
4595
+ market_data['recommendations'] = recommendations.tail(5).to_dict('records')
4596
+ else:
4597
+ market_data['recommendations'] = []
4598
+ except Exception as e:
4599
+ print(f"Error fetching recommendations for {symbol}: {str(e)}")
4600
+ market_data['recommendations'] = []
4601
+
4602
+ # Get earnings info if available with retry - use get_earnings() method
4603
+ try:
4604
+ earnings = retry_yfinance_request(lambda: ticker.get_earnings())
4605
+ # Check if earnings is None or empty before accessing .empty
4606
+ if earnings is not None and hasattr(earnings, 'empty') and not earnings.empty:
4607
+ market_data['earnings'] = earnings.tail(4).to_dict('records')
4608
+ else:
4609
+ market_data['earnings'] = []
4610
+ except Exception as e:
4611
+ print(f"Error fetching earnings for {symbol}: {str(e)}")
4612
+ market_data['earnings'] = []
4613
+
4614
+ # Get additional data based on symbol type
4615
+
4616
+ # For stocks, try to get dividends and splits
4617
+ if not skip_earnings:
4618
+ try:
4619
+ dividends = retry_yfinance_request(lambda: ticker.get_dividends())
4620
+ if dividends is not None and hasattr(dividends, 'empty') and not dividends.empty:
4621
+ additional_data['dividends'] = dividends.tail(4).to_dict('records')
4622
+ else:
4623
+ additional_data['dividends'] = []
4624
+ except Exception as e:
4625
+ print(f"Error fetching dividends for {symbol}: {str(e)}")
4626
+ additional_data['dividends'] = []
4627
+
4628
+ try:
4629
+ splits = retry_yfinance_request(lambda: ticker.get_splits())
4630
+ if splits is not None and hasattr(splits, 'empty') and not splits.empty:
4631
+ additional_data['splits'] = splits.tail(4).to_dict('records')
4632
+ else:
4633
+ additional_data['splits'] = []
4634
+ except Exception as e:
4635
+ print(f"Error fetching splits for {symbol}: {str(e)}")
4636
+ additional_data['splits'] = []
4637
 
4638
  # Combine all data
4639
  result = {
4640
  'symbol': symbol,
4641
  'info': info,
4642
  'market_data': market_data,
4643
+ 'additional_data': additional_data,
4644
  'timestamp': datetime.now().isoformat()
4645
  }
4646
 
 
4733
  except Exception as e:
4734
  return f"❌ Error checking market status: {str(e)}"
4735
 
4736
+ def calculate_technical_indicators(df: pd.DataFrame) -> pd.DataFrame:
4737
+ """
4738
+ Calculate technical indicators for a given DataFrame.
4739
+
4740
+ Args:
4741
+ df (pd.DataFrame): DataFrame with OHLCV data
4742
+
4743
+ Returns:
4744
+ pd.DataFrame: DataFrame with technical indicators added
4745
+ """
4746
+ try:
4747
+ # Calculate moving averages
4748
+ df['SMA_20'] = df['Close'].rolling(window=20, min_periods=1).mean()
4749
+ df['SMA_50'] = df['Close'].rolling(window=50, min_periods=1).mean()
4750
+ df['SMA_200'] = df['Close'].rolling(window=200, min_periods=1).mean()
4751
+
4752
+ # Calculate RSI
4753
+ df['RSI'] = calculate_rsi(df['Close'])
4754
+
4755
+ # Calculate MACD
4756
+ df['MACD'], df['MACD_Signal'] = calculate_macd(df['Close'])
4757
+
4758
+ # Calculate Bollinger Bands
4759
+ df['BB_Upper'], df['BB_Middle'], df['BB_Lower'] = calculate_bollinger_bands(df['Close'])
4760
+
4761
+ # Calculate additional volatility metrics
4762
+ df['Annualized_Vol'] = df['Volatility'] * np.sqrt(252)
4763
+
4764
+ # Calculate drawdown metrics
4765
+ df['Rolling_Max'] = df['Close'].rolling(window=len(df), min_periods=1).max()
4766
+ df['Drawdown'] = (df['Close'] - df['Rolling_Max']) / df['Rolling_Max']
4767
+ df['Max_Drawdown'] = df['Drawdown'].rolling(window=len(df), min_periods=1).min()
4768
+
4769
+ # Calculate liquidity metrics
4770
+ df['Avg_Daily_Volume'] = df['Volume'].rolling(window=20, min_periods=1).mean()
4771
+ df['Volume_Volatility'] = df['Volume'].rolling(window=20, min_periods=1).std()
4772
+
4773
+ # Fill any remaining NaN values
4774
+ df = df.fillna(method='ffill').fillna(method='bfill')
4775
+
4776
+ return df
4777
+
4778
+ except Exception as e:
4779
+ print(f"Error calculating technical indicators: {str(e)}")
4780
+ return df
4781
+
4782
  if __name__ == "__main__":
4783
  import signal
4784
  import atexit