lucifer7210 commited on
Commit
75ea76e
Β·
verified Β·
1 Parent(s): c690a86

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +771 -0
app.py ADDED
@@ -0,0 +1,771 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import numpy as np
3
+ import yfinance as yf
4
+ import gradio as gr
5
+ from datetime import datetime, timedelta
6
+ import warnings
7
+ import logging
8
+ from typing import List, Dict, Tuple
9
+ import os
10
+ import json
11
+
12
+ # Hugging Face and LangChain imports
13
+ from langchain.docstore.document import Document
14
+ from langchain_huggingface import HuggingFaceEmbeddings, HuggingFacePipeline
15
+ from langchain.vectorstores import Chroma
16
+ from langchain.chains import RetrievalQA
17
+ from langchain.prompts import PromptTemplate
18
+ import transformers
19
+ from transformers import AutoTokenizer
20
+
21
+ warnings.filterwarnings('ignore')
22
+
23
+ # Configure logging
24
+ logging.basicConfig(level=logging.INFO)
25
+ logger = logging.getLogger(__name__)
26
+
27
+ class MutualFundRAG:
28
+ """RAG system for mutual fund portfolio optimization with LLM"""
29
+
30
+ def __init__(self):
31
+ # Popular mutual fund tickers
32
+ self.fund_tickers = [
33
+ 'VTIAX', # Vanguard Total International Stock Index
34
+ 'VTSAX', # Vanguard Total Stock Market Index
35
+ 'VBTLX', # Vanguard Total Bond Market Index
36
+ 'VTBLX', # Vanguard Total International Bond Index
37
+ 'VGIAX', # Vanguard Growth Index
38
+ 'VIMAX', # Vanguard Mid-Cap Index
39
+ 'VSMAX', # Vanguard Small-Cap Index
40
+ 'VGSLX', # Vanguard Real Estate Index
41
+ 'VHDYX', # Vanguard High Dividend Yield Index
42
+ 'VTAPX' # Vanguard Target Retirement 2065
43
+ ]
44
+
45
+ # Additional popular funds
46
+ self.extended_tickers = [
47
+ 'FXNAX', # Fidelity US Bond Index
48
+ 'FSKAX', # Fidelity Total Market Index
49
+ 'FTIHX', # Fidelity Total International Index
50
+ 'SPY', # SPDR S&P 500 ETF
51
+ 'QQQ', # Invesco QQQ Trust
52
+ 'VTI', # Vanguard Total Stock Market ETF
53
+ 'BND', # Vanguard Total Bond Market ETF
54
+ ]
55
+
56
+ self.fund_data = None
57
+ self.embeddings = None
58
+ self.vectorstore = None
59
+ self.qa_chain = None
60
+ self.llm = None
61
+
62
+ # Market indicators
63
+ self.market_indicators = {}
64
+
65
+ # User profile
66
+ self.user_profile = {
67
+ 'risk_tolerance': 'moderate',
68
+ 'investment_amount': 50000,
69
+ 'investment_horizon': 5,
70
+ 'preferred_sectors': []
71
+ }
72
+
73
+ def initialize_llm(self, model_name="Qwen/Qwen3-0.6B-Base"):
74
+ """Initialize the LLM for RAG system"""
75
+ try:
76
+ logger.info(f"Initializing LLM: {model_name}")
77
+
78
+ # Initialize tokenizer and model
79
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
80
+ if tokenizer.pad_token is None:
81
+ tokenizer.pad_token = tokenizer.eos_token
82
+
83
+ model = transformers.AutoModelForCausalLM.from_pretrained(
84
+ model_name,
85
+ device_map="auto",
86
+ torch_dtype="auto"
87
+ )
88
+
89
+ # Create pipeline
90
+ pipeline = transformers.pipeline(
91
+ "text-generation",
92
+ model=model,
93
+ tokenizer=tokenizer,
94
+ max_new_tokens=512,
95
+ temperature=0.7,
96
+ do_sample=True,
97
+ pad_token_id=tokenizer.eos_token_id
98
+ )
99
+
100
+ self.llm = HuggingFacePipeline(pipeline=pipeline)
101
+ logger.info("LLM initialized successfully")
102
+ return "βœ… LLM initialized successfully"
103
+
104
+ except Exception as e:
105
+ logger.error(f"Error initializing LLM: {e}")
106
+ return f"❌ Error initializing LLM: {str(e)}"
107
+
108
+ def fetch_fund_data(self, tickers: List[str] = None, period: str = '1y') -> pd.DataFrame:
109
+ """Fetch real mutual fund data from Yahoo Finance"""
110
+ if tickers is None:
111
+ tickers = self.fund_tickers
112
+
113
+ fund_data = []
114
+
115
+ logger.info("Fetching mutual fund data from Yahoo Finance...")
116
+
117
+ for ticker in tickers:
118
+ try:
119
+ fund = yf.Ticker(ticker)
120
+ hist = fund.history(period=period)
121
+ info = fund.info
122
+
123
+ if hist.empty:
124
+ continue
125
+
126
+ # Calculate metrics
127
+ returns = hist['Close'].pct_change().dropna()
128
+ avg_return = returns.mean() * 252 # Annualized
129
+ volatility = returns.std() * np.sqrt(252) # Annualized
130
+ sharpe_ratio = avg_return / volatility if volatility != 0 else 0
131
+
132
+ # Get latest NAV
133
+ latest_nav = hist['Close'].iloc[-1]
134
+
135
+ # Risk categorization
136
+ if volatility < 0.1:
137
+ risk_level = 'Low'
138
+ elif volatility < 0.2:
139
+ risk_level = 'Medium'
140
+ else:
141
+ risk_level = 'High'
142
+
143
+ # Get fund information
144
+ fund_name = info.get('longName', ticker)
145
+ category = info.get('category', 'Unknown')
146
+ expense_ratio = info.get('annualReportExpenseRatio', np.nan)
147
+
148
+ # Estimate sector exposure (simplified)
149
+ sector_exposure = self.estimate_sector_exposure(fund_name, category)
150
+
151
+ fund_data.append({
152
+ 'Ticker': ticker,
153
+ 'Name': fund_name[:50] + '...' if len(fund_name) > 50 else fund_name,
154
+ 'Category': category,
155
+ 'NAV': round(latest_nav, 2),
156
+ 'Annual_Return_%': round(avg_return * 100, 2),
157
+ 'Volatility_%': round(volatility * 100, 2),
158
+ 'Sharpe_Ratio': round(sharpe_ratio, 3),
159
+ 'Risk_Level': risk_level,
160
+ 'Expense_Ratio_%': round(expense_ratio * 100, 2) if not np.isnan(expense_ratio) else 'N/A',
161
+ **sector_exposure
162
+ })
163
+
164
+ logger.info(f"Successfully fetched data for {ticker}")
165
+
166
+ except Exception as e:
167
+ logger.error(f"Error fetching {ticker}: {e}")
168
+ continue
169
+
170
+ self.fund_data = pd.DataFrame(fund_data)
171
+ return self.fund_data
172
+
173
+ def estimate_sector_exposure(self, fund_name: str, category: str) -> Dict:
174
+ """Estimate sector exposure based on fund type"""
175
+ sector_exposure = {
176
+ 'Technology_%': 0,
177
+ 'Healthcare_%': 0,
178
+ 'Finance_%': 0,
179
+ 'Energy_%': 0,
180
+ 'Consumer_%': 0,
181
+ 'Real_Estate_%': 0
182
+ }
183
+
184
+ fund_name_lower = fund_name.lower()
185
+ category_lower = category.lower()
186
+
187
+ if 'technology' in fund_name_lower or 'tech' in fund_name_lower:
188
+ sector_exposure['Technology_%'] = np.random.uniform(60, 90)
189
+ elif 'real estate' in fund_name_lower or 'reit' in fund_name_lower:
190
+ sector_exposure['Real_Estate_%'] = np.random.uniform(70, 95)
191
+ elif 'total' in fund_name_lower or 'market' in fund_name_lower:
192
+ # Diversified fund
193
+ total = 100
194
+ for sector in sector_exposure.keys():
195
+ if total > 0:
196
+ allocation = np.random.uniform(10, 25)
197
+ allocation = min(allocation, total)
198
+ sector_exposure[sector] = round(allocation, 1)
199
+ total -= allocation
200
+ else:
201
+ # Random allocation for other funds
202
+ remaining = 100
203
+ sectors = list(sector_exposure.keys())
204
+ for i, sector in enumerate(sectors[:-1]):
205
+ if remaining > 0:
206
+ allocation = np.random.uniform(0, min(30, remaining))
207
+ sector_exposure[sector] = round(allocation, 1)
208
+ remaining -= allocation
209
+ sector_exposure[sectors[-1]] = round(remaining, 1)
210
+
211
+ return sector_exposure
212
+
213
+ def get_market_indicators(self) -> Dict:
214
+ """Fetch current market indicators"""
215
+ try:
216
+ # Fetch 10-year treasury yield
217
+ treasury = yf.Ticker("^TNX")
218
+ treasury_hist = treasury.history(period="5d")
219
+ interest_rate = treasury_hist['Close'].iloc[-1] if not treasury_hist.empty else 3.5
220
+
221
+ # VIX for market volatility
222
+ vix = yf.Ticker("^VIX")
223
+ vix_hist = vix.history(period="5d")
224
+ market_volatility = vix_hist['Close'].iloc[-1] if not vix_hist.empty else 20
225
+
226
+ self.market_indicators = {
227
+ 'Interest_Rate_%': round(interest_rate, 2),
228
+ 'Inflation_Rate_%': 3.2, # Static for demo
229
+ 'Market_Volatility_VIX': round(market_volatility, 2),
230
+ 'Last_Updated': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
231
+ }
232
+
233
+ return self.market_indicators
234
+
235
+ except Exception as e:
236
+ logger.error(f"Error fetching market indicators: {e}")
237
+ return {
238
+ 'Interest_Rate_%': 3.5,
239
+ 'Inflation_Rate_%': 3.2,
240
+ 'Market_Volatility_VIX': 20.0,
241
+ 'Last_Updated': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
242
+ }
243
+
244
+ def prepare_documents(self) -> List[Document]:
245
+ """Convert fund data to documents for ChromaDB"""
246
+ if self.fund_data is None or self.fund_data.empty:
247
+ return []
248
+
249
+ documents = []
250
+
251
+ for _, row in self.fund_data.iterrows():
252
+ content = f"""
253
+ Fund: {row['Ticker']} - {row['Name']}
254
+ Category: {row['Category']}
255
+ NAV: ${row['NAV']}
256
+ Annual Return: {row['Annual_Return_%']}%
257
+ Volatility: {row['Volatility_%']}%
258
+ Sharpe Ratio: {row['Sharpe_Ratio']}
259
+ Risk Level: {row['Risk_Level']}
260
+ Expense Ratio: {row['Expense_Ratio_%']}%
261
+ Sector Allocation - Technology: {row['Technology_%']}%, Healthcare: {row['Healthcare_%']}%,
262
+ Finance: {row['Finance_%']}%, Energy: {row['Energy_%']}%,
263
+ Consumer: {row['Consumer_%']}%, Real Estate: {row['Real_Estate_%']}%
264
+ Market Context - Interest Rate: {self.market_indicators.get('Interest_Rate_%', 'N/A')}%,
265
+ Inflation: {self.market_indicators.get('Inflation_Rate_%', 'N/A')}%,
266
+ VIX: {self.market_indicators.get('Market_Volatility_VIX', 'N/A')}
267
+ """
268
+
269
+ documents.append(Document(page_content=content.strip()))
270
+
271
+ return documents
272
+
273
+ def setup_rag_system(self):
274
+ """Setup the complete RAG system"""
275
+ try:
276
+ logger.info("Setting up RAG system...")
277
+
278
+ # Initialize embeddings
279
+ self.embeddings = HuggingFaceEmbeddings(
280
+ model_name="sentence-transformers/all-MiniLM-L6-v2"
281
+ )
282
+
283
+ # Prepare documents
284
+ documents = self.prepare_documents()
285
+
286
+ if not documents:
287
+ return "❌ No documents to process. Please fetch fund data first."
288
+
289
+ # Setup ChromaDB
290
+ self.vectorstore = Chroma.from_documents(
291
+ documents=documents,
292
+ collection_name="mutual_fund_optimization",
293
+ embedding=self.embeddings,
294
+ persist_directory="./mutual_fund_db"
295
+ )
296
+
297
+ # Setup QA chain if LLM is available
298
+ if self.llm is not None:
299
+ template = """
300
+ You are a financial advisor specializing in mutual fund portfolio optimization.
301
+
302
+ Based on the following mutual fund data, provide specific investment recommendations.
303
+
304
+ Context: {context}
305
+
306
+ Question: {question}
307
+
308
+ Please provide:
309
+ 1. Recommended portfolio allocation percentages
310
+ 2. Risk assessment based on the user's profile
311
+ 3. Expected returns analysis
312
+ 4. Sector diversification recommendations
313
+ 5. Specific fund recommendations with rationale
314
+
315
+ Keep your response concise and actionable.
316
+
317
+ Answer:
318
+ """
319
+
320
+ prompt = PromptTemplate(
321
+ input_variables=["context", "question"],
322
+ template=template
323
+ )
324
+
325
+ self.qa_chain = RetrievalQA.from_chain_type(
326
+ llm=self.llm,
327
+ chain_type="stuff",
328
+ retriever=self.vectorstore.as_retriever(search_kwargs={"k": 5}),
329
+ chain_type_kwargs={"prompt": prompt}
330
+ )
331
+
332
+ logger.info("RAG system setup complete")
333
+ return "βœ… RAG system initialized successfully"
334
+
335
+ except Exception as e:
336
+ logger.error(f"Error setting up RAG system: {e}")
337
+ return f"❌ Error setting up RAG system: {str(e)}"
338
+
339
+ def get_ai_recommendations(self, user_query: str) -> str:
340
+ """Get AI-powered investment recommendations"""
341
+ try:
342
+ if self.qa_chain is None:
343
+ return "❌ AI system not initialized. Please setup the RAG system first."
344
+
345
+ # Add user profile context to query
346
+ contextual_query = f"""
347
+ User Profile:
348
+ - Risk Tolerance: {self.user_profile['risk_tolerance']}
349
+ - Investment Amount: ${self.user_profile['investment_amount']:,}
350
+ - Investment Horizon: {self.user_profile['investment_horizon']} years
351
+
352
+ Market Context:
353
+ - Interest Rate: {self.market_indicators.get('Interest_Rate_%', 'N/A')}%
354
+ - Market Volatility (VIX): {self.market_indicators.get('Market_Volatility_VIX', 'N/A')}
355
+
356
+ User Question: {user_query}
357
+ """
358
+
359
+ logger.info("Generating AI recommendations...")
360
+ result = self.qa_chain({"query": contextual_query})
361
+
362
+ return result.get('result', 'No recommendation generated')
363
+
364
+ except Exception as e:
365
+ logger.error(f"Error getting AI recommendations: {e}")
366
+ return f"❌ Error generating recommendations: {str(e)}"
367
+
368
+ def calculate_portfolio_metrics(self, selected_funds: List[str], weights: List[float]) -> Dict:
369
+ """Calculate portfolio-level metrics"""
370
+ try:
371
+ # Fetch historical data for selected funds
372
+ tickers_str = ' '.join(selected_funds)
373
+ data = yf.download(tickers_str, period='1y', progress=False)['Close']
374
+
375
+ if data.empty:
376
+ return {"error": "No data available for selected funds"}
377
+
378
+ # Calculate returns
379
+ returns = data.pct_change().dropna()
380
+
381
+ # Portfolio returns
382
+ weights = np.array(weights) / np.sum(weights) # Normalize weights
383
+ portfolio_returns = returns.dot(weights)
384
+
385
+ # Portfolio metrics
386
+ annual_return = portfolio_returns.mean() * 252
387
+ annual_volatility = portfolio_returns.std() * np.sqrt(252)
388
+ sharpe_ratio = annual_return / annual_volatility if annual_volatility != 0 else 0
389
+
390
+ # Risk metrics
391
+ var_95 = np.percentile(portfolio_returns, 5)
392
+ max_drawdown = self.calculate_max_drawdown(portfolio_returns)
393
+
394
+ return {
395
+ 'Annual Return (%)': round(annual_return * 100, 2),
396
+ 'Annual Volatility (%)': round(annual_volatility * 100, 2),
397
+ 'Sharpe Ratio': round(sharpe_ratio, 3),
398
+ 'VaR (95%)': round(var_95 * 100, 2),
399
+ 'Max Drawdown (%)': round(max_drawdown * 100, 2)
400
+ }
401
+
402
+ except Exception as e:
403
+ return {"error": f"Error calculating portfolio metrics: {str(e)}"}
404
+
405
+ def calculate_max_drawdown(self, returns: pd.Series) -> float:
406
+ """Calculate maximum drawdown"""
407
+ cumulative = (1 + returns).cumprod()
408
+ rolling_max = cumulative.expanding().max()
409
+ drawdowns = (cumulative - rolling_max) / rolling_max
410
+ return drawdowns.min()
411
+
412
+ # Initialize the RAG system
413
+ rag_system = MutualFundRAG()
414
+
415
+ def initialize_system():
416
+ """Initialize the complete system"""
417
+ try:
418
+ # Initialize LLM
419
+ llm_status = rag_system.initialize_llm()
420
+
421
+ # Fetch market indicators
422
+ rag_system.get_market_indicators()
423
+
424
+ return llm_status
425
+ except Exception as e:
426
+ return f"❌ Error initializing system: {str(e)}"
427
+
428
+ def fetch_data_interface(include_extended: bool = False):
429
+ """Interface function to fetch fund data"""
430
+ try:
431
+ tickers = rag_system.fund_tickers + (rag_system.extended_tickers if include_extended else [])
432
+ df = rag_system.fetch_fund_data(tickers)
433
+
434
+ if df.empty:
435
+ return "❌ No data fetched. Please check your internet connection.", None
436
+
437
+ # Setup RAG system after fetching data
438
+ rag_status = rag_system.setup_rag_system()
439
+
440
+ status = f"βœ… Successfully fetched data for {len(df)} funds\n{rag_status}"
441
+ return status, df
442
+
443
+ except Exception as e:
444
+ return f"❌ Error fetching data: {str(e)}", None
445
+
446
+ def get_ai_recommendation_interface(user_query: str, risk_tolerance: str, investment_amount: float, horizon: int):
447
+ """Interface function for AI recommendations"""
448
+ try:
449
+ if not user_query.strip():
450
+ return "❌ Please enter a question about your investment needs."
451
+
452
+ # Update user profile
453
+ rag_system.user_profile.update({
454
+ 'risk_tolerance': risk_tolerance.lower(),
455
+ 'investment_amount': investment_amount,
456
+ 'investment_horizon': horizon
457
+ })
458
+
459
+ # Get AI recommendations
460
+ recommendation = rag_system.get_ai_recommendations(user_query)
461
+
462
+ return recommendation
463
+
464
+ except Exception as e:
465
+ return f"❌ Error getting AI recommendations: {str(e)}"
466
+
467
+ def calculate_metrics_interface(selected_funds_text: str, weights_text: str):
468
+ """Interface function to calculate portfolio metrics"""
469
+ try:
470
+ if not selected_funds_text.strip() or not weights_text.strip():
471
+ return "Please provide both fund tickers and weights"
472
+
473
+ # Parse inputs
474
+ selected_funds = [ticker.strip().upper() for ticker in selected_funds_text.split(',')]
475
+ weights = [float(w.strip()) for w in weights_text.split(',')]
476
+
477
+ if len(selected_funds) != len(weights):
478
+ return "Number of funds and weights must match"
479
+
480
+ metrics = rag_system.calculate_portfolio_metrics(selected_funds, weights)
481
+
482
+ if 'error' in metrics:
483
+ return metrics['error']
484
+
485
+ # Format metrics for display
486
+ formatted_metrics = "\n".join([f"{key}: {value}" for key, value in metrics.items()])
487
+ return f"πŸ“Š Portfolio Metrics:\n\n{formatted_metrics}"
488
+
489
+ except Exception as e:
490
+ return f"❌ Error calculating metrics: {str(e)}"
491
+
492
+ # Initialize system on startup
493
+ print("πŸš€ Initializing Mutual Fund RAG System...")
494
+ init_status = initialize_system()
495
+ print(init_status)
496
+
497
+ # Create the Gradio interface
498
+ with gr.Blocks(title="AI-Powered Mutual Fund Optimizer", theme="default") as app:
499
+
500
+ gr.Markdown("""
501
+ # πŸ€– AI-Powered Mutual Fund Portfolio Optimizer
502
+
503
+ Get personalized investment recommendations using real Yahoo Finance data and advanced AI analysis.
504
+ """)
505
+
506
+ with gr.Tabs():
507
+
508
+ # Data Fetching Tab
509
+ with gr.Tab("πŸ“Š Fund Data"):
510
+ gr.Markdown("### Fetch Real-Time Mutual Fund Data")
511
+
512
+ with gr.Row():
513
+ with gr.Column():
514
+ include_extended = gr.Checkbox(
515
+ label="Include Extended Fund List",
516
+ value=False,
517
+ info="Include additional ETFs and funds"
518
+ )
519
+ fetch_btn = gr.Button("πŸ”„ Fetch Fund Data", variant="primary")
520
+
521
+ with gr.Column():
522
+ fetch_status = gr.Textbox(
523
+ label="Status",
524
+ interactive=False,
525
+ placeholder="Click 'Fetch Fund Data' to start",
526
+ lines=3
527
+ )
528
+
529
+ fund_data_display = gr.Dataframe(
530
+ label="πŸ“‹ Available Mutual Funds",
531
+ interactive=False,
532
+ wrap=True
533
+ )
534
+
535
+ fetch_btn.click(
536
+ fn=fetch_data_interface,
537
+ inputs=[include_extended],
538
+ outputs=[fetch_status, fund_data_display]
539
+ )
540
+
541
+ # AI Recommendations Tab
542
+ with gr.Tab("πŸ€– AI Investment Advisor"):
543
+ gr.Markdown("### Get Personalized AI Investment Recommendations")
544
+
545
+ with gr.Row():
546
+ with gr.Column():
547
+ user_query = gr.Textbox(
548
+ label="Your Investment Question",
549
+ placeholder="e.g., 'I want to invest $50,000 for retirement in 20 years with moderate risk'",
550
+ lines=3,
551
+ info="Ask about portfolio allocation, fund selection, or investment strategy"
552
+ )
553
+
554
+ with gr.Row():
555
+ risk_tolerance = gr.Radio(
556
+ choices=["Conservative", "Moderate", "Aggressive"],
557
+ label="Risk Tolerance",
558
+ value="Moderate"
559
+ )
560
+
561
+ investment_amount = gr.Number(
562
+ label="Investment Amount ($)",
563
+ value=50000,
564
+ minimum=1000
565
+ )
566
+
567
+ investment_horizon = gr.Slider(
568
+ label="Investment Horizon (Years)",
569
+ minimum=1,
570
+ maximum=30,
571
+ value=5,
572
+ step=1
573
+ )
574
+
575
+ get_recommendation_btn = gr.Button("🧠 Get AI Recommendation", variant="primary")
576
+
577
+ with gr.Column():
578
+ ai_recommendation = gr.Textbox(
579
+ label="πŸ’‘ AI Investment Recommendation",
580
+ interactive=False,
581
+ lines=15,
582
+ placeholder="AI recommendations will appear here..."
583
+ )
584
+
585
+ # Example questions
586
+ gr.Markdown("### πŸ’‘ Example Questions:")
587
+ with gr.Row():
588
+ example1 = gr.Button("Conservative portfolio for retirement", size="sm")
589
+ example2 = gr.Button("Growth-focused portfolio for young investor", size="sm")
590
+ example3 = gr.Button("Balanced portfolio with international exposure", size="sm")
591
+
592
+ # Connect example buttons
593
+ example1.click(
594
+ lambda: "I'm 55 years old and want a conservative portfolio for retirement in 10 years. What funds should I choose?",
595
+ outputs=[user_query]
596
+ )
597
+ example2.click(
598
+ lambda: "I'm 25 years old and want an aggressive growth portfolio for long-term wealth building. What's your recommendation?",
599
+ outputs=[user_query]
600
+ )
601
+ example3.click(
602
+ lambda: "I want a balanced portfolio with both US and international exposure. What allocation do you recommend?",
603
+ outputs=[user_query]
604
+ )
605
+
606
+ get_recommendation_btn.click(
607
+ fn=get_ai_recommendation_interface,
608
+ inputs=[user_query, risk_tolerance, investment_amount, investment_horizon],
609
+ outputs=[ai_recommendation]
610
+ )
611
+
612
+ # Portfolio Analysis Tab
613
+ with gr.Tab("πŸ“ˆ Portfolio Analysis"):
614
+ gr.Markdown("### Analyze Custom Portfolio Metrics")
615
+
616
+ with gr.Row():
617
+ with gr.Column():
618
+ gr.Markdown("**Enter your fund selection:**")
619
+ custom_funds = gr.Textbox(
620
+ label="Fund Tickers",
621
+ placeholder="e.g., VTSAX, VTIAX, VBTLX",
622
+ info="Comma-separated list of fund tickers"
623
+ )
624
+
625
+ custom_weights = gr.Textbox(
626
+ label="Allocation Weights",
627
+ placeholder="e.g., 50, 30, 20",
628
+ info="Comma-separated percentages (should sum to 100)"
629
+ )
630
+
631
+ analyze_btn = gr.Button("πŸ“Š Calculate Metrics", variant="primary")
632
+
633
+ with gr.Column():
634
+ metrics_output = gr.Textbox(
635
+ label="Portfolio Metrics",
636
+ interactive=False,
637
+ lines=10,
638
+ placeholder="Enter fund tickers and weights, then click 'Calculate Metrics'"
639
+ )
640
+
641
+ analyze_btn.click(
642
+ fn=calculate_metrics_interface,
643
+ inputs=[custom_funds, custom_weights],
644
+ outputs=[metrics_output]
645
+ )
646
+
647
+ # System Status Tab
648
+ with gr.Tab("βš™οΈ System Status"):
649
+ gr.Markdown("### AI System Status and Information")
650
+
651
+ with gr.Row():
652
+ with gr.Column():
653
+ system_status = gr.Textbox(
654
+ label="πŸ€– AI System Status",
655
+ value=init_status,
656
+ interactive=False,
657
+ lines=3
658
+ )
659
+
660
+ market_indicators = gr.JSON(
661
+ label="πŸ“Š Current Market Indicators",
662
+ value=rag_system.market_indicators
663
+ )
664
+
665
+ with gr.Column():
666
+ gr.Markdown("""
667
+ ### 🧠 AI Capabilities
668
+
669
+ **LLM Model**: Microsoft DialoGPT
670
+ **Embeddings**: Sentence Transformers
671
+ **Vector Database**: ChromaDB
672
+ **Data Source**: Yahoo Finance
673
+
674
+ **What the AI can help with:**
675
+ - Personalized portfolio recommendations
676
+ - Risk assessment and analysis
677
+ - Fund selection based on your goals
678
+ - Market-aware investment strategies
679
+ - Sector allocation suggestions
680
+ """)
681
+
682
+ refresh_status_btn = gr.Button("πŸ”„ Refresh Status", variant="secondary")
683
+
684
+ def refresh_system_status():
685
+ rag_system.get_market_indicators()
686
+ return "βœ… System operational", rag_system.market_indicators
687
+
688
+ refresh_status_btn.click(
689
+ fn=refresh_system_status,
690
+ outputs=[system_status, market_indicators]
691
+ )
692
+
693
+ # User Guide Tab
694
+ with gr.Tab("πŸ“– User Guide"):
695
+ gr.Markdown("""
696
+ ## How to Use the AI-Powered Mutual Fund Optimizer
697
+
698
+ ### 1. πŸ“Š **Setup Data**
699
+ - Go to "Fund Data" tab and click "Fetch Fund Data"
700
+ - This loads real-time data and initializes the AI system
701
+ - Review available funds and their characteristics
702
+
703
+ ### 2. πŸ€– **Get AI Recommendations**
704
+ - Use the "AI Investment Advisor" tab
705
+ - Describe your investment goals and situation
706
+ - Set your risk tolerance and investment parameters
707
+ - Get personalized AI-powered recommendations
708
+
709
+ ### 3. πŸ“ˆ **Analyze Portfolios**
710
+ - Use "Portfolio Analysis" for custom calculations
711
+ - Enter specific fund combinations and weights
712
+ - Get detailed risk and return metrics
713
+
714
+ ### πŸ€– **AI System Architecture**
715
+
716
+ **RAG (Retrieval-Augmented Generation)**:
717
+ - Real fund data stored in vector database
718
+ - AI retrieves relevant information for your query
719
+ - Generates contextual recommendations
720
+
721
+ **Components**:
722
+ - **LLM**: Language model for generating advice
723
+ - **Embeddings**: Convert fund data to vectors
724
+ - **Vector Database**: ChromaDB for similarity search
725
+ - **Real Data**: Live Yahoo Finance integration
726
+
727
+ ### πŸ’‘ **Sample Queries**
728
+
729
+ - "I have $100k to invest for 15 years, what's the best allocation?"
730
+ - "Compare growth vs value funds for my situation"
731
+ - "Should I include international funds in my portfolio?"
732
+ - "What's the optimal bond allocation for a 40-year-old?"
733
+ - "How should I adjust my portfolio during market volatility?"
734
+
735
+ ### ⚠️ **Important Notes**
736
+
737
+ - AI recommendations are for educational purposes
738
+ - Always verify suggestions with financial advisors
739
+ - Past performance doesn't guarantee future results
740
+ - Consider your complete financial situation
741
+ - The AI learns from real fund data and market conditions
742
+
743
+ ### πŸ”§ **Technical Details**
744
+
745
+ - **Data Source**: Yahoo Finance API
746
+ - **AI Model**: Microsoft DialoGPT (can be upgraded)
747
+ - **Embeddings**: Sentence Transformers all-MiniLM-L6-v2
748
+ - **Vector DB**: ChromaDB with persistent storage
749
+ - **Update Frequency**: Real-time when data is refreshed
750
+ """)
751
+
752
+ # Footer
753
+ gr.Markdown("""
754
+ ---
755
+ **πŸ€– AI-Powered**: This system uses advanced AI to analyze real market data and provide personalized investment recommendations.
756
+
757
+ **⚠️ Disclaimer**: AI recommendations are for educational purposes only. Always consult with qualified financial advisors before making investment decisions.
758
+ """)
759
+
760
+ # Launch the app
761
+ if __name__ == "__main__":
762
+ print("πŸš€ Starting AI-Powered Mutual Fund Portfolio Optimizer...")
763
+ print("πŸ€– LLM and RAG system initialized")
764
+ print("πŸ“Š Real-time Yahoo Finance data integration enabled")
765
+ print("🧠 AI investment advisor ready")
766
+
767
+ app.launch(
768
+ share=True,
769
+ server_name="0.0.0.0",
770
+ show_error=True,
771
+ )