Abid Ali Awan commited on
Commit
27965d3
·
1 Parent(s): 0a88725

Refactor portfolio analyzer in FinancialTools: simplified input extraction and handling, improved default portfolio logic, and enhanced analysis output with basic recommendations for diversification.

Browse files
Files changed (3) hide show
  1. agents/financial_agent.py +1 -26
  2. agents/tools.py +48 -71
  3. app.py +1 -1
agents/financial_agent.py CHANGED
@@ -113,32 +113,7 @@ When a user asks a question:
113
  return json.dumps({"income": income, "expenses": expenses})
114
 
115
  elif tool_name == "portfolio_analyzer":
116
- # Try to extract portfolio data from message
117
- import re
118
-
119
- # Look for JSON-like structure in message
120
- json_match = re.search(r'\{.*\}|\[.*\]', message, re.DOTALL)
121
- if json_match:
122
- try:
123
- # Try to parse the extracted JSON
124
- potential_json = json_match.group(0)
125
- parsed_data = json.loads(potential_json)
126
- return json.dumps(parsed_data)
127
- except:
128
- pass
129
-
130
- # Look for simple symbol mentions like "AAPL 100 shares, GOOGL 50 shares"
131
- symbol_pattern = r'([A-Z]{2,5})\s+(\d+)\s*(?:shares?)?'
132
- matches = re.findall(symbol_pattern, message, re.I)
133
-
134
- if matches:
135
- holdings = []
136
- for symbol, shares in matches:
137
- holdings.append({"symbol": symbol.upper(), "shares": int(shares)})
138
- return json.dumps({"holdings": holdings})
139
-
140
- # Default portfolio for demo
141
- return json.dumps({"holdings": [{"symbol": "AAPL", "shares": 100}, {"symbol": "GOOGL", "shares": 50}]})
142
 
143
  elif tool_name == "market_trends":
144
  return message
 
113
  return json.dumps({"income": income, "expenses": expenses})
114
 
115
  elif tool_name == "portfolio_analyzer":
116
+ return message
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
  elif tool_name == "market_trends":
119
  return message
agents/tools.py CHANGED
@@ -465,105 +465,82 @@ class FinancialTools:
465
  def portfolio_analyzer(input_str: str) -> str:
466
  """Analyze portfolio performance and diversification"""
467
  try:
468
- data = json.loads(input_str)
 
469
 
470
- # Handle different input formats
471
- if isinstance(data, list):
472
- # Input is directly a list of holdings
473
- holdings = data
474
- elif isinstance(data, dict):
475
- # Input is a dict with 'holdings' key
476
- holdings = data.get("holdings", [])
477
- else:
478
- holdings = []
 
 
 
 
 
 
 
 
 
 
 
479
 
480
  total_value = 0
481
  portfolio_data = []
482
 
 
483
  for holding in holdings:
484
- # Handle different holding formats
485
- if isinstance(holding, dict):
486
- symbol = holding.get("symbol", "")
487
- shares = holding.get("shares", 0)
488
- else:
489
- # Skip invalid holdings
490
- continue
491
 
492
  if not symbol:
493
  continue
494
 
495
  try:
 
496
  stock = yf.Ticker(symbol)
497
- info = stock.info
498
- current_price = info.get("currentPrice", 0)
499
 
500
- # Fallback for current price if not available
501
- if current_price == 0:
502
- hist = stock.history(period="1d")
503
- if not hist.empty:
504
- current_price = hist["Close"].iloc[-1]
505
-
506
- value = current_price * shares
507
- total_value += value
508
 
509
- portfolio_data.append(
510
- {
511
  "symbol": symbol,
512
  "shares": shares,
513
- "current_price": current_price,
514
  "value": value,
515
- "sector": info.get("sector", "Unknown"),
516
- }
517
- )
518
- except Exception as e:
519
- # Handle individual stock errors gracefully
520
- portfolio_data.append(
521
- {
522
- "symbol": symbol,
523
- "shares": shares,
524
- "current_price": 0,
525
- "value": 0,
526
- "sector": "Unknown",
527
- "error": f"Failed to fetch data: {str(e)}"
528
- }
529
- )
530
 
531
  # Calculate allocations
532
  for item in portfolio_data:
533
- item["allocation"] = (
534
- (item["value"] / total_value * 100) if total_value > 0 else 0
535
- )
536
-
537
- # Sector diversification
538
- df = pd.DataFrame(portfolio_data)
539
- # Handle case where portfolio_data is empty or sector column has issues
540
- if not df.empty and "sector" in df.columns:
541
- sector_allocation = df.groupby("sector")["allocation"].sum().to_dict()
542
- else:
543
- sector_allocation = {}
544
 
 
545
  analysis = {
546
  "total_portfolio_value": f"${total_value:.2f}",
 
547
  "holdings": portfolio_data,
548
- "sector_allocation": sector_allocation,
549
- "diversification_score": len(sector_allocation)
550
- / 11
551
- * 100, # 11 major sectors
552
- "recommendations": [],
553
  }
554
 
555
- # Add recommendations
556
- if len(holdings) < 5:
557
- analysis["recommendations"].append(
558
- "Consider diversifying with more holdings"
559
- )
560
-
561
  if portfolio_data:
562
  max_allocation = max(item["allocation"] for item in portfolio_data)
563
  if max_allocation > 30:
564
- analysis["recommendations"].append(
565
- f"High concentration risk: largest holding is {max_allocation:.1f}%"
566
- )
567
 
568
  return json.dumps(analysis, indent=2)
569
 
@@ -572,7 +549,7 @@ class FinancialTools:
572
 
573
  return Tool(
574
  name="portfolio_analyzer",
575
- description="Analyze portfolio performance and diversification",
576
  func=portfolio_analyzer,
577
  )
578
 
 
465
  def portfolio_analyzer(input_str: str) -> str:
466
  """Analyze portfolio performance and diversification"""
467
  try:
468
+ # Simple extraction from user message
469
+ import re
470
 
471
+ # Look for JSON in the input
472
+ json_match = re.search(r'\{.*\}|\[.*\]', input_str, re.DOTALL)
473
+ holdings = []
474
+
475
+ if json_match:
476
+ try:
477
+ data = json.loads(json_match.group(0))
478
+ if isinstance(data, list):
479
+ holdings = data
480
+ elif isinstance(data, dict) and "holdings" in data:
481
+ holdings = data["holdings"]
482
+ except:
483
+ pass
484
+
485
+ # If no JSON found, use default example
486
+ if not holdings:
487
+ holdings = [
488
+ {"symbol": "AAPL", "shares": 100},
489
+ {"symbol": "GOOGL", "shares": 50}
490
+ ]
491
 
492
  total_value = 0
493
  portfolio_data = []
494
 
495
+ # Fetch data for each holding
496
  for holding in holdings:
497
+ symbol = holding.get("symbol", "")
498
+ shares = holding.get("shares", 0)
 
 
 
 
 
499
 
500
  if not symbol:
501
  continue
502
 
503
  try:
504
+ # Simple yfinance call when needed
505
  stock = yf.Ticker(symbol)
506
+ hist = stock.history(period="1d")
 
507
 
508
+ if not hist.empty:
509
+ current_price = hist["Close"].iloc[-1]
510
+ value = current_price * shares
511
+ total_value += value
 
 
 
 
512
 
513
+ portfolio_data.append({
 
514
  "symbol": symbol,
515
  "shares": shares,
516
+ "current_price": f"${current_price:.2f}",
517
  "value": value,
518
+ "allocation": 0 # Will calculate after
519
+ })
520
+ except:
521
+ # Skip if can't get data
522
+ continue
 
 
 
 
 
 
 
 
 
 
523
 
524
  # Calculate allocations
525
  for item in portfolio_data:
526
+ item["allocation"] = (item["value"] / total_value * 100) if total_value > 0 else 0
 
 
 
 
 
 
 
 
 
 
527
 
528
+ # Simple analysis
529
  analysis = {
530
  "total_portfolio_value": f"${total_value:.2f}",
531
+ "number_of_holdings": len(portfolio_data),
532
  "holdings": portfolio_data,
533
+ "recommendations": []
 
 
 
 
534
  }
535
 
536
+ # Basic recommendations
537
+ if len(portfolio_data) < 5:
538
+ analysis["recommendations"].append("Consider diversifying with more holdings")
539
+
 
 
540
  if portfolio_data:
541
  max_allocation = max(item["allocation"] for item in portfolio_data)
542
  if max_allocation > 30:
543
+ analysis["recommendations"].append(f"High concentration risk: largest holding is {max_allocation:.1f}%")
 
 
544
 
545
  return json.dumps(analysis, indent=2)
546
 
 
549
 
550
  return Tool(
551
  name="portfolio_analyzer",
552
+ description="Analyze portfolio performance and diversification. Input should include holdings like: [{'symbol': 'AAPL', 'shares': 100}]",
553
  func=portfolio_analyzer,
554
  )
555
 
app.py CHANGED
@@ -675,7 +675,7 @@ def process_financial_query(message, history):
675
 
676
  # Create the Gradio interface
677
  with gr.Blocks(theme=gr.themes.Base(), title="Financial Advisory Agent") as demo:
678
- gr.Markdown("""<center><img src="/gradio_api/file=public/images/fin_logo.png" alt="Fin Logo" style="width: 50px; vertical-align: middle;">
679
  <h1 style="text-align: center;">AI Financial Advisory Agent</h1>
680
  Your AI-powered financial advisor for budgeting, investments, expense tracking, portfolio analysis, and market trends.
681
  </center>
 
675
 
676
  # Create the Gradio interface
677
  with gr.Blocks(theme=gr.themes.Base(), title="Financial Advisory Agent") as demo:
678
+ gr.HTML("""<center><img src="/gradio_api/file=public/images/fin_logo.png" alt="Fin Logo" style="width: 50px; vertical-align: middle;">
679
  <h1 style="text-align: center;">AI Financial Advisory Agent</h1>
680
  Your AI-powered financial advisor for budgeting, investments, expense tracking, portfolio analysis, and market trends.
681
  </center>