Dmitry Beresnev commited on
Commit
3610f46
·
1 Parent(s): c226f41

fix portfolio calculator

Browse files
Files changed (1) hide show
  1. portfolio_calculator.py +73 -36
portfolio_calculator.py CHANGED
@@ -31,6 +31,10 @@ def fetch_historical_data(
31
  """
32
  Fetch historical price data using yfinance.
33
 
 
 
 
 
34
  Args:
35
  tickers: Tuple of ticker symbols (e.g., ('AAPL', 'GOOGL', 'MSFT'))
36
  period: Time period for historical data (default: '1y')
@@ -40,45 +44,78 @@ def fetch_historical_data(
40
  - If successful: (DataFrame, None)
41
  - If failed: (None, error_message)
42
  """
 
 
 
43
  try:
44
- # Convert tuple back to list for yfinance
45
- ticker_list = list(tickers)
46
-
47
- # Download data (progress=False to avoid console output in Streamlit)
48
- data = yf.download(ticker_list, period=period, progress=False)
49
-
50
- # Check if data was returned
51
- if data.empty:
52
- return None, "No data returned from yfinance. Please check ticker symbols."
53
-
54
- # Extract 'Adj Close' prices
55
- if len(ticker_list) == 1:
56
- # Single ticker: yfinance returns different structure
57
- prices = data[['Adj Close']].copy()
58
- prices.columns = ticker_list
59
- else:
60
- # Multiple tickers
61
- prices = data['Adj Close'].copy()
62
-
63
- # Check for missing data
64
- missing_count = prices.isnull().sum()
65
- if missing_count.sum() > 0:
66
- missing_tickers = missing_count[missing_count > 0]
67
- warning = f"Warning: Missing data detected - {dict(missing_tickers)}"
68
- # Don't fail, just warn
69
- st.warning(warning)
70
-
71
- # Drop rows with NaN values
72
- prices = prices.dropna()
73
-
74
- # Check we have enough data points
75
- if len(prices) < MIN_DATA_POINTS:
76
- return None, f"Insufficient data: only {len(prices)} days available (minimum {MIN_DATA_POINTS} required)"
77
-
78
- return prices, None
79
 
80
  except Exception as e:
81
- return None, f"Failed to fetch data: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
 
84
  def calculate_log_returns(prices: pd.DataFrame) -> pd.DataFrame:
 
31
  """
32
  Fetch historical price data using yfinance.
33
 
34
+ Uses fallback strategy:
35
+ 1. Try downloading all tickers together
36
+ 2. If that fails, download one by one and combine
37
+
38
  Args:
39
  tickers: Tuple of ticker symbols (e.g., ('AAPL', 'GOOGL', 'MSFT'))
40
  period: Time period for historical data (default: '1y')
 
44
  - If successful: (DataFrame, None)
45
  - If failed: (None, error_message)
46
  """
47
+ ticker_list = list(tickers)
48
+
49
+ # Strategy 1: Try downloading all tickers together
50
  try:
51
+ data = yf.download(
52
+ ticker_list,
53
+ period=period,
54
+ progress=False,
55
+ threads=False, # Disable threading for better reliability
56
+ ignore_tz=True
57
+ )
58
+
59
+ if not data.empty:
60
+ # Extract 'Adj Close' prices
61
+ if len(ticker_list) == 1:
62
+ prices = data[['Adj Close']].copy()
63
+ prices.columns = ticker_list
64
+ else:
65
+ prices = data['Adj Close'].copy()
66
+
67
+ # Drop rows with NaN values
68
+ prices = prices.dropna()
69
+
70
+ if len(prices) >= MIN_DATA_POINTS:
71
+ return prices, None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
  except Exception as e:
74
+ # Log the error but continue to fallback strategy
75
+ print(f"Batch download failed: {str(e)}, trying individual downloads...")
76
+
77
+ # Strategy 2: Download one ticker at a time
78
+ st.info("Fetching data individually for each ticker...")
79
+ individual_prices = {}
80
+ failed_tickers = []
81
+
82
+ for ticker in ticker_list:
83
+ try:
84
+ # Use Ticker object for more reliable downloads
85
+ ticker_obj = yf.Ticker(ticker)
86
+ hist = ticker_obj.history(period=period)
87
+
88
+ if hist.empty:
89
+ failed_tickers.append(ticker)
90
+ continue
91
+
92
+ # Extract close prices
93
+ individual_prices[ticker] = hist['Close']
94
+
95
+ except Exception as e:
96
+ failed_tickers.append(ticker)
97
+ print(f"Failed to fetch {ticker}: {str(e)}")
98
+ continue
99
+
100
+ # Check if we got any data
101
+ if not individual_prices:
102
+ return None, f"Could not fetch data for any tickers. Failed: {', '.join(failed_tickers)}"
103
+
104
+ # Combine all individual price series
105
+ prices_df = pd.DataFrame(individual_prices)
106
+
107
+ # Drop rows with NaN values
108
+ prices_df = prices_df.dropna()
109
+
110
+ # Check we have enough data points
111
+ if len(prices_df) < MIN_DATA_POINTS:
112
+ return None, f"Insufficient data: only {len(prices_df)} days available (minimum {MIN_DATA_POINTS} required)"
113
+
114
+ # Warn about failed tickers
115
+ if failed_tickers:
116
+ st.warning(f"⚠️ Could not fetch data for: {', '.join(failed_tickers)}")
117
+
118
+ return prices_df, None
119
 
120
 
121
  def calculate_log_returns(prices: pd.DataFrame) -> pd.DataFrame: