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, {}) |