Spaces:
Paused
Paused
Tonic commited on
attempts better api fetching and graceful fallbacks
Browse files
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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 |
-
|
| 593 |
-
|
| 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 |
-
|
| 599 |
-
end_date = datetime.now()
|
| 600 |
-
start_date = end_date - timedelta(days=lookback_days)
|
| 601 |
|
| 602 |
-
#
|
| 603 |
ticker = yf.Ticker(symbol)
|
| 604 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 605 |
def fetch_history():
|
| 606 |
return ticker.history(
|
| 607 |
-
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
|
| 611 |
-
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 615 |
)
|
| 616 |
|
| 617 |
df = retry_yfinance_request(fetch_history)
|
| 618 |
|
| 619 |
-
if df.empty:
|
| 620 |
-
raise Exception(f"No data
|
| 621 |
|
| 622 |
-
#
|
| 623 |
-
|
| 624 |
-
|
| 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 |
-
#
|
| 630 |
-
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
|
| 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 |
-
#
|
| 699 |
-
df
|
| 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=
|
| 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 |
-
#
|
| 765 |
-
df =
|
| 766 |
|
|
|
|
| 767 |
return df
|
| 768 |
|
| 769 |
except Exception as e:
|
| 770 |
-
|
|
|
|
| 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 |
-
|
| 2046 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2047 |
|
| 2048 |
def fetch_market_history():
|
| 2049 |
return ticker.history(
|
| 2050 |
-
|
| 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(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2136 |
elif attempt == 1:
|
| 2137 |
-
model = hmm.GaussianHMM(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2138 |
else:
|
| 2139 |
-
model = hmm.GaussianHMM(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2140 |
|
| 2141 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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.
|
| 4566 |
|
| 4567 |
-
# Get current market data with retry
|
| 4568 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 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 |
-
#
|
| 4593 |
-
|
| 4594 |
-
|
| 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 |
-
|
| 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
|