lucifer7210 commited on
Commit
eb606e1
·
verified ·
1 Parent(s): c6cc481

Upload 21 files

Browse files
app/__init__.py ADDED
File without changes
app/api/__init__.py ADDED
File without changes
app/api/endpoints/__init__.py ADDED
File without changes
app/api/endpoints/ai.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends, HTTPException
2
+ from app.services.ai_swarm import AISwarmService
3
+ from app.models.ai_models import (
4
+ AIAnalysisRequest, AIAnalysisResponse,
5
+ AnalysisFocus, MarketView, InvestmentHorizon
6
+ )
7
+
8
+ router = APIRouter()
9
+
10
+ @router.post("/analyze", response_model=AIAnalysisResponse)
11
+ async def get_ai_recommendations(request: AIAnalysisRequest):
12
+ """
13
+ Get AI-powered investment recommendations based on client profile, portfolio, and goals
14
+ """
15
+ try:
16
+ ai_service = AISwarmService()
17
+ result = ai_service.get_enhanced_analysis(request)
18
+ return result
19
+ except Exception as e:
20
+ raise HTTPException(status_code=500, detail=f"Error generating AI recommendations: {str(e)}")
21
+
22
+ @router.post("/fund-selection")
23
+ async def get_fund_selection_recommendations(
24
+ client_profile: dict,
25
+ goals_data: dict,
26
+ market_conditions: MarketView = MarketView.NEUTRAL,
27
+ investment_horizon: InvestmentHorizon = InvestmentHorizon.MEDIUM_TERM
28
+ ):
29
+ """
30
+ Get AI-powered fund selection recommendations
31
+ """
32
+ try:
33
+ ai_service = AISwarmService()
34
+
35
+ request = AIAnalysisRequest(
36
+ client_profile=client_profile,
37
+ portfolio_data={},
38
+ goals_data=goals_data,
39
+ analysis_focus=[AnalysisFocus.FUND_SELECTION],
40
+ market_conditions=market_conditions,
41
+ investment_horizon=investment_horizon
42
+ )
43
+
44
+ result = ai_service.get_enhanced_analysis(request)
45
+ return result
46
+ except Exception as e:
47
+ raise HTTPException(status_code=500, detail=f"Error generating fund selection recommendations: {str(e)}")
48
+
49
+ @router.post("/risk-assessment")
50
+ async def get_risk_assessment(
51
+ client_profile: dict,
52
+ portfolio_data: dict,
53
+ market_conditions: MarketView = MarketView.NEUTRAL
54
+ ):
55
+ """
56
+ Get AI-powered risk assessment for a portfolio
57
+ """
58
+ try:
59
+ ai_service = AISwarmService()
60
+
61
+ request = AIAnalysisRequest(
62
+ client_profile=client_profile,
63
+ portfolio_data=portfolio_data,
64
+ goals_data={},
65
+ analysis_focus=[AnalysisFocus.RISK_ASSESSMENT],
66
+ market_conditions=market_conditions,
67
+ investment_horizon=InvestmentHorizon.MEDIUM_TERM
68
+ )
69
+
70
+ result = ai_service.get_enhanced_analysis(request)
71
+ return result
72
+ except Exception as e:
73
+ raise HTTPException(status_code=500, detail=f"Error generating risk assessment: {str(e)}")
74
+
75
+ @router.post("/goal-planning")
76
+ async def get_goal_planning_recommendations(
77
+ client_profile: dict,
78
+ goals_data: dict,
79
+ investment_horizon: InvestmentHorizon = InvestmentHorizon.MEDIUM_TERM
80
+ ):
81
+ """
82
+ Get AI-powered goal planning recommendations
83
+ """
84
+ try:
85
+ ai_service = AISwarmService()
86
+
87
+ request = AIAnalysisRequest(
88
+ client_profile=client_profile,
89
+ portfolio_data={},
90
+ goals_data=goals_data,
91
+ analysis_focus=[AnalysisFocus.GOAL_ALIGNMENT],
92
+ market_conditions=MarketView.NEUTRAL,
93
+ investment_horizon=investment_horizon
94
+ )
95
+
96
+ result = ai_service.get_enhanced_analysis(request)
97
+ return result
98
+ except Exception as e:
99
+ raise HTTPException(status_code=500, detail=f"Error generating goal planning recommendations: {str(e)}")
app/api/endpoints/funds.py ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends, HTTPException, Query
2
+ from typing import List, Dict, Any, Optional
3
+ from app.services.data_fetcher import MutualFundDataFetcher
4
+ from app.services.sip_calculator import SIPCalculator
5
+ from app.models.fund_models import (
6
+ FundNAVResponse, FundAnalysisRequest, FundAnalysisResponse
7
+ )
8
+ from app.models.goal_models import (
9
+ SIPCalculationRequest, SIPCalculationResponse,
10
+ RequiredSIPRequest, RequiredSIPResponse
11
+ )
12
+
13
+ # Popular mutual fund categories with scheme codes
14
+ POPULAR_FUNDS = {
15
+ 'Large Cap Equity': {
16
+ 'HDFC Top 100 Fund': '120503',
17
+ 'ICICI Pru Bluechip Fund': '120505',
18
+ 'SBI Bluechip Fund': '125497',
19
+ 'Axis Bluechip Fund': '120503',
20
+ 'Kotak Bluechip Fund': '118989'
21
+ },
22
+ 'Mid Cap Equity': {
23
+ 'HDFC Mid-Cap Opportunities Fund': '118551',
24
+ 'ICICI Pru Mid Cap Fund': '120544',
25
+ 'Kotak Emerging Equity Fund': '118999',
26
+ 'SBI Magnum Mid Cap Fund': '100281',
27
+ 'DSP Mid Cap Fund': '112618'
28
+ },
29
+ 'Small Cap Equity': {
30
+ 'SBI Small Cap Fund': '122639',
31
+ 'DSP Small Cap Fund': '112618',
32
+ 'HDFC Small Cap Fund': '118551',
33
+ 'Axis Small Cap Fund': '125487',
34
+ 'Kotak Small Cap Fund': '119028'
35
+ },
36
+ 'ELSS (Tax Saving)': {
37
+ 'Axis Long Term Equity Fund': '125494',
38
+ 'HDFC Tax Saver': '100277',
39
+ 'SBI Tax Saver': '125497',
40
+ 'ICICI Pru ELSS Tax Saver': '120503',
41
+ 'Kotak Tax Saver': '118989'
42
+ },
43
+ 'Debt Funds': {
44
+ 'HDFC Corporate Bond Fund': '101762',
45
+ 'ICICI Pru Corporate Bond Fund': '120503',
46
+ 'SBI Corporate Bond Fund': '125497',
47
+ 'Kotak Corporate Bond Fund': '118989',
48
+ 'Axis Corporate Debt Fund': '125494'
49
+ },
50
+ 'Hybrid Funds': {
51
+ 'HDFC Hybrid Equity Fund': '118551',
52
+ 'ICICI Pru Balanced Advantage Fund': '120505',
53
+ 'SBI Equity Hybrid Fund': '125497',
54
+ 'Kotak Equity Hybrid Fund': '118999',
55
+ 'Axis Hybrid Fund': '125494'
56
+ }
57
+ }
58
+
59
+ router = APIRouter()
60
+
61
+ @router.get("/schemes")
62
+ async def get_all_schemes():
63
+ """
64
+ Get all available mutual fund schemes
65
+ """
66
+ try:
67
+ fetcher = MutualFundDataFetcher()
68
+ schemes = fetcher.get_all_schemes()
69
+ return {"schemes": schemes}
70
+ except Exception as e:
71
+ raise HTTPException(status_code=500, detail=f"Error fetching schemes: {str(e)}")
72
+
73
+ @router.get("/nav/{scheme_code}", response_model=FundNAVResponse)
74
+ async def get_fund_nav_history(scheme_code: str):
75
+ """
76
+ Get NAV history for a specific mutual fund scheme
77
+ """
78
+ try:
79
+ fetcher = MutualFundDataFetcher()
80
+ nav_data, fund_meta = fetcher.get_fund_nav_history(scheme_code)
81
+
82
+ return FundNAVResponse(
83
+ meta=fund_meta,
84
+ data=nav_data.to_dict('records')
85
+ )
86
+ except Exception as e:
87
+ raise HTTPException(status_code=500, detail=f"Error fetching NAV data: {str(e)}")
88
+
89
+ @router.get("/popular")
90
+ async def get_popular_funds():
91
+ """
92
+ Get list of popular mutual funds by category
93
+ """
94
+ return POPULAR_FUNDS
95
+
96
+ @router.post("/sip-calculate", response_model=SIPCalculationResponse)
97
+ async def calculate_sip(request: SIPCalculationRequest,
98
+ include_yearly_breakdown: bool = Query(False, description="Include yearly breakdown in response")):
99
+ """
100
+ Calculate SIP maturity amount based on monthly investment, expected return, and time period
101
+ """
102
+ try:
103
+ calculator = SIPCalculator()
104
+ result = calculator.get_sip_calculation(request, include_yearly_breakdown)
105
+ return result
106
+ except Exception as e:
107
+ raise HTTPException(status_code=500, detail=f"Error calculating SIP: {str(e)}")
108
+
109
+ @router.post("/required-sip", response_model=RequiredSIPResponse)
110
+ async def calculate_required_sip(request: RequiredSIPRequest):
111
+ """
112
+ Calculate required SIP amount to reach a target amount
113
+ """
114
+ try:
115
+ calculator = SIPCalculator()
116
+ result = calculator.get_required_sip(request)
117
+ return result
118
+ except Exception as e:
119
+ raise HTTPException(status_code=500, detail=f"Error calculating required SIP: {str(e)}")
120
+
121
+ @router.post("/analyze", response_model=FundAnalysisResponse)
122
+ async def analyze_funds(request: FundAnalysisRequest):
123
+ """
124
+ Analyze performance of selected mutual funds over a specified period
125
+ """
126
+ try:
127
+ fetcher = MutualFundDataFetcher()
128
+ calculator = SIPCalculator()
129
+
130
+ analysis_results = []
131
+
132
+ for fund_name in request.fund_names:
133
+ # Find scheme code for the fund
134
+ scheme_code = None
135
+ fund_category = None
136
+
137
+ for category, funds in POPULAR_FUNDS.items():
138
+ if fund_name in funds:
139
+ scheme_code = funds[fund_name]
140
+ fund_category = category
141
+ break
142
+
143
+ if not scheme_code:
144
+ continue
145
+
146
+ # Fetch NAV data
147
+ nav_data, fund_meta = fetcher.get_fund_nav_history(scheme_code)
148
+
149
+ if not nav_data.empty:
150
+ returns_data = calculator.calculate_fund_returns(
151
+ nav_data, request.investment_amount, request.start_date, request.end_date
152
+ )
153
+
154
+ if returns_data:
155
+ analysis_results.append({
156
+ 'fund_name': fund_name,
157
+ 'category': fund_category,
158
+ 'scheme_code': scheme_code,
159
+ 'fund_house': fund_meta.fund_house,
160
+ 'returns_data': returns_data,
161
+ 'nav_data': nav_data.to_dict('records'),
162
+ 'fund_meta': fund_meta.dict()
163
+ })
164
+
165
+ # Prepare comparison data
166
+ comparison_data = []
167
+ for result in analysis_results:
168
+ returns = result['returns_data']
169
+ comparison_data.append({
170
+ 'Fund Name': result['fund_name'],
171
+ 'Category': result['category'],
172
+ 'Fund House': result['fund_house'],
173
+ 'Investment': f"₹{returns['investment_amount']:,.0f}",
174
+ 'Current Value': f"₹{returns['final_value']:,.0f}",
175
+ 'Total Return': f"{returns['total_return']:.2f}%",
176
+ 'Absolute Gain': f"₹{returns['final_value'] - returns['investment_amount']:,.0f}"
177
+ })
178
+
179
+ return FundAnalysisResponse(
180
+ results=analysis_results,
181
+ comparison_data=comparison_data
182
+ )
183
+ except Exception as e:
184
+ raise HTTPException(status_code=500, detail=f"Error analyzing funds: {str(e)}")
app/api/endpoints/goals.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends, HTTPException
2
+ from typing import List, Dict, Any
3
+ from app.models.goal_models import (
4
+ ClientProfile, InvestmentGoal, GoalsDashboard,
5
+ SIPCalculationRequest, SIPCalculationResponse,
6
+ RequiredSIPRequest, RequiredSIPResponse, GoalsDashboardRequest
7
+ )
8
+ from app.services.sip_calculator import SIPCalculator
9
+
10
+ router = APIRouter()
11
+
12
+ @router.post("/dashboard", response_model=GoalsDashboard)
13
+ async def get_goals_dashboard(request: GoalsDashboardRequest):
14
+ """
15
+ Calculate goals dashboard metrics including total required SIP, shortfall/surplus
16
+ """
17
+ try:
18
+ total_required_sip = sum(goal.required_sip for goal in request.goals)
19
+ shortfall = max(0, total_required_sip - request.monthly_savings)
20
+ surplus = max(0, request.monthly_savings - total_required_sip)
21
+
22
+ return GoalsDashboard(
23
+ goals=request.goals,
24
+ total_required_sip=total_required_sip,
25
+ monthly_savings=request.monthly_savings,
26
+ shortfall=shortfall,
27
+ surplus=surplus
28
+ )
29
+ except Exception as e:
30
+ raise HTTPException(status_code=500, detail=f"Error calculating goals dashboard: {str(e)}")
31
+
32
+ @router.post("/calculate-sip", response_model=SIPCalculationResponse)
33
+ async def calculate_sip(
34
+ request: SIPCalculationRequest,
35
+ include_yearly_breakdown: bool = False
36
+ ):
37
+ """
38
+ Calculate SIP maturity amount based on monthly investment, expected return, and time period
39
+ """
40
+ try:
41
+ calculator = SIPCalculator()
42
+ result = calculator.get_sip_calculation(request, include_yearly_breakdown)
43
+ return result
44
+ except Exception as e:
45
+ raise HTTPException(status_code=500, detail=f"Error calculating SIP: {str(e)}")
46
+
47
+ @router.post("/required-sip", response_model=RequiredSIPResponse)
48
+ async def calculate_required_sip(request: RequiredSIPRequest):
49
+ """
50
+ Calculate required SIP amount to reach a target amount
51
+ """
52
+ try:
53
+ calculator = SIPCalculator()
54
+ result = calculator.get_required_sip(request)
55
+ return result
56
+ except Exception as e:
57
+ raise HTTPException(status_code=500, detail=f"Error calculating required SIP: {str(e)}")
58
+
59
+ @router.post("/inflation-adjusted")
60
+ async def calculate_inflation_adjusted_amount(
61
+ target_amount: float,
62
+ years: int,
63
+ expected_inflation: float
64
+ ):
65
+ """
66
+ Calculate inflation-adjusted target amount for a goal
67
+ """
68
+ try:
69
+ inflation_adjusted_amount = target_amount * ((1 + expected_inflation/100) ** years)
70
+ calculator = SIPCalculator()
71
+ required_sip = calculator.calculate_required_sip(inflation_adjusted_amount, years, 12)
72
+
73
+ return {
74
+ "original_amount": target_amount,
75
+ "inflation_adjusted_amount": inflation_adjusted_amount,
76
+ "required_sip": required_sip,
77
+ "years": years,
78
+ "expected_inflation": expected_inflation
79
+ }
80
+ except Exception as e:
81
+ raise HTTPException(status_code=500, detail=f"Error calculating inflation-adjusted amount: {str(e)}")
app/api/endpoints/market.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends, HTTPException
2
+ from app.services.data_fetcher import MutualFundDataFetcher
3
+ from app.models.fund_models import MarketIndicesResponse
4
+
5
+ router = APIRouter()
6
+
7
+ @router.get("/indices", response_model=MarketIndicesResponse)
8
+ async def get_market_indices():
9
+ """
10
+ Get current market indices data including Nifty 50, Sensex, etc.
11
+ """
12
+ try:
13
+ fetcher = MutualFundDataFetcher()
14
+ indices_data = fetcher.get_market_indices()
15
+ return indices_data
16
+ except Exception as e:
17
+ raise HTTPException(status_code=500, detail=f"Error fetching market indices: {str(e)}")
app/api/endpoints/portfolio.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends, HTTPException, Query
2
+ from typing import Dict, Any, List
3
+ from app.services.portfolio_analyzer import PortfolioAnalyzer
4
+ from app.models.portfolio_models import (
5
+ Portfolio, PortfolioMetrics, PortfolioTemplate,
6
+ RebalanceAnalysis, PerformanceReport
7
+ )
8
+
9
+ router = APIRouter()
10
+
11
+ @router.post("/metrics", response_model=PortfolioMetrics)
12
+ async def calculate_portfolio_metrics(portfolio: Portfolio):
13
+ """
14
+ Calculate portfolio-level metrics including total value, gains, and category allocation
15
+ """
16
+ try:
17
+ analyzer = PortfolioAnalyzer()
18
+ metrics = analyzer.calculate_portfolio_metrics(portfolio.holdings)
19
+ return metrics
20
+ except Exception as e:
21
+ raise HTTPException(status_code=500, detail=f"Error calculating portfolio metrics: {str(e)}")
22
+
23
+ @router.post("/rebalance", response_model=RebalanceAnalysis)
24
+ async def generate_rebalance_analysis(portfolio: Portfolio):
25
+ """
26
+ Generate detailed rebalancing analysis and recommendations for a portfolio
27
+ """
28
+ try:
29
+ analyzer = PortfolioAnalyzer()
30
+ rebalance_analysis = analyzer.generate_rebalance_analysis(portfolio.holdings)
31
+ return rebalance_analysis
32
+ except Exception as e:
33
+ raise HTTPException(status_code=500, detail=f"Error generating rebalance analysis: {str(e)}")
34
+
35
+ @router.post("/performance", response_model=PerformanceReport)
36
+ async def generate_performance_report(portfolio: Portfolio):
37
+ """
38
+ Generate comprehensive performance report for a portfolio
39
+ """
40
+ try:
41
+ analyzer = PortfolioAnalyzer()
42
+ performance_report = analyzer.generate_performance_report(portfolio.holdings)
43
+ return performance_report
44
+ except Exception as e:
45
+ raise HTTPException(status_code=500, detail=f"Error generating performance report: {str(e)}")
46
+
47
+ @router.get("/template/{template}")
48
+ async def get_portfolio_template(template: PortfolioTemplate):
49
+ """
50
+ Get a predefined portfolio template (Conservative, Balanced, Aggressive, Custom Sample)
51
+ """
52
+ try:
53
+ analyzer = PortfolioAnalyzer()
54
+ template_portfolio = analyzer.get_template_portfolio(template)
55
+ return {"template": template_portfolio}
56
+ except Exception as e:
57
+ raise HTTPException(status_code=500, detail=f"Error getting portfolio template: {str(e)}")
58
+
59
+ @router.get("/templates")
60
+ async def get_all_portfolio_templates():
61
+ """
62
+ Get all available portfolio templates
63
+ """
64
+ try:
65
+ templates = {}
66
+ for template in PortfolioTemplate:
67
+ analyzer = PortfolioAnalyzer()
68
+ template_portfolio = analyzer.get_template_portfolio(template)
69
+ templates[template.value] = template_portfolio
70
+ return templates
71
+ except Exception as e:
72
+ raise HTTPException(status_code=500, detail=f"Error getting portfolio templates: {str(e)}")
app/config.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from pydantic_settings import BaseSettings
3
+
4
+ class Settings(BaseSettings):
5
+ # API Configuration
6
+ API_V1_STR: str = "/api/v1"
7
+ PROJECT_NAME: str = "Mutual Fund Investment API"
8
+
9
+ # External API Keys
10
+ SWARMS_API_KEY: str = os.getenv("SWARMS_API_KEY", "")
11
+ SWARMS_BASE_URL: str = "https://api.swarms.world"
12
+
13
+ # Mutual Fund API
14
+ MFAPI_BASE_URL: str = "https://api.mfapi.in/mf"
15
+
16
+ # CORS Configuration
17
+ BACKEND_CORS_ORIGINS: list[str] = ["http://localhost:8501", "http://localhost:3000"]
18
+
19
+ # Cache TTL (in seconds)
20
+ CACHE_TTL: int = 3600
21
+
22
+ class Config:
23
+ env_file = ".env"
24
+
25
+ settings = Settings()
app/models/__init__.py ADDED
File without changes
app/models/ai_models.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+ from typing import List, Dict, Optional, Any
3
+ from enum import Enum
4
+
5
+ class AnalysisFocus(str, Enum):
6
+ FUND_SELECTION = "Fund Selection"
7
+ RISK_ASSESSMENT = "Risk Assessment"
8
+ GOAL_ALIGNMENT = "Goal Alignment"
9
+ TAX_OPTIMIZATION = "Tax Optimization"
10
+ REBALANCING = "Rebalancing"
11
+
12
+ class MarketView(str, Enum):
13
+ BULLISH = "Bullish"
14
+ NEUTRAL = "Neutral"
15
+ BEARISH = "Bearish"
16
+ VOLATILE = "Volatile"
17
+
18
+ class InvestmentHorizon(str, Enum):
19
+ SHORT_TERM = "Short Term (1-3 years)"
20
+ MEDIUM_TERM = "Medium Term (3-7 years)"
21
+ LONG_TERM = "Long Term (7+ years)"
22
+
23
+ class AIAnalysisRequest(BaseModel):
24
+ client_profile: Dict[str, Any]
25
+ portfolio_data: Dict[str, Any]
26
+ goals_data: Dict[str, Any]
27
+ analysis_focus: List[AnalysisFocus]
28
+ market_conditions: MarketView
29
+ investment_horizon: InvestmentHorizon
30
+
31
+ class AISwarmAgent(BaseModel):
32
+ agent_name: str
33
+ content: Optional[str] = None
34
+
35
+ class AIAnalysisResponse(BaseModel):
36
+ success: bool
37
+ error: Optional[str] = None
38
+ output: Optional[List[AISwarmAgent]] = None
app/models/fund_models.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field, field_validator
2
+ from typing import List, Dict, Optional, Any
3
+ from datetime import datetime
4
+
5
+ class FundMeta(BaseModel):
6
+ fund_house: Optional[str] = None
7
+ scheme_type: Optional[str] = None
8
+ scheme_category: Optional[str] = None
9
+ scheme_code: Optional[str] = None
10
+
11
+ @field_validator('scheme_code', mode='before')
12
+ @classmethod
13
+ def convert_scheme_code_to_str(cls, v):
14
+ if v is not None:
15
+ return str(v)
16
+ return v
17
+
18
+ class NAVData(BaseModel):
19
+ date: datetime
20
+ nav: float
21
+
22
+ class FundNAVResponse(BaseModel):
23
+ meta: FundMeta
24
+ data: List[NAVData]
25
+
26
+ class MarketIndex(BaseModel):
27
+ name: str
28
+ symbol: str
29
+ current_price: float
30
+ change: float
31
+ change_pct: float
32
+
33
+ class MarketIndicesResponse(BaseModel):
34
+ indices: List[MarketIndex]
35
+ last_updated: datetime
36
+
37
+ class FundAnalysisRequest(BaseModel):
38
+ fund_names: List[str]
39
+ investment_amount: float = Field(gt=0)
40
+ start_date: datetime
41
+ end_date: datetime
42
+
43
+ class FundReturn(BaseModel):
44
+ start_nav: float
45
+ end_nav: float
46
+ units: float
47
+ final_value: float
48
+ total_return: float
49
+ investment_amount: float
50
+
51
+ class FundAnalysisResult(BaseModel):
52
+ fund_name: str
53
+ category: str
54
+ scheme_code: str
55
+ fund_house: str
56
+ returns_data: FundReturn
57
+ nav_data: List[NAVData]
58
+ fund_meta: FundMeta
59
+
60
+ class FundAnalysisResponse(BaseModel):
61
+ results: List[FundAnalysisResult]
62
+ comparison_data: List[Dict[str, Any]]
app/models/goal_models.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+ from typing import List, Dict, Optional, Any
3
+ from datetime import datetime
4
+ from enum import Enum
5
+
6
+ class RiskTolerance(str, Enum):
7
+ CONSERVATIVE = "Conservative"
8
+ MODERATE = "Moderate"
9
+ AGGRESSIVE = "Aggressive"
10
+
11
+ class InvestmentExperience(str, Enum):
12
+ BEGINNER = "Beginner"
13
+ INTERMEDIATE = "Intermediate"
14
+ ADVANCED = "Advanced"
15
+
16
+ class TaxBracket(str, Enum):
17
+ TEN_PERCENT = "10%"
18
+ TWENTY_PERCENT = "20%"
19
+ THIRTY_PERCENT = "30%"
20
+
21
+ class PriorityLevel(str, Enum):
22
+ LOW = "Low"
23
+ MEDIUM = "Medium"
24
+ HIGH = "High"
25
+ CRITICAL = "Critical"
26
+
27
+ class ClientProfile(BaseModel):
28
+ age: int = Field(ge=18, le=100)
29
+ monthly_income: float = Field(gt=0)
30
+ risk_tolerance: RiskTolerance
31
+ investment_experience: InvestmentExperience
32
+ tax_bracket: TaxBracket
33
+ monthly_savings: float = Field(gt=0)
34
+
35
+ class InvestmentGoal(BaseModel):
36
+ name: str
37
+ amount: float = Field(gt=0)
38
+ inflation_adjusted_amount: float = Field(gt=0)
39
+ years: int = Field(ge=1, le=40)
40
+ priority: PriorityLevel
41
+ required_sip: float = Field(ge=0)
42
+ expected_inflation: float = Field(ge=0, le=20)
43
+ id: int
44
+
45
+ class GoalsDashboard(BaseModel):
46
+ goals: List[InvestmentGoal]
47
+ total_required_sip: float
48
+ monthly_savings: float
49
+ shortfall: float
50
+ surplus: float
51
+
52
+ class SIPCalculationRequest(BaseModel):
53
+ monthly_amount: float = Field(gt=0)
54
+ annual_return: float = Field(ge=0)
55
+ years: int = Field(ge=1)
56
+
57
+ class SIPCalculationResponse(BaseModel):
58
+ maturity_amount: float
59
+ total_invested: float
60
+ gains: float
61
+ return_multiple: float
62
+ yearly_breakdown: Optional[List[Dict[str, Any]]] = None
63
+
64
+ class RequiredSIPRequest(BaseModel):
65
+ target_amount: float = Field(gt=0)
66
+ years: int = Field(ge=1)
67
+ expected_return: float = Field(ge=0)
68
+
69
+ class RequiredSIPResponse(BaseModel):
70
+ required_sip: float
71
+
72
+ class GoalsDashboardRequest(BaseModel):
73
+ goals: List[InvestmentGoal]
74
+ monthly_savings: float
app/models/portfolio_models.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+ from typing import List, Dict, Optional, Any
3
+ from datetime import datetime
4
+ from enum import Enum
5
+
6
+ class InvestmentType(str, Enum):
7
+ LUMP_SUM = "Lump Sum"
8
+ SIP_MONTHLY = "SIP (Monthly)"
9
+ STP = "STP"
10
+
11
+ class PortfolioHolding(BaseModel):
12
+ scheme_code: str
13
+ category: str
14
+ fund_house: str
15
+ invested_amount: float = Field(gt=0)
16
+ current_value: float = Field(gt=0)
17
+ units: float = Field(gt=0)
18
+ current_nav: float = Field(gt=0)
19
+ investment_type: InvestmentType
20
+ nav_data: List[Any] = []
21
+
22
+ class Portfolio(BaseModel):
23
+ holdings: Dict[str, PortfolioHolding] = {}
24
+
25
+ class PortfolioMetrics(BaseModel):
26
+ total_value: float
27
+ total_invested: float
28
+ total_gains: float
29
+ category_allocation: Dict[str, float]
30
+
31
+ class PortfolioTemplate(str, Enum):
32
+ CONSERVATIVE = "Conservative"
33
+ BALANCED = "Balanced"
34
+ AGGRESSIVE = "Aggressive"
35
+ CUSTOM_SAMPLE = "Custom Sample"
36
+
37
+ class RebalanceAction(BaseModel):
38
+ category: str
39
+ current_pct: float
40
+ target_pct: float
41
+ difference: float
42
+ amount_change: float
43
+ action: str # "INCREASE" or "DECREASE"
44
+
45
+ class RebalanceAnalysis(BaseModel):
46
+ actions: List[RebalanceAction]
47
+ recommended_strategy: str
48
+ target_allocation: Dict[str, float]
49
+
50
+ class PerformanceReport(BaseModel):
51
+ total_invested: float
52
+ total_value: float
53
+ total_gains: float
54
+ overall_return_pct: float
55
+ fund_performance: List[Dict[str, Any]]
56
+ best_performer: Optional[str] = None
57
+ worst_performer: Optional[str] = None
58
+ max_return: float
59
+ min_return: float
60
+ volatility: float
61
+ sharpe_ratio: float
62
+ portfolio_metrics: PortfolioMetrics
app/services/__init__.py ADDED
File without changes
app/services/ai_swarm.py ADDED
@@ -0,0 +1,338 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import json
3
+ from typing import Dict, Any, List
4
+ from app.config import settings
5
+ from app.models.ai_models import AIAnalysisRequest, AIAnalysisResponse, AISwarmAgent
6
+ import pandas as pd
7
+
8
+ # Popular mutual fund categories with scheme codes
9
+ POPULAR_FUNDS = {
10
+ 'Large Cap Equity': {
11
+ 'HDFC Top 100 Fund': '120503',
12
+ 'ICICI Pru Bluechip Fund': '120505',
13
+ 'SBI Bluechip Fund': '125497',
14
+ 'Axis Bluechip Fund': '120503',
15
+ 'Kotak Bluechip Fund': '118989'
16
+ },
17
+ 'Mid Cap Equity': {
18
+ 'HDFC Mid-Cap Opportunities Fund': '118551',
19
+ 'ICICI Pru Mid Cap Fund': '120544',
20
+ 'Kotak Emerging Equity Fund': '118999',
21
+ 'SBI Magnum Mid Cap Fund': '100281',
22
+ 'DSP Mid Cap Fund': '112618'
23
+ },
24
+ 'Small Cap Equity': {
25
+ 'SBI Small Cap Fund': '122639',
26
+ 'DSP Small Cap Fund': '112618',
27
+ 'HDFC Small Cap Fund': '118551',
28
+ 'Axis Small Cap Fund': '125487',
29
+ 'Kotak Small Cap Fund': '119028'
30
+ },
31
+ 'ELSS (Tax Saving)': {
32
+ 'Axis Long Term Equity Fund': '125494',
33
+ 'HDFC Tax Saver': '100277',
34
+ 'SBI Tax Saver': '125497',
35
+ 'ICICI Pru ELSS Tax Saver': '120503',
36
+ 'Kotak Tax Saver': '118989'
37
+ },
38
+ 'Debt Funds': {
39
+ 'HDFC Corporate Bond Fund': '101762',
40
+ 'ICICI Pru Corporate Bond Fund': '120503',
41
+ 'SBI Corporate Bond Fund': '125497',
42
+ 'Kotak Corporate Bond Fund': '118989',
43
+ 'Axis Corporate Debt Fund': '125494'
44
+ },
45
+ 'Hybrid Funds': {
46
+ 'HDFC Hybrid Equity Fund': '118551',
47
+ 'ICICI Pru Balanced Advantage Fund': '120505',
48
+ 'SBI Equity Hybrid Fund': '125497',
49
+ 'Kotak Equity Hybrid Fund': '118999',
50
+ 'Axis Hybrid Fund': '125494'
51
+ }
52
+ }
53
+
54
+ # AI Agent Prompts for Mutual Funds
55
+ FUND_SELECTION_PROMPT = """
56
+ You are an expert Indian mutual fund selection specialist with deep knowledge of fund analysis,
57
+ performance evaluation, and fund house comparisons for Indian mutual fund markets.
58
+ Your responsibilities:
59
+ 1. Analyze fund performance across different time periods (1Y, 3Y, 5Y)
60
+ 2. Evaluate expense ratios and their impact on long-term returns
61
+ 3. Assess fund manager track record and consistency
62
+ 4. Compare funds within categories using quantitative metrics
63
+ 5. Identify funds with consistent alpha generation
64
+ 6. Evaluate fund house stability and investor service quality
65
+ Focus areas for Indian mutual fund analysis:
66
+ - Performance consistency across market cycles
67
+ - Expense ratio optimization (direct vs regular plans)
68
+ - Fund manager tenure and investment philosophy
69
+ - AUM growth and scalability
70
+ - Category benchmarking and peer comparison
71
+ - Tax efficiency and dividend distribution policy
72
+ - Goal-based fund recommendations
73
+ Provide specific recommendations with rationale for Indian investors.
74
+ """
75
+
76
+ GOAL_PLANNING_PROMPT = """
77
+ You are an expert in goal-based financial planning specialized in mapping investment
78
+ goals to appropriate mutual fund strategies for Indian investors.
79
+ Your responsibilities:
80
+ 1. Analyze client goals for timeline, amount, and priority
81
+ 2. Map goals to appropriate fund categories and asset allocation
82
+ 3. Design SIP strategies aligned with goal timelines
83
+ 4. Recommend optimal fund combinations for multiple goals
84
+ 5. Plan step-up SIP strategies for inflation adjustment
85
+ 6. Create tax-efficient investment strategies including ELSS
86
+ Goal-based fund mapping expertise:
87
+ - Short-term goals (1-3 years): Debt funds, liquid funds
88
+ - Medium-term goals (3-7 years): Hybrid funds, large cap equity
89
+ - Long-term goals (7+ years): Mid cap, small cap equity
90
+ - Tax-saving goals: ELSS funds with 3-year lock-in
91
+ - Retirement planning: Systematic equity exposure with debt balancing
92
+ - Emergency funds: Liquid funds with instant redemption capability
93
+ Provide actionable SIP recommendations with specific amounts and fund selections.
94
+ """
95
+
96
+ RISK_ASSESSMENT_PROMPT = """
97
+ You are an expert in mutual fund risk analysis with extensive knowledge of
98
+ fund-specific risks, category risks, and portfolio risk management for Indian mutual fund investments.
99
+ Your responsibilities:
100
+ 1. Assess fund-specific risks including manager risk, style drift
101
+ 2. Analyze category concentration and diversification needs
102
+ 3. Evaluate expense ratio impact on long-term wealth creation
103
+ 4. Assess liquidity risks across different fund categories
104
+ 5. Identify regulatory and tax-related risks
105
+ 6. Recommend risk mitigation strategies
106
+ Focus on Indian mutual fund risk factors:
107
+ - Fund manager tenure and philosophy changes
108
+ - Category concentration and overlap analysis
109
+ - Expense ratio drag on returns over long periods
110
+ - Exit load structures and liquidity constraints
111
+ - Tax implications of different fund types
112
+ - Market timing risks in equity fund investments
113
+ - Credit risks in debt fund categories
114
+ Provide practical risk management recommendations for Indian mutual fund investors.
115
+ """
116
+
117
+ FUND_ALLOCATION_PROMPT = """
118
+ You are an expert in mutual fund portfolio allocation and performance analysis, with deep knowledge of fund analysis, performance evaluation, and fund house comparisons for the Indian mutual fund market. Your role is to evaluate fund performance, provide insights on key metrics, and generate tailored recommendations based on individual investor needs and goals.
119
+ Your Responsibilities:
120
+ 1. Fund Performance Analysis:
121
+ Analyze fund performance over different time periods (1Y, 3Y, 5Y) to assess consistency and growth potential.
122
+ 2. Expense Ratio Evaluation:
123
+ Evaluate expense ratios (direct vs regular plans) and their long-term impact on net returns for different types of investors.
124
+ 3. Fund Manager Analysis:
125
+ Assess the track record and investment philosophy of the fund manager. Determine consistency in management and its impact on performance.
126
+ 4. Category Comparison:
127
+ Compare funds within categories (e.g., equity, debt, hybrid) using quantitative metrics such as:
128
+ Risk-adjusted returns
129
+ Sharpe ratio
130
+ Alpha generation
131
+ Drawdowns and volatility
132
+ 5. Alpha Generation Identification:
133
+ Identify funds that consistently generate alpha (outperform the benchmark) and assess the strategies that lead to such performance.
134
+ 6. Fund House Stability:
135
+ Evaluate the fund house's financial stability, reputation, and investor service quality.
136
+ Analyze AUM growth and scalability of the fund house to ensure long-term reliability.
137
+ 7. Tax Efficiency & Dividend Distribution Policy:
138
+ Evaluate the tax efficiency of each fund considering capital gains tax, dividend distribution, and holding period taxation.
139
+ Provide insights into how these factors impact long-term returns for Indian investors.
140
+ 8. Goal-Based Fund Recommendations:
141
+ Provide goal-based fund recommendations (e.g., retirement, education, wealth creation) with tailored suggestions based on:
142
+ Risk tolerance
143
+ Investment horizon
144
+ Tax considerations
145
+
146
+ Key Areas of Focus for Indian Mutual Fund Analysis:
147
+ Performance Consistency Across Market Cycles:
148
+ Assess how well the fund performs in different market environments (bullish, bearish, sideways).
149
+ Expense Ratio Optimization (Direct vs Regular Plans):
150
+ Evaluate the trade-off between cost efficiency and accessibility, recommending the most suitable fund plans for different investor types.
151
+ Fund Manager Tenure & Investment Philosophy:
152
+ Assess the impact of a fund manager's tenure and their investment philosophy on the fund's consistency and long-term performance.
153
+ AUM Growth & Scalability:
154
+ Determine how AUM growth impacts a fund's ability to scale and maintain performance. Large AUM may affect liquidity and flexibility, but also signal trust.
155
+ Category Benchmarking & Peer Comparison:
156
+ Compare funds against category benchmarks (e.g., Nifty 50 for equity, Crisil for debt) and identify top performers within their category.
157
+ Tax Efficiency & Dividend Policies:
158
+ Evaluate tax efficiency considering capital gains, dividends, and fund turnover. Provide strategies to minimize tax liabilities over the investment horizon.
159
+ Goal-Based Recommendations:
160
+ Provide tailored investment solutions based on individual investor goals, such as:
161
+ Retirement planning
162
+ Wealth creation
163
+ Education funding
164
+ Short-term goals
165
+
166
+ Recommendations for Indian Investors:
167
+ 1. Equity Funds (Growth-Oriented):
168
+ Focus on funds with strong long-term performance, consistently high alpha, and low expense ratios. These funds are suitable for growth-focused investors seeking capital appreciation.
169
+ 2. Debt Funds (Risk-Averse):
170
+ For investors with a low risk tolerance, recommend low-volatility funds with stable returns and a track record of consistency. Ensure tax-efficient funds for better after-tax returns.
171
+ 3. Hybrid Funds (Balanced Approach):
172
+ Combine equity and debt for a diversified approach. These funds are suitable for investors seeking a balance between risk and reward, particularly those with a medium-term horizon.
173
+ 4. Tax Efficiency:
174
+ Recommend funds that are tax-efficient, such as those with lower turnover and capital gains distributions. Focus on LTCG tax advantages for long-term investors.
175
+ 5. Goal-Based Recommendations:
176
+ For retirement planning, suggest equity funds for long-term growth. For short-term goals, recommend debt funds or hybrid funds based on the investor's risk appetite.
177
+ Provide specific recommendations with rationale for Indian investors.
178
+ Make use of the following advanced portfolio techniques where applicable:
179
+ 1. Modern Portfolio Theory (MPT) – Efficient Frontier
180
+ 2. Risk Parity Allocation
181
+ 3. Black-Litterman Model
182
+ 4. Monte Carlo Simulation
183
+ """
184
+
185
+ class AISwarmService:
186
+ """Service for creating AI swarms for mutual fund analysis"""
187
+
188
+ def __init__(self):
189
+ self.api_key = settings.SWARMS_API_KEY
190
+ self.base_url = settings.SWARMS_BASE_URL
191
+ self.headers = {
192
+ "x-api-key": self.api_key,
193
+ "Content-Type": "application/json"
194
+ }
195
+
196
+ def create_mutual_fund_swarm(self, portfolio_data: Dict[str, Any],
197
+ client_profile: Dict[str, Any],
198
+ goals_data: Dict[str, Any]) -> AIAnalysisResponse:
199
+ """Create AI swarm for mutual fund analysis"""
200
+
201
+ swarm_config = {
202
+ "name": "Mutual Fund Investment Analysis Swarm",
203
+ "description": "AI swarm for Indian mutual fund investment analysis and recommendations",
204
+ "agents": [
205
+ {
206
+ "agent_name": "Fund Selection Specialist",
207
+ "system_prompt": FUND_SELECTION_PROMPT,
208
+ "model_name": "gpt-4o",
209
+ "role": "worker",
210
+ "max_loops": 1,
211
+ "max_tokens": 4096,
212
+ "temperature": 0.3,
213
+ },
214
+ {
215
+ "agent_name": "Goal Planning Specialist",
216
+ "system_prompt": GOAL_PLANNING_PROMPT,
217
+ "model_name": "gpt-4o",
218
+ "role": "worker",
219
+ "max_loops": 1,
220
+ "max_tokens": 4096,
221
+ "temperature": 0.3,
222
+ },
223
+ {
224
+ "agent_name": "Risk Assessment Specialist",
225
+ "system_prompt": RISK_ASSESSMENT_PROMPT,
226
+ "model_name": "gpt-4o",
227
+ "role": "worker",
228
+ "max_loops": 1,
229
+ "max_tokens": 4096,
230
+ "temperature": 0.3,
231
+ },
232
+ {
233
+ "agent_name": "Fund Allocation Specialist",
234
+ "system_prompt": FUND_ALLOCATION_PROMPT,
235
+ "model_name": "gpt-4o",
236
+ "role": "worker",
237
+ "max_loops": 1,
238
+ "max_tokens": 4096,
239
+ "temperature": 0.3,
240
+ }
241
+ ],
242
+ "max_loops": 2,
243
+ "swarm_type": "ConcurrentWorkflow",
244
+ "task": f"""
245
+ Analyze the mutual fund investment requirements:
246
+
247
+ Client Profile: {client_profile}
248
+ Current Portfolio: {portfolio_data}
249
+ Investment Goals: {goals_data}
250
+
251
+ Provide comprehensive recommendations for:
252
+ 1. Fund selection and optimization
253
+ 2. Goal-based SIP planning
254
+ 3. Risk assessment and mitigation
255
+ 4. Tax-efficient strategies
256
+ 5. Implementation roadmap
257
+ """
258
+ }
259
+
260
+ try:
261
+ if self.api_key:
262
+ response = requests.post(
263
+ f"{self.base_url}/v1/swarm/completions",
264
+ headers=self.headers,
265
+ json=swarm_config,
266
+ timeout=120
267
+ )
268
+
269
+ if response.status_code == 200:
270
+ result = response.json()
271
+
272
+ # Parse the output to extract agent responses
273
+ output_agents = []
274
+ if "output" in result and isinstance(result["output"], list):
275
+ for agent_output in result["output"]:
276
+ if isinstance(agent_output, dict):
277
+ output_agents.append(AISwarmAgent(
278
+ agent_name=agent_output.get("agent_name", ""),
279
+ content=agent_output.get("content", "")
280
+ ))
281
+
282
+ return AIAnalysisResponse(
283
+ success=True,
284
+ output=output_agents
285
+ )
286
+ else:
287
+ return AIAnalysisResponse(
288
+ success=False,
289
+ error=f"API request failed with status code {response.status_code}"
290
+ )
291
+ else:
292
+ return AIAnalysisResponse(
293
+ success=False,
294
+ error="Swarms API key not configured"
295
+ )
296
+ except Exception as e:
297
+ return AIAnalysisResponse(
298
+ success=False,
299
+ error=f"Swarm analysis failed: {str(e)}"
300
+ )
301
+
302
+ def get_enhanced_analysis(self, request: AIAnalysisRequest) -> AIAnalysisResponse:
303
+ """Get enhanced AI analysis with additional context"""
304
+
305
+ # Prepare comprehensive data for AI analysis
306
+ client_profile = request.client_profile
307
+
308
+ portfolio_data = {
309
+ 'holdings': request.portfolio_data.get('holdings', []),
310
+ 'categories': request.portfolio_data.get('categories', []),
311
+ 'total_value': request.portfolio_data.get('total_value', 0),
312
+ 'total_invested': request.portfolio_data.get('total_invested', 0),
313
+ 'total_gains': request.portfolio_data.get('total_gains', 0),
314
+ 'category_allocation': request.portfolio_data.get('category_allocation', {})
315
+ }
316
+
317
+ goals_data = {
318
+ 'goals': request.goals_data.get('goals', []),
319
+ 'goal_details': request.goals_data.get('goal_details', []),
320
+ 'total_required_sip': request.goals_data.get('total_required_sip', 0),
321
+ 'timeline_range': request.goals_data.get('timeline_range', ""),
322
+ 'priority_goals': request.goals_data.get('priority_goals', [])
323
+ }
324
+
325
+ # Enhanced context
326
+ analysis_context = {
327
+ 'market_conditions': request.market_conditions.value,
328
+ 'investment_horizon': request.investment_horizon.value,
329
+ 'analysis_focus': [focus.value for focus in request.analysis_focus],
330
+ 'current_date': str(pd.Timestamp.now())
331
+ }
332
+
333
+ # Run AI analysis
334
+ return self.create_mutual_fund_swarm(
335
+ {**portfolio_data, **analysis_context},
336
+ client_profile,
337
+ goals_data
338
+ )
app/services/data_fetcher.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import yfinance as yf
3
+ import pandas as pd
4
+ from datetime import datetime, timedelta
5
+ from typing import List, Dict, Any, Optional, Tuple
6
+ from app.config import settings
7
+ from app.models.fund_models import FundMeta, NAVData, FundNAVResponse, MarketIndex, MarketIndicesResponse
8
+
9
+ class MutualFundDataFetcher:
10
+ """Fetch mutual fund data from MFAPI.in and Yahoo Finance for indices"""
11
+
12
+ def __init__(self):
13
+ self.mfapi_base = settings.MFAPI_BASE_URL
14
+
15
+ def get_all_schemes(self) -> List[Dict[str, Any]]:
16
+ """Fetch all mutual fund schemes"""
17
+ try:
18
+ response = requests.get(self.mfapi_base)
19
+ if response.status_code == 200:
20
+ return response.json()
21
+ else:
22
+ return []
23
+ except Exception as e:
24
+ print(f"Error fetching schemes: {e}")
25
+ return []
26
+
27
+ def get_fund_nav_history(self, scheme_code: str) -> Tuple[pd.DataFrame, FundMeta]:
28
+ """Fetch NAV history for a specific scheme"""
29
+ try:
30
+ url = f"{self.mfapi_base}/{scheme_code}"
31
+ response = requests.get(url)
32
+ if response.status_code == 200:
33
+ data = response.json()
34
+ nav_data = data.get('data', [])
35
+
36
+ # Convert to DataFrame
37
+ df = pd.DataFrame(nav_data)
38
+ if not df.empty:
39
+ df['date'] = pd.to_datetime(df['date'], format='%d-%m-%Y')
40
+ df['nav'] = pd.to_numeric(df['nav'])
41
+ df = df.sort_values('date')
42
+
43
+ # Create FundMeta object
44
+ meta_data = data.get('meta', {})
45
+ fund_meta = FundMeta(
46
+ fund_house=meta_data.get('fund_house'),
47
+ scheme_type=meta_data.get('scheme_type'),
48
+ scheme_category=meta_data.get('scheme_category'),
49
+ scheme_code=str(meta_data.get('scheme_code', scheme_code))
50
+ )
51
+
52
+ return df, fund_meta
53
+ else:
54
+ return pd.DataFrame(), FundMeta()
55
+ except Exception as e:
56
+ print(f"Error fetching NAV data: {e}")
57
+ return pd.DataFrame(), FundMeta()
58
+
59
+ def get_market_indices(self) -> MarketIndicesResponse:
60
+ """Fetch Indian market indices using Yahoo Finance"""
61
+ indices = {
62
+ '^NSEI': 'Nifty 50',
63
+ '^BSESN': 'BSE Sensex',
64
+ '^NSEBANK': 'Nifty Bank',
65
+ '^CNXIT': 'Nifty IT',
66
+ '^NSEMDCP50': 'Nifty Midcap 50',
67
+ 'NIFTYSMLCAP50.NS': 'Nifty Smallcap 50',
68
+ '^CNXPHARMA': 'Nifty Pharma',
69
+ '^CNXAUTO': 'Nifty Auto',
70
+ '^CNXFMCG': 'Nifty FMCG',
71
+ '^CNXENERGY': 'Nifty Energy',
72
+ '^CNXREALTY': 'Nifty Realty',
73
+ '^NSMIDCP': 'Nifty Next 50',
74
+ }
75
+
76
+ indices_data = []
77
+ for symbol, name in indices.items():
78
+ try:
79
+ ticker = yf.Ticker(symbol)
80
+ hist = ticker.history(period="5d")
81
+
82
+ if not hist.empty:
83
+ current_price = hist['Close'].iloc[-1]
84
+ previous_close = hist['Close'].iloc[-2] if len(hist) > 1 else current_price
85
+ change = current_price - previous_close
86
+ change_pct = (change / previous_close) * 100
87
+
88
+ indices_data.append(MarketIndex(
89
+ name=name,
90
+ symbol=symbol,
91
+ current_price=current_price,
92
+ change=change,
93
+ change_pct=change_pct
94
+ ))
95
+ except Exception as e:
96
+ print(f"Could not fetch {name}: {e}")
97
+
98
+ return MarketIndicesResponse(
99
+ indices=indices_data,
100
+ last_updated=datetime.now()
101
+ )
app/services/portfolio_analyzer.py ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import pandas as pd
3
+ from typing import Dict, Any, List, Tuple, Optional
4
+ from app.models.portfolio_models import (
5
+ Portfolio, PortfolioMetrics, PortfolioTemplate,
6
+ RebalanceAction, RebalanceAnalysis, PerformanceReport
7
+ )
8
+
9
+ class PortfolioAnalyzer:
10
+ """Analyze mutual fund portfolio performance and allocation"""
11
+
12
+ @staticmethod
13
+ def calculate_portfolio_metrics(portfolio_holdings: Dict[str, Any]) -> PortfolioMetrics:
14
+ """Calculate portfolio-level metrics"""
15
+ if not portfolio_holdings:
16
+ return PortfolioMetrics(
17
+ total_value=0,
18
+ total_invested=0,
19
+ total_gains=0,
20
+ category_allocation={}
21
+ )
22
+
23
+ total_value = sum(holding.current_value for holding in portfolio_holdings.values())
24
+
25
+ metrics = {
26
+ 'total_value': total_value,
27
+ 'total_invested': sum(holding.invested_amount for holding in portfolio_holdings.values()),
28
+ 'total_gains': total_value - sum(holding.invested_amount for holding in portfolio_holdings.values()),
29
+ 'category_allocation': {}
30
+ }
31
+
32
+ # Calculate category allocation
33
+ for fund_name, holding in portfolio_holdings.items():
34
+ category = holding.category if hasattr(holding, 'category') else 'Other'
35
+ if category not in metrics['category_allocation']:
36
+ metrics['category_allocation'][category] = 0
37
+ metrics['category_allocation'][category] += holding.current_value
38
+
39
+ # Convert to percentages
40
+ for category in metrics['category_allocation']:
41
+ metrics['category_allocation'][category] = (
42
+ metrics['category_allocation'][category] / total_value
43
+ ) * 100 if total_value > 0 else 0
44
+
45
+ return PortfolioMetrics(**metrics)
46
+
47
+ @staticmethod
48
+ def generate_rebalance_analysis(portfolio: Dict[str, Any]) -> RebalanceAnalysis:
49
+ """Generate detailed rebalancing analysis and recommendations"""
50
+ if not portfolio:
51
+ return RebalanceAnalysis(
52
+ actions=[],
53
+ recommended_strategy="No portfolio data available",
54
+ target_allocation={}
55
+ )
56
+
57
+ portfolio_metrics = PortfolioAnalyzer.calculate_portfolio_metrics(portfolio)
58
+ current_allocation = portfolio_metrics.category_allocation
59
+
60
+ # Define target allocations based on common strategies
61
+ target_allocations = {
62
+ 'Conservative': {
63
+ 'Large Cap Equity': 30, 'Debt Funds': 40, 'Hybrid Funds': 25,
64
+ 'ELSS (Tax Saving)': 5, 'Mid Cap Equity': 0, 'Small Cap Equity': 0
65
+ },
66
+ 'Balanced': {
67
+ 'Large Cap Equity': 40, 'Mid Cap Equity': 25, 'ELSS (Tax Saving)': 15,
68
+ 'Debt Funds': 15, 'Hybrid Funds': 5, 'Small Cap Equity': 0
69
+ },
70
+ 'Aggressive': {
71
+ 'Large Cap Equity': 35, 'Mid Cap Equity': 30, 'Small Cap Equity': 25,
72
+ 'ELSS (Tax Saving)': 10, 'Debt Funds': 0, 'Hybrid Funds': 0
73
+ }
74
+ }
75
+
76
+ # Determine closest target allocation
77
+ total_equity = (current_allocation.get('Large Cap Equity', 0) +
78
+ current_allocation.get('Mid Cap Equity', 0) +
79
+ current_allocation.get('Small Cap Equity', 0))
80
+
81
+ if total_equity >= 70:
82
+ recommended_strategy = 'Aggressive'
83
+ elif total_equity >= 45:
84
+ recommended_strategy = 'Balanced'
85
+ else:
86
+ recommended_strategy = 'Conservative'
87
+
88
+ target = target_allocations[recommended_strategy]
89
+ total_portfolio_value = portfolio_metrics.total_value
90
+
91
+ # Calculate rebalancing requirements
92
+ rebalance_actions = []
93
+ for category in target:
94
+ current_pct = current_allocation.get(category, 0)
95
+ target_pct = target[category]
96
+ difference = target_pct - current_pct
97
+
98
+ if abs(difference) > 5: # Only suggest rebalancing if difference > 5%
99
+ current_value = (current_pct / 100) * total_portfolio_value
100
+ target_value = (target_pct / 100) * total_portfolio_value
101
+ amount_change = target_value - current_value
102
+
103
+ action = "INCREASE" if difference > 0 else "DECREASE"
104
+ rebalance_actions.append(RebalanceAction(
105
+ category=category,
106
+ current_pct=current_pct,
107
+ target_pct=target_pct,
108
+ difference=difference,
109
+ amount_change=amount_change,
110
+ action=action
111
+ ))
112
+
113
+ return RebalanceAnalysis(
114
+ actions=rebalance_actions,
115
+ recommended_strategy=recommended_strategy,
116
+ target_allocation=target
117
+ )
118
+
119
+ @staticmethod
120
+ def generate_performance_report(portfolio: Dict[str, Any]) -> PerformanceReport:
121
+ """Generate comprehensive performance report"""
122
+ if not portfolio:
123
+ return PerformanceReport(
124
+ total_invested=0,
125
+ total_value=0,
126
+ total_gains=0,
127
+ overall_return_pct=0,
128
+ fund_performance=[],
129
+ max_return=0,
130
+ min_return=0,
131
+ volatility=0,
132
+ sharpe_ratio=0,
133
+ portfolio_metrics=PortfolioMetrics(
134
+ total_value=0,
135
+ total_invested=0,
136
+ total_gains=0,
137
+ category_allocation={}
138
+ )
139
+ )
140
+
141
+ portfolio_metrics = PortfolioAnalyzer.calculate_portfolio_metrics(portfolio)
142
+
143
+ # Calculate performance metrics
144
+ total_invested = portfolio_metrics.total_invested
145
+ total_value = portfolio_metrics.total_value
146
+ total_gains = portfolio_metrics.total_gains
147
+
148
+ if total_invested > 0:
149
+ overall_return_pct = (total_gains / total_invested) * 100
150
+ else:
151
+ overall_return_pct = 0
152
+
153
+ # Fund-wise performance
154
+ fund_performance = []
155
+ best_performer = None
156
+ worst_performer = None
157
+ max_return = float('-inf')
158
+ min_return = float('inf')
159
+
160
+ for fund_name, holding in portfolio.items():
161
+ invested = holding.invested_amount
162
+ current = holding.current_value
163
+ gain_loss = current - invested
164
+ return_pct = (gain_loss / invested * 100) if invested > 0 else 0
165
+
166
+ fund_performance.append({
167
+ 'fund': fund_name,
168
+ 'category': holding.category if hasattr(holding, 'category') else 'Other',
169
+ 'invested': invested,
170
+ 'current_value': current,
171
+ 'gain_loss': gain_loss,
172
+ 'return_pct': return_pct
173
+ })
174
+
175
+ if return_pct > max_return:
176
+ max_return = return_pct
177
+ best_performer = fund_name
178
+
179
+ if return_pct < min_return:
180
+ min_return = return_pct
181
+ worst_performer = fund_name
182
+
183
+ # Risk metrics (simplified)
184
+ returns = [perf['return_pct'] for perf in fund_performance]
185
+ if len(returns) > 1:
186
+ volatility = np.std(returns)
187
+ sharpe_ratio = (np.mean(returns) - 6) / volatility if volatility > 0 else 0 # Assuming 6% risk-free rate
188
+ else:
189
+ volatility = 0
190
+ sharpe_ratio = 0
191
+
192
+ return PerformanceReport(
193
+ total_invested=total_invested,
194
+ total_value=total_value,
195
+ total_gains=total_gains,
196
+ overall_return_pct=overall_return_pct,
197
+ fund_performance=fund_performance,
198
+ best_performer=best_performer,
199
+ worst_performer=worst_performer,
200
+ max_return=max_return,
201
+ min_return=min_return,
202
+ volatility=volatility,
203
+ sharpe_ratio=sharpe_ratio,
204
+ portfolio_metrics=portfolio_metrics
205
+ )
206
+
207
+ @staticmethod
208
+ def get_template_portfolio(template: PortfolioTemplate) -> Dict[str, Any]:
209
+ """Get a predefined portfolio template"""
210
+ templates = {
211
+ PortfolioTemplate.CONSERVATIVE: {
212
+ 'HDFC Corporate Bond Fund': {
213
+ 'scheme_code': '101762', 'category': 'Debt Funds',
214
+ 'invested_amount': 40000, 'current_value': 42000, 'units': 800,
215
+ 'investment_type': 'SIP (Monthly)'
216
+ },
217
+ 'HDFC Top 100 Fund': {
218
+ 'scheme_code': '120503', 'category': 'Large Cap Equity',
219
+ 'invested_amount': 30000, 'current_value': 33000, 'units': 600,
220
+ 'investment_type': 'SIP (Monthly)'
221
+ },
222
+ 'HDFC Hybrid Equity Fund': {
223
+ 'scheme_code': '118551', 'category': 'Hybrid Funds',
224
+ 'invested_amount': 30000, 'current_value': 32000, 'units': 600,
225
+ 'investment_type': 'SIP (Monthly)'
226
+ }
227
+ },
228
+ PortfolioTemplate.BALANCED: {
229
+ 'HDFC Top 100 Fund': {
230
+ 'scheme_code': '120503', 'category': 'Large Cap Equity',
231
+ 'invested_amount': 40000, 'current_value': 45000, 'units': 800,
232
+ 'investment_type': 'SIP (Monthly)'
233
+ },
234
+ 'ICICI Pru Mid Cap Fund': {
235
+ 'scheme_code': '120544', 'category': 'Mid Cap Equity',
236
+ 'invested_amount': 30000, 'current_value': 36000, 'units': 400,
237
+ 'investment_type': 'SIP (Monthly)'
238
+ },
239
+ 'HDFC Tax Saver': {
240
+ 'scheme_code': '100277', 'category': 'ELSS (Tax Saving)',
241
+ 'invested_amount': 20000, 'current_value': 23000, 'units': 400,
242
+ 'investment_type': 'SIP (Monthly)'
243
+ },
244
+ 'HDFC Corporate Bond Fund': {
245
+ 'scheme_code': '101762', 'category': 'Debt Funds',
246
+ 'invested_amount': 10000, 'current_value': 10500, 'units': 200,
247
+ 'investment_type': 'Lump Sum'
248
+ }
249
+ },
250
+ PortfolioTemplate.AGGRESSIVE: {
251
+ 'SBI Small Cap Fund': {
252
+ 'scheme_code': '122639', 'category': 'Small Cap Equity',
253
+ 'invested_amount': 30000, 'current_value': 38000, 'units': 500,
254
+ 'investment_type': 'SIP (Monthly)'
255
+ },
256
+ 'ICICI Pru Mid Cap Fund': {
257
+ 'scheme_code': '120544', 'category': 'Mid Cap Equity',
258
+ 'invested_amount': 30000, 'current_value': 36000, 'units': 400,
259
+ 'investment_type': 'SIP (Monthly)'
260
+ },
261
+ 'HDFC Top 100 Fund': {
262
+ 'scheme_code': '120503', 'category': 'Large Cap Equity',
263
+ 'invested_amount': 25000, 'current_value': 28000, 'units': 500,
264
+ 'investment_type': 'SIP (Monthly)'
265
+ },
266
+ 'Kotak Emerging Equity Fund': {
267
+ 'scheme_code': '118999', 'category': 'Mid Cap Equity',
268
+ 'invested_amount': 15000, 'current_value': 18000, 'units': 200,
269
+ 'investment_type': 'SIP (Monthly)'
270
+ }
271
+ },
272
+ PortfolioTemplate.CUSTOM_SAMPLE: {
273
+ 'HDFC Balanced Advantage Fund': {
274
+ 'scheme_code': '104248', 'category': 'Hybrid Funds',
275
+ 'invested_amount': 30000, 'current_value': 34000, 'units': 600,
276
+ 'investment_type': 'SIP (Monthly)'
277
+ },
278
+ 'HDFC Top 100 Fund': {
279
+ 'scheme_code': '120503', 'category': 'Large Cap Equity',
280
+ 'invested_amount': 30000, 'current_value': 33000, 'units': 600,
281
+ 'investment_type': 'SIP (Monthly)'
282
+ },
283
+ 'HDFC Corporate Bond Fund': {
284
+ 'scheme_code': '101762', 'category': 'Debt Funds',
285
+ 'invested_amount': 20000, 'current_value': 21000, 'units': 400,
286
+ 'investment_type': 'Lump Sum'
287
+ },
288
+ 'HDFC Tax Saver': {
289
+ 'scheme_code': '100277', 'category': 'ELSS (Tax Saving)',
290
+ 'invested_amount': 20000, 'current_value': 23000, 'units': 400,
291
+ 'investment_type': 'SIP (Monthly)'
292
+ }
293
+ }
294
+ }
295
+
296
+ return templates.get(template, {})
app/services/sip_calculator.py ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ from datetime import datetime
3
+ from typing import Optional, Dict, Any, List
4
+ from app.models.goal_models import SIPCalculationRequest, SIPCalculationResponse, RequiredSIPRequest, RequiredSIPResponse
5
+
6
+ class SIPCalculator:
7
+ """Calculate SIP returns and goal-based investments"""
8
+
9
+ @staticmethod
10
+ def calculate_sip_maturity(monthly_amount: float, annual_return: float, years: int) -> float:
11
+ """Calculate SIP maturity amount"""
12
+ if annual_return == 0:
13
+ return monthly_amount * years * 12
14
+
15
+ monthly_return = annual_return / 12 / 100
16
+ months = years * 12
17
+
18
+ maturity_amount = monthly_amount * (
19
+ ((1 + monthly_return) ** months - 1) / monthly_return
20
+ ) * (1 + monthly_return)
21
+
22
+ return maturity_amount
23
+
24
+ @staticmethod
25
+ def calculate_required_sip(target_amount: float, years: int, expected_return: float) -> float:
26
+ """Calculate required SIP amount for a target"""
27
+ if expected_return == 0:
28
+ return target_amount / (years * 12)
29
+
30
+ monthly_return = expected_return / 12 / 100
31
+ months = years * 12
32
+
33
+ required_sip = target_amount / (
34
+ ((1 + monthly_return) ** months - 1) / monthly_return
35
+ ) / (1 + monthly_return)
36
+
37
+ return required_sip
38
+
39
+ @staticmethod
40
+ def calculate_fund_returns(nav_data: pd.DataFrame, investment_amount: float,
41
+ start_date: datetime, end_date: datetime) -> Optional[Dict[str, Any]]:
42
+ """Calculate returns for a fund investment"""
43
+ try:
44
+ filtered_data = nav_data[
45
+ (nav_data['date'] >= pd.to_datetime(start_date)) &
46
+ (nav_data['date'] <= pd.to_datetime(end_date))
47
+ ]
48
+
49
+ if len(filtered_data) < 2:
50
+ return None
51
+
52
+ start_nav = filtered_data.iloc[0]['nav']
53
+ end_nav = filtered_data.iloc[-1]['nav']
54
+
55
+ units = investment_amount / start_nav
56
+ final_value = units * end_nav
57
+ total_return = ((final_value - investment_amount) / investment_amount) * 100
58
+
59
+ return {
60
+ 'start_nav': start_nav,
61
+ 'end_nav': end_nav,
62
+ 'units': units,
63
+ 'final_value': final_value,
64
+ 'total_return': total_return,
65
+ 'investment_amount': investment_amount
66
+ }
67
+ except Exception as e:
68
+ print(f"Error calculating returns: {e}")
69
+ return None
70
+
71
+ @staticmethod
72
+ def get_sip_calculation(request: SIPCalculationRequest, include_yearly_breakdown: bool = False) -> SIPCalculationResponse:
73
+ """Get SIP calculation with optional yearly breakdown"""
74
+ maturity_amount = SIPCalculator.calculate_sip_maturity(
75
+ request.monthly_amount, request.annual_return, request.years
76
+ )
77
+
78
+ total_invested = request.monthly_amount * request.years * 12
79
+ gains = maturity_amount - total_invested
80
+ return_multiple = maturity_amount / total_invested if total_invested > 0 else 0
81
+
82
+ yearly_breakdown = None
83
+ if include_yearly_breakdown:
84
+ yearly_breakdown = []
85
+ invested_cumulative = 0
86
+
87
+ for year in range(1, request.years + 1):
88
+ maturity_year = SIPCalculator.calculate_sip_maturity(
89
+ request.monthly_amount, request.annual_return, year
90
+ )
91
+ invested_year = request.monthly_amount * year * 12
92
+
93
+ yearly_breakdown.append({
94
+ 'Year': year,
95
+ 'Invested': invested_year,
96
+ 'Maturity Value': maturity_year,
97
+ 'Gains': maturity_year - invested_year
98
+ })
99
+
100
+ return SIPCalculationResponse(
101
+ maturity_amount=maturity_amount,
102
+ total_invested=total_invested,
103
+ gains=gains,
104
+ return_multiple=return_multiple,
105
+ yearly_breakdown=yearly_breakdown
106
+ )
107
+
108
+ @staticmethod
109
+ def get_required_sip(request: RequiredSIPRequest) -> RequiredSIPResponse:
110
+ """Get required SIP amount for a target"""
111
+ required_sip = SIPCalculator.calculate_required_sip(
112
+ request.target_amount, request.years, request.expected_return
113
+ )
114
+
115
+ return RequiredSIPResponse(required_sip=required_sip)
app/utils/__init__.py ADDED
File without changes
app/utils/helpers.py ADDED
File without changes