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- agents/financial_agent.py +1 -26
- agents/tools.py +48 -71
- 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 |
-
|
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 |
-
|
|
|
469 |
|
470 |
-
#
|
471 |
-
|
472 |
-
|
473 |
-
|
474 |
-
|
475 |
-
|
476 |
-
|
477 |
-
|
478 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
479 |
|
480 |
total_value = 0
|
481 |
portfolio_data = []
|
482 |
|
|
|
483 |
for holding in holdings:
|
484 |
-
|
485 |
-
|
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 |
-
|
498 |
-
current_price = info.get("currentPrice", 0)
|
499 |
|
500 |
-
|
501 |
-
|
502 |
-
|
503 |
-
|
504 |
-
current_price = hist["Close"].iloc[-1]
|
505 |
-
|
506 |
-
value = current_price * shares
|
507 |
-
total_value += value
|
508 |
|
509 |
-
|
510 |
-
{
|
511 |
"symbol": symbol,
|
512 |
"shares": shares,
|
513 |
-
"current_price": current_price,
|
514 |
"value": value,
|
515 |
-
"
|
516 |
-
}
|
517 |
-
|
518 |
-
|
519 |
-
|
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 |
-
"
|
549 |
-
"diversification_score": len(sector_allocation)
|
550 |
-
/ 11
|
551 |
-
* 100, # 11 major sectors
|
552 |
-
"recommendations": [],
|
553 |
}
|
554 |
|
555 |
-
#
|
556 |
-
if len(
|
557 |
-
analysis["recommendations"].append(
|
558 |
-
|
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.
|
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>
|