Spaces:
Running
Running
import numpy as np | |
import pandas as pd | |
from typing import Dict, Any, List, Tuple, Optional | |
from app.models.portfolio_models import ( | |
Portfolio, PortfolioMetrics, PortfolioTemplate, | |
RebalanceAction, RebalanceAnalysis, PerformanceReport | |
) | |
class PortfolioAnalyzer: | |
"""Analyze mutual fund portfolio performance and allocation""" | |
def calculate_portfolio_metrics(portfolio_holdings: Dict[str, Any]) -> PortfolioMetrics: | |
"""Calculate portfolio-level metrics""" | |
if not portfolio_holdings: | |
return PortfolioMetrics( | |
total_value=0, | |
total_invested=0, | |
total_gains=0, | |
category_allocation={} | |
) | |
total_value = sum(holding.current_value for holding in portfolio_holdings.values()) | |
metrics = { | |
'total_value': total_value, | |
'total_invested': sum(holding.invested_amount for holding in portfolio_holdings.values()), | |
'total_gains': total_value - sum(holding.invested_amount for holding in portfolio_holdings.values()), | |
'category_allocation': {} | |
} | |
# Calculate category allocation | |
for fund_name, holding in portfolio_holdings.items(): | |
category = holding.category if hasattr(holding, 'category') else 'Other' | |
if category not in metrics['category_allocation']: | |
metrics['category_allocation'][category] = 0 | |
metrics['category_allocation'][category] += holding.current_value | |
# Convert to percentages | |
for category in metrics['category_allocation']: | |
metrics['category_allocation'][category] = ( | |
metrics['category_allocation'][category] / total_value | |
) * 100 if total_value > 0 else 0 | |
return PortfolioMetrics(**metrics) | |
def generate_rebalance_analysis(portfolio: Dict[str, Any]) -> RebalanceAnalysis: | |
"""Generate detailed rebalancing analysis and recommendations""" | |
if not portfolio: | |
return RebalanceAnalysis( | |
actions=[], | |
recommended_strategy="No portfolio data available", | |
target_allocation={} | |
) | |
portfolio_metrics = PortfolioAnalyzer.calculate_portfolio_metrics(portfolio) | |
current_allocation = portfolio_metrics.category_allocation | |
# Define target allocations based on common strategies | |
target_allocations = { | |
'Conservative': { | |
'Large Cap Equity': 30, 'Debt Funds': 40, 'Hybrid Funds': 25, | |
'ELSS (Tax Saving)': 5, 'Mid Cap Equity': 0, 'Small Cap Equity': 0 | |
}, | |
'Balanced': { | |
'Large Cap Equity': 40, 'Mid Cap Equity': 25, 'ELSS (Tax Saving)': 15, | |
'Debt Funds': 15, 'Hybrid Funds': 5, 'Small Cap Equity': 0 | |
}, | |
'Aggressive': { | |
'Large Cap Equity': 35, 'Mid Cap Equity': 30, 'Small Cap Equity': 25, | |
'ELSS (Tax Saving)': 10, 'Debt Funds': 0, 'Hybrid Funds': 0 | |
} | |
} | |
# Determine closest target allocation | |
total_equity = (current_allocation.get('Large Cap Equity', 0) + | |
current_allocation.get('Mid Cap Equity', 0) + | |
current_allocation.get('Small Cap Equity', 0)) | |
if total_equity >= 70: | |
recommended_strategy = 'Aggressive' | |
elif total_equity >= 45: | |
recommended_strategy = 'Balanced' | |
else: | |
recommended_strategy = 'Conservative' | |
target = target_allocations[recommended_strategy] | |
total_portfolio_value = portfolio_metrics.total_value | |
# Calculate rebalancing requirements | |
rebalance_actions = [] | |
for category in target: | |
current_pct = current_allocation.get(category, 0) | |
target_pct = target[category] | |
difference = target_pct - current_pct | |
if abs(difference) > 5: # Only suggest rebalancing if difference > 5% | |
current_value = (current_pct / 100) * total_portfolio_value | |
target_value = (target_pct / 100) * total_portfolio_value | |
amount_change = target_value - current_value | |
action = "INCREASE" if difference > 0 else "DECREASE" | |
rebalance_actions.append(RebalanceAction( | |
category=category, | |
current_pct=current_pct, | |
target_pct=target_pct, | |
difference=difference, | |
amount_change=amount_change, | |
action=action | |
)) | |
return RebalanceAnalysis( | |
actions=rebalance_actions, | |
recommended_strategy=recommended_strategy, | |
target_allocation=target | |
) | |
def generate_performance_report(portfolio: Dict[str, Any]) -> PerformanceReport: | |
"""Generate comprehensive performance report""" | |
if not portfolio: | |
return PerformanceReport( | |
total_invested=0, | |
total_value=0, | |
total_gains=0, | |
overall_return_pct=0, | |
fund_performance=[], | |
max_return=0, | |
min_return=0, | |
volatility=0, | |
sharpe_ratio=0, | |
portfolio_metrics=PortfolioMetrics( | |
total_value=0, | |
total_invested=0, | |
total_gains=0, | |
category_allocation={} | |
) | |
) | |
portfolio_metrics = PortfolioAnalyzer.calculate_portfolio_metrics(portfolio) | |
# Calculate performance metrics | |
total_invested = portfolio_metrics.total_invested | |
total_value = portfolio_metrics.total_value | |
total_gains = portfolio_metrics.total_gains | |
if total_invested > 0: | |
overall_return_pct = (total_gains / total_invested) * 100 | |
else: | |
overall_return_pct = 0 | |
# Fund-wise performance | |
fund_performance = [] | |
best_performer = None | |
worst_performer = None | |
max_return = float('-inf') | |
min_return = float('inf') | |
for fund_name, holding in portfolio.items(): | |
invested = holding.invested_amount | |
current = holding.current_value | |
gain_loss = current - invested | |
return_pct = (gain_loss / invested * 100) if invested > 0 else 0 | |
fund_performance.append({ | |
'fund': fund_name, | |
'category': holding.category if hasattr(holding, 'category') else 'Other', | |
'invested': invested, | |
'current_value': current, | |
'gain_loss': gain_loss, | |
'return_pct': return_pct | |
}) | |
if return_pct > max_return: | |
max_return = return_pct | |
best_performer = fund_name | |
if return_pct < min_return: | |
min_return = return_pct | |
worst_performer = fund_name | |
# Risk metrics (simplified) | |
returns = [perf['return_pct'] for perf in fund_performance] | |
if len(returns) > 1: | |
volatility = np.std(returns) | |
sharpe_ratio = (np.mean(returns) - 6) / volatility if volatility > 0 else 0 # Assuming 6% risk-free rate | |
else: | |
volatility = 0 | |
sharpe_ratio = 0 | |
return PerformanceReport( | |
total_invested=total_invested, | |
total_value=total_value, | |
total_gains=total_gains, | |
overall_return_pct=overall_return_pct, | |
fund_performance=fund_performance, | |
best_performer=best_performer, | |
worst_performer=worst_performer, | |
max_return=max_return, | |
min_return=min_return, | |
volatility=volatility, | |
sharpe_ratio=sharpe_ratio, | |
portfolio_metrics=portfolio_metrics | |
) | |
def get_template_portfolio(template: PortfolioTemplate) -> Dict[str, Any]: | |
"""Get a predefined portfolio template""" | |
templates = { | |
PortfolioTemplate.CONSERVATIVE: { | |
'HDFC Corporate Bond Fund': { | |
'scheme_code': '101762', 'category': 'Debt Funds', | |
'invested_amount': 40000, 'current_value': 42000, 'units': 800, | |
'investment_type': 'SIP (Monthly)' | |
}, | |
'HDFC Top 100 Fund': { | |
'scheme_code': '120503', 'category': 'Large Cap Equity', | |
'invested_amount': 30000, 'current_value': 33000, 'units': 600, | |
'investment_type': 'SIP (Monthly)' | |
}, | |
'HDFC Hybrid Equity Fund': { | |
'scheme_code': '118551', 'category': 'Hybrid Funds', | |
'invested_amount': 30000, 'current_value': 32000, 'units': 600, | |
'investment_type': 'SIP (Monthly)' | |
} | |
}, | |
PortfolioTemplate.BALANCED: { | |
'HDFC Top 100 Fund': { | |
'scheme_code': '120503', 'category': 'Large Cap Equity', | |
'invested_amount': 40000, 'current_value': 45000, 'units': 800, | |
'investment_type': 'SIP (Monthly)' | |
}, | |
'ICICI Pru Mid Cap Fund': { | |
'scheme_code': '120544', 'category': 'Mid Cap Equity', | |
'invested_amount': 30000, 'current_value': 36000, 'units': 400, | |
'investment_type': 'SIP (Monthly)' | |
}, | |
'HDFC Tax Saver': { | |
'scheme_code': '100277', 'category': 'ELSS (Tax Saving)', | |
'invested_amount': 20000, 'current_value': 23000, 'units': 400, | |
'investment_type': 'SIP (Monthly)' | |
}, | |
'HDFC Corporate Bond Fund': { | |
'scheme_code': '101762', 'category': 'Debt Funds', | |
'invested_amount': 10000, 'current_value': 10500, 'units': 200, | |
'investment_type': 'Lump Sum' | |
} | |
}, | |
PortfolioTemplate.AGGRESSIVE: { | |
'SBI Small Cap Fund': { | |
'scheme_code': '122639', 'category': 'Small Cap Equity', | |
'invested_amount': 30000, 'current_value': 38000, 'units': 500, | |
'investment_type': 'SIP (Monthly)' | |
}, | |
'ICICI Pru Mid Cap Fund': { | |
'scheme_code': '120544', 'category': 'Mid Cap Equity', | |
'invested_amount': 30000, 'current_value': 36000, 'units': 400, | |
'investment_type': 'SIP (Monthly)' | |
}, | |
'HDFC Top 100 Fund': { | |
'scheme_code': '120503', 'category': 'Large Cap Equity', | |
'invested_amount': 25000, 'current_value': 28000, 'units': 500, | |
'investment_type': 'SIP (Monthly)' | |
}, | |
'Kotak Emerging Equity Fund': { | |
'scheme_code': '118999', 'category': 'Mid Cap Equity', | |
'invested_amount': 15000, 'current_value': 18000, 'units': 200, | |
'investment_type': 'SIP (Monthly)' | |
} | |
}, | |
PortfolioTemplate.CUSTOM_SAMPLE: { | |
'HDFC Balanced Advantage Fund': { | |
'scheme_code': '104248', 'category': 'Hybrid Funds', | |
'invested_amount': 30000, 'current_value': 34000, 'units': 600, | |
'investment_type': 'SIP (Monthly)' | |
}, | |
'HDFC Top 100 Fund': { | |
'scheme_code': '120503', 'category': 'Large Cap Equity', | |
'invested_amount': 30000, 'current_value': 33000, 'units': 600, | |
'investment_type': 'SIP (Monthly)' | |
}, | |
'HDFC Corporate Bond Fund': { | |
'scheme_code': '101762', 'category': 'Debt Funds', | |
'invested_amount': 20000, 'current_value': 21000, 'units': 400, | |
'investment_type': 'Lump Sum' | |
}, | |
'HDFC Tax Saver': { | |
'scheme_code': '100277', 'category': 'ELSS (Tax Saving)', | |
'invested_amount': 20000, 'current_value': 23000, 'units': 400, | |
'investment_type': 'SIP (Monthly)' | |
} | |
} | |
} | |
return templates.get(template, {}) |