mutual-fund / app /services /portfolio_analyzer.py
lucifer7210's picture
Upload 21 files
eb606e1 verified
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"""
@staticmethod
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)
@staticmethod
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
)
@staticmethod
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
)
@staticmethod
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, {})