lucifer7210 commited on
Commit
c04ca05
·
verified ·
1 Parent(s): dcec87f

Upload 16 files

Browse files
.env ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ SWARMS_API_KEY=sk-f763a9c6f898aa3ec74bf49047736a0201c293eefc36d5a04be8af647b93b711
2
+ FRED_API_KEY=d421178827e3d9c621210bc6f0303439
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ __pycache__/
README.md CHANGED
@@ -1,10 +0,0 @@
1
- ---
2
- title: Financial Analyst
3
- emoji: 🚀
4
- colorFrom: red
5
- colorTo: green
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
app.py CHANGED
@@ -1,7 +1,120 @@
1
- from fastapi import FastAPI
2
-
3
- app = FastAPI()
4
-
5
- @app.get("/")
6
- def greet_json():
7
- return {"Hello": "World!"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # main.py
2
+ from fastapi import FastAPI, HTTPException, Depends
3
+ from fastapi.middleware.cors import CORSMiddleware # If Streamlit runs on a different port
4
+ from typing import Dict, List
5
+ from services.data_fetcher import IndianMarketDataFetcher
6
+ from services.analyzer import IndianFinancialAnalyzer
7
+ from services.swarm_service import create_indian_market_swarm
8
+ from models.market_data import StockDataResponse, IndexData, SectorPerformance, EconomicIndicators
9
+ from models.analysis import StockAnalysisResponse, SwarmAnalysisResponse, InvestmentRecommendationResponse
10
+ from config import NIFTY50_COMPANIES
11
+
12
+ app = FastAPI(title="Indian Market Analysis API", version="1.0.0")
13
+
14
+ # Configure CORS if needed (e.g., if Streamlit runs on localhost:8501)
15
+ app.add_middleware(
16
+ CORSMiddleware,
17
+ allow_origins=["*"], # Restrict this in production!
18
+ allow_credentials=True,
19
+ allow_methods=["*"],
20
+ allow_headers=["*"],
21
+ )
22
+
23
+ # Dependency Injection for services
24
+ def get_data_fetcher():
25
+ return IndianMarketDataFetcher()
26
+
27
+ def get_analyzer():
28
+ return IndianFinancialAnalyzer()
29
+
30
+ # --- API Endpoints ---
31
+
32
+ @app.get("/")
33
+ async def root():
34
+ return {"message": "Indian Market Analysis API"}
35
+
36
+ # --- Market Data Endpoints ---
37
+
38
+ @app.get("/api/market/indices", response_model=Dict[str, IndexData])
39
+ async def get_market_indices(fetcher: IndianMarketDataFetcher = Depends(get_data_fetcher)):
40
+ """Get live market indices data."""
41
+ return fetcher.get_market_indices()
42
+
43
+ @app.get("/api/market/sectors", response_model=Dict[str, SectorPerformance])
44
+ async def get_sector_performance(fetcher: IndianMarketDataFetcher = Depends(get_data_fetcher)):
45
+ """Get sector performance data."""
46
+ return fetcher.get_sector_performance()
47
+
48
+ @app.get("/api/market/economic", response_model=EconomicIndicators)
49
+ async def get_economic_indicators(analyzer: IndianFinancialAnalyzer = Depends(get_analyzer)):
50
+ """Get key economic indicators."""
51
+ return EconomicIndicators(
52
+ repo_rate=analyzer.rbi_repo_rate,
53
+ inflation_rate=analyzer.indian_inflation_rate
54
+ )
55
+
56
+ # --- Stock Data Endpoints ---
57
+
58
+ @app.get("/api/stocks/list", response_model=Dict[str, str])
59
+ async def get_nifty50_list(fetcher: IndianMarketDataFetcher = Depends(get_data_fetcher)):
60
+ """Get the list of Nifty 50 companies."""
61
+ return fetcher.get_nifty50_companies()
62
+
63
+ @app.get("/api/stocks/{symbol}", response_model=StockDataResponse)
64
+ async def get_stock_data(symbol: str, period: str = "1y", fetcher: IndianMarketDataFetcher = Depends(get_data_fetcher)):
65
+ """Get detailed stock data for a given symbol."""
66
+ stock_data = fetcher.get_stock_data(symbol, period)
67
+ if not stock_data:
68
+ raise HTTPException(status_code=404, detail=f"Data for stock symbol {symbol} could not be fetched.")
69
+ return stock_data
70
+
71
+ # --- Analysis Endpoints ---
72
+
73
+ @app.post("/api/analysis/stock/{symbol}", response_model=StockAnalysisResponse)
74
+ async def analyze_stock(symbol: str, period: str = "1y",
75
+ fetcher: IndianMarketDataFetcher = Depends(get_data_fetcher),
76
+ analyzer: IndianFinancialAnalyzer = Depends(get_analyzer)):
77
+ """Perform basic financial analysis on a stock."""
78
+ stock_data = fetcher.get_stock_data(symbol, period)
79
+ if not stock_data:
80
+ raise HTTPException(status_code=404, detail=f"Data for stock symbol {symbol} could not be fetched.")
81
+
82
+ company_name = NIFTY50_COMPANIES.get(symbol, symbol)
83
+ return analyzer.analyze_indian_stock(stock_data, company_name)
84
+
85
+ @app.post("/api/analysis/swarm/{symbol}", response_model=SwarmAnalysisResponse)
86
+ async def run_swarm_analysis(symbol: str, period: str = "1y",
87
+ fetcher: IndianMarketDataFetcher = Depends(get_data_fetcher),
88
+ analyzer: IndianFinancialAnalyzer = Depends(get_analyzer)):
89
+ """Run AI swarm analysis on a stock."""
90
+ stock_data = fetcher.get_stock_data(symbol, period)
91
+ if not stock_data:
92
+ return SwarmAnalysisResponse(status="error", error=f"Data for stock symbol {symbol} could not be fetched.")
93
+
94
+ company_name = NIFTY50_COMPANIES.get(symbol, symbol)
95
+
96
+ # Perform basic analysis first
97
+ basic_analysis_response = analyzer.analyze_indian_stock(stock_data, company_name)
98
+ basic_analysis_text = basic_analysis_response.basic_analysis
99
+
100
+ # Prepare data for swarm analysis
101
+ market_data_summary = f"""
102
+ Stock: {company_name} ({symbol})
103
+ Current Price: ₹{stock_data.history.get('Close', [0])[-1] if stock_data.history.get('Close') else 'N/A'}
104
+ Market Data Analysis: {basic_analysis_text}
105
+ """
106
+
107
+ # Run swarm analysis
108
+ return create_indian_market_swarm(market_data_summary, company_name)
109
+
110
+ @app.get("/api/analysis/recommendation", response_model=InvestmentRecommendationResponse)
111
+ async def get_investment_recommendation(analyzer: IndianFinancialAnalyzer = Depends(get_analyzer)):
112
+ """Get general investment recommendations."""
113
+ return analyzer.generate_investment_recommendation()
114
+
115
+ # --- Utility Endpoints ---
116
+
117
+ @app.get("/api/health")
118
+ async def health_check():
119
+ """Health check endpoint."""
120
+ return {"status": "ok"}
config.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # config.py
2
+ import os
3
+ from dotenv import load_dotenv
4
+
5
+ load_dotenv()
6
+
7
+ # API Keys
8
+ SWARMS_API_KEY = os.getenv("SWARMS_API_KEY")
9
+ FRED_API_KEY = os.getenv("FRED_API_KEY")
10
+
11
+ # API URLs
12
+ SWARMS_BASE_URL = "https://api.swarms.world"
13
+ FRED_BASE_URL = "https://api.stlouisfed.org/fred/series/observations"
14
+
15
+ # Default Values
16
+ DEFAULT_REPO_RATE = 6.5
17
+ DEFAULT_INFLATION_RATE = 5.7
18
+
19
+ # Nifty 50 Companies (could be moved to a separate file if too large)
20
+ NIFTY50_COMPANIES = {
21
+ 'ADANIENT.NS': 'Adani Enterprises', 'ADANIPORTS.NS': 'Adani Ports',
22
+ 'APOLLOHOSP.NS': 'Apollo Hospitals', 'ASIANPAINT.NS': 'Asian Paints',
23
+ 'AXISBANK.NS': 'Axis Bank', 'BAJAJ-AUTO.NS': 'Bajaj Auto',
24
+ 'BAJFINANCE.NS': 'Bajaj Finance', 'BAJAJFINSV.NS': 'Bajaj Finserv',
25
+ 'BEL.NS': 'Bharat Electronics', 'BHARTIARTL.NS': 'Bharti Airtel',
26
+ 'CIPLA.NS': 'Cipla', 'COALINDIA.NS': 'Coal India', 'DRREDDY.NS': 'Dr Reddy Labs',
27
+ 'EICHERMOT.NS': 'Eicher Motors', 'GRASIM.NS': 'Grasim Industries',
28
+ 'HCLTECH.NS': 'HCL Technologies', 'HDFCBANK.NS': 'HDFC Bank',
29
+ 'HDFCLIFE.NS': 'HDFC Life', 'HEROMOTOCO.NS': 'Hero MotoCorp',
30
+ 'HINDALCO.NS': 'Hindalco', 'HINDUNILVR.NS': 'Hindustan Unilever',
31
+ 'ICICIBANK.NS': 'ICICI Bank', 'INDUSINDBK.NS': 'IndusInd Bank',
32
+ 'INFY.NS': 'Infosys', 'ITC.NS': 'ITC', 'JIOFIN.NS': 'Jio Financial',
33
+ 'JSWSTEEL.NS': 'JSW Steel', 'KOTAKBANK.NS': 'Kotak Mahindra Bank',
34
+ 'LT.NS': 'Larsen & Toubro', 'M&M.NS': 'Mahindra & Mahindra',
35
+ 'MARUTI.NS': 'Maruti Suzuki', 'NESTLEIND.NS': 'Nestle India',
36
+ 'NTPC.NS': 'NTPC', 'ONGC.NS': 'ONGC', 'POWERGRID.NS': 'Power Grid Corp',
37
+ 'RELIANCE.NS': 'Reliance Industries', 'SBILIFE.NS': 'SBI Life',
38
+ 'SHRIRAMFIN.NS': 'Shriram Finance', 'SBIN.NS': 'State Bank of India',
39
+ 'SUNPHARMA.NS': 'Sun Pharma', 'TATACONSUM.NS': 'Tata Consumer',
40
+ 'TCS.NS': 'Tata Consultancy Services', 'TATAMOTORS.NS': 'Tata Motors',
41
+ 'TATASTEEL.NS': 'Tata Steel', 'TECHM.NS': 'Tech Mahindra',
42
+ 'TITAN.NS': 'Titan Company', 'TRENT.NS': 'Trent',
43
+ 'ULTRACEMCO.NS': 'UltraTech Cement', 'WIPRO.NS': 'Wipro'
44
+ }
45
+
46
+ # Sector ETFs
47
+ SECTOR_ETFS = {
48
+ 'BANKBEES.NS': 'Banking',
49
+ 'ITBEES.NS': 'IT',
50
+ 'PHARMABEES.NS': 'Pharma',
51
+ 'AUTOBEES.NS': 'Auto',
52
+ 'NIFTYBEES.NS': 'Metal' # Note: This is Nifty 50 ETF, not Metal. You might want a proper Metal ETF.
53
+ }
54
+
55
+ # Market Indices
56
+ MARKET_INDICES = {
57
+ '^NSEI': 'Nifty 50',
58
+ '^BSESN': 'BSE Sensex',
59
+ 'NIFTY_MIDCAP_100.NS': 'Nifty Midcap 100'
60
+ }
models/__init__.py ADDED
File without changes
models/analysis.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models/analysis.py
2
+ from pydantic import BaseModel
3
+ from typing import List, Optional
4
+
5
+ class StockAnalysisResponse(BaseModel):
6
+ basic_analysis: str
7
+ # Add fields for more structured analysis if needed
8
+
9
+ class SwarmAgentOutput(BaseModel):
10
+ role: str
11
+ content: str
12
+
13
+ class SwarmAnalysisResponse(BaseModel):
14
+ status: str # 'success' or 'error'
15
+ output: Optional[List[SwarmAgentOutput]] = None
16
+ error: Optional[str] = None
17
+
18
+ class InvestmentRecommendationResponse(BaseModel):
19
+ recommendation: str
models/market_data.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models/market_data.py
2
+ from pydantic import BaseModel
3
+ from typing import Dict, Any, List
4
+ import pandas as pd
5
+
6
+ class StockDataResponse(BaseModel):
7
+ history: Dict[str, Any] # Simplified, consider serializing DataFrame properly
8
+ info: Dict[str, Any]
9
+ financials: List[Dict[str, Any]] # List of records for financials
10
+ balance_sheet: List[Dict[str, Any]] # List of records for balance sheet
11
+ cash_flow: List[Dict[str, Any]] # List of records for cash flow
12
+
13
+ class IndexData(BaseModel):
14
+ current_price: float
15
+ change: float
16
+ change_pct: float
17
+ volume: float
18
+
19
+ class SectorPerformance(BaseModel):
20
+ sector: str
21
+ change_pct: float
22
+
23
+ class EconomicIndicators(BaseModel):
24
+ repo_rate: float
25
+ inflation_rate: float
requirements.txt CHANGED
@@ -1,2 +1,10 @@
1
- fastapi
2
- uvicorn[standard]
 
 
 
 
 
 
 
 
 
1
+ fast-api[standard]
2
+ uvicorn[standard]
3
+ sqlalchemy
4
+ yfinance
5
+ pandas
6
+ numpy
7
+ requests
8
+ pydantic
9
+ plotly
10
+ python-dotenv
services/__init__.py ADDED
File without changes
services/analyzer.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # services/analyzer.py
2
+ import numpy as np
3
+ import pandas as pd
4
+ from typing import Dict, Any, Optional
5
+ from services.economic_data import EconomicDataFetcher
6
+ from models.analysis import StockAnalysisResponse, InvestmentRecommendationResponse
7
+ from models.market_data import StockDataResponse
8
+
9
+ class IndianFinancialAnalyzer:
10
+ """Analyze Indian market financial data"""
11
+
12
+ def __init__(self):
13
+ self.economic_data_fetcher = EconomicDataFetcher()
14
+ self.rbi_repo_rate = self.economic_data_fetcher.get_rbi_repo_rate()
15
+ self.indian_inflation_rate = self.economic_data_fetcher.get_indian_inflation_rate()
16
+
17
+ def analyze_indian_stock(self, stock_data: StockDataResponse, company_name: str) -> StockAnalysisResponse:
18
+ """Comprehensive analysis of Indian stock"""
19
+ # Convert history dict back to DataFrame for analysis (basic reconstruction)
20
+ # This assumes the dict keys are column names and values are lists of values
21
+ try:
22
+ hist_df = pd.DataFrame(stock_data.history)
23
+ if 'Date' in hist_df.columns:
24
+ hist_df['Date'] = pd.to_datetime(hist_df['Date'])
25
+ hist_df.set_index('Date', inplace=True)
26
+ except Exception as e:
27
+ print(f"Error reconstructing DataFrame for analysis: {e}")
28
+ hist_df = pd.DataFrame() # Return empty DataFrame on error
29
+
30
+ if hist_df.empty:
31
+ analysis_text = "No data available for analysis"
32
+ else:
33
+ # Current metrics
34
+ current_price = hist_df['Close'].iloc[-1]
35
+ year_high = hist_df['High'].max()
36
+ year_low = hist_df['Low'].min()
37
+
38
+ # Calculate returns
39
+ returns_1m = ((current_price - hist_df['Close'].iloc[-21]) / hist_df['Close'].iloc[-21]) * 100 if len(hist_df) >= 21 else 0
40
+ returns_3m = ((current_price - hist_df['Close'].iloc[-63]) / hist_df['Close'].iloc[-63]) * 100 if len(hist_df) >= 63 else 0
41
+ returns_1y = ((current_price - hist_df['Close'].iloc[0]) / hist_df['Close'].iloc[0]) * 100
42
+
43
+ # Volatility (standard deviation of daily returns)
44
+ daily_returns = hist_df['Close'].pct_change().dropna()
45
+ volatility = daily_returns.std() * np.sqrt(252) * 100 # Annualized volatility
46
+
47
+ # Technical indicators
48
+ sma_20 = hist_df['Close'].rolling(20).mean().iloc[-1] if len(hist_df) >= 20 else np.nan
49
+ sma_50 = hist_df['Close'].rolling(50).mean().iloc[-1] if len(hist_df) >= 50 else np.nan
50
+ ema_20 = hist_df['Close'].ewm(span=20, adjust=False).mean().iloc[-1] if len(hist_df) >= 20 else np.nan
51
+ ema_50 = hist_df['Close'].ewm(span=50, adjust=False).mean().iloc[-1] if len(hist_df) >= 50 else np.nan
52
+ rsi = hist_df['Close'].diff().ewm(span=14, adjust=False).mean() / hist_df['Close'].diff().ewm(span=14, adjust=False).std()
53
+ macd = hist_df['Close'].ewm(span=12, adjust=False).mean() - hist_df['Close'].ewm(span=26, adjust=False).mean()
54
+ macd_signal = macd.ewm(span=9, adjust=False).mean()
55
+ macd_hist = macd - macd_signal
56
+ macd_cross = 'Bullish' if macd.iloc[-1] > macd_signal.iloc[-1] else 'Bearish'
57
+ stochastic_k = ((hist_df['Close'].iloc[-1] - hist_df['Low'].rolling(14).min().iloc[-1]) /
58
+ (hist_df['High'].rolling(14).max().iloc[-1] - hist_df['Low'].rolling(14).min().iloc[-1]) * 100) if len(hist_df) >= 14 else np.nan
59
+ stochastic_d = pd.Series(stochastic_k).rolling(3).mean().iloc[-1] if not pd.isna(stochastic_k) else np.nan
60
+ stochastic_cross = 'Bullish' if stochastic_k > stochastic_d else 'Bearish'
61
+ atr = hist_df['High'].combine(hist_df['Low'], max) - hist_df['Low'].combine(hist_df['Close'].shift(), min)
62
+ atr = atr.rolling(14).mean().iloc[-1] if len(hist_df) >= 14 else np.nan
63
+ obv = (np.sign(hist_df['Close'].diff()) * hist_df['Volume']).fillna(0).cumsum().iloc[-1]
64
+ mfi = 100 - (100 / (1 + (hist_df['Close'].diff().fillna(0) * hist_df['Volume']).rolling(14).sum() /
65
+ (-hist_df['Close'].diff().fillna(0) * hist_df['Volume']).rolling(14).sum())).iloc[-1] if len(hist_df) >= 14 else np.nan
66
+ adx = pd.Series(np.abs(hist_df['High'].diff()) - np.abs(hist_df['Low'].diff())).rolling(14).mean().iloc[-1] if len(hist_df) >= 14 else np.nan
67
+
68
+
69
+ # Determine trend
70
+ if pd.notna(current_price) and pd.notna(sma_20) and pd.notna(sma_50):
71
+ if current_price > sma_20 > sma_50:
72
+ trend = 'Bullish'
73
+ elif current_price < sma_20 < sma_50:
74
+ trend = 'Bearish'
75
+ else:
76
+ trend = 'Neutral'
77
+ else:
78
+ trend = 'Insufficient Data'
79
+
80
+ # Indian market specific analysis
81
+ analysis = f"""
82
+ ## 🇮🇳 Indian Market Analysis for {company_name}
83
+
84
+ ### Current Market Position
85
+ - **Current Price**: ₹{current_price:.2f}
86
+ - **52-Week High**: ₹{year_high:.2f}
87
+ - **52-Week Low**: ₹{year_low:.2f}
88
+ - **Distance from High**: {((current_price - year_high) / year_high * 100):.1f}%
89
+
90
+ ### Returns Performance
91
+ - **1 Month Return**: {returns_1m:.2f}%
92
+ - **3 Month Return**: {returns_3m:.2f}%
93
+ - **1 Year Return**: {returns_1y:.2f}%
94
+ - **Annualized Volatility**: {volatility:.2f}%
95
+
96
+ ### Technical Analysis
97
+ - **20-Day SMA**: ₹{sma_20:.2f}
98
+ - **50-Day SMA**: ₹{sma_50:.2f}
99
+ - **20-Day EMA**: ₹{ema_20:.2f}
100
+ - **50-Day EMA**: ₹{ema_50:.2f}
101
+ - **RSI (14-day)**: {rsi.iloc[-1]:.2f}'
102
+ - **MACD**: {macd.iloc[-1]:.2f} ({macd_cross})
103
+ - **MACD HIST**: {macd_hist.iloc[-1]:.2f}
104
+ - **Stochastic %K**: {stochastic_k:.2f} ({stochastic_cross})
105
+ - **ATR (14-day)**: ₹{atr:.2f}'
106
+ - **OBV**: {obv:.2f}
107
+ - **MFI (14-day)**: {mfi:.2f}'
108
+ - **ADX (14-day)**: {adx:.2f}'
109
+ - **Trend**: {trend}
110
+
111
+ ### Indian Market Context
112
+ - **Relative to RBI Repo Rate ({self.rbi_repo_rate}%)**: {'Attractive' if returns_1y > self.rbi_repo_rate else 'Underperforming'}
113
+ - **Inflation Adjusted Return**: {returns_1y - self.indian_inflation_rate:.2f}%
114
+
115
+ ### Key Company Metrics
116
+ """
117
+
118
+ # Add company-specific info if available
119
+ info = stock_data.info
120
+ if info:
121
+ market_cap = info.get('marketCap', 'N/A')
122
+ pe_ratio = info.get('forwardPE', info.get('trailingPE', 'N/A'))
123
+ dividend_yield = info.get('dividendYield', 0) * 100 if info.get('dividendYield') else 'N/A'
124
+
125
+ market_cap_str = f"₹{market_cap/10000000:.0f} Cr." if isinstance(market_cap, (int, float)) else market_cap
126
+ pe_str = f"{pe_ratio:.2f}" if isinstance(pe_ratio, (int, float)) else 'N/A'
127
+ div_yield_str = f"{dividend_yield:.2f}%" if isinstance(dividend_yield, (int, float)) else 'N/A'
128
+
129
+ analysis += f"""
130
+ - **Market Cap**: {market_cap_str}
131
+ - **P/E Ratio**: {pe_str}
132
+ - **Dividend Yield**: {div_yield_str}
133
+ """
134
+ analysis_text = analysis
135
+
136
+ return StockAnalysisResponse(basic_analysis=analysis_text)
137
+
138
+ def generate_investment_recommendation(self) -> InvestmentRecommendationResponse:
139
+ """Generate investment recommendation based on Indian market conditions"""
140
+
141
+ recommendation = f"""
142
+ ## 📊 Investment Recommendation (Indian Market Context)
143
+
144
+ ### Risk Assessment
145
+ - **Market Risk**: Indian equity markets are subject to high volatility
146
+ - **Currency Risk**: INR fluctuations affect returns for foreign investors
147
+ - **Regulatory Risk**: SEBI regulations and policy changes impact
148
+
149
+ ### Recommendation Framework
150
+ Based on current Indian market conditions:
151
+
152
+ 1. **Conservative Investors**: Consider Large Cap stocks with dividend yield
153
+ 2. **Moderate Risk**: Mid Cap stocks with strong fundamentals
154
+ 3. **Aggressive**: Small Cap and sector-specific opportunities
155
+
156
+ ### Indian Market Specific Factors
157
+ - **Monsoon Impact**: Agricultural and rural demand dependency
158
+ - **Festival Season**: Seasonal consumption patterns
159
+ - **Government Policy**: Budget announcements and reforms
160
+ - **FII/DII Flows**: Foreign and domestic institutional investor sentiment
161
+
162
+ ### Economic Context
163
+ - **RBI Repo Rate**: {self.rbi_repo_rate}%
164
+ - **Inflation Rate**: {self.indian_inflation_rate}%
165
+
166
+ ### Tax Implications (Indian Investors)
167
+ - **Short Term Capital Gains**: 15% (< 1 year)
168
+ - **Long Term Capital Gains**: 10% on gains > ₹1 Lakh (> 1 year)
169
+ - **Dividend Tax**: TDS as per income tax slab
170
+
171
+ **Disclaimer**: This is for educational purposes only. Please consult a SEBI registered investment advisor.
172
+ """
173
+
174
+ return InvestmentRecommendationResponse(recommendation=recommendation)
services/data_fetcher.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import yfinance as yf
2
+ import pandas as pd
3
+ from typing import Dict, Optional, Any, List
4
+ from config import NIFTY50_COMPANIES, MARKET_INDICES, SECTOR_ETFS
5
+ from models.market_data import StockDataResponse, IndexData, SectorPerformance
6
+
7
+ class IndianMarketDataFetcher:
8
+ """Fetch real-time Indian market data using yfinance"""
9
+
10
+ def get_nifty50_companies(self) -> Dict[str, str]:
11
+ """Get Nifty 50 company list"""
12
+ return NIFTY50_COMPANIES
13
+
14
+ def get_market_indices(self) -> Dict[str, IndexData]:
15
+ """Fetch Indian market indices data"""
16
+ indices_data = {}
17
+ for symbol, name in MARKET_INDICES.items():
18
+ try:
19
+ ticker = yf.Ticker(symbol)
20
+ hist = ticker.history(period="5d")
21
+
22
+ if not hist.empty:
23
+ current_price = hist['Close'].iloc[-1]
24
+ previous_close = hist['Close'].iloc[-2] if len(hist) > 1 else current_price
25
+ change = current_price - previous_close
26
+ change_pct = (change / previous_close) * 100
27
+
28
+ indices_data[name] = IndexData(
29
+ current_price=current_price,
30
+ change=change,
31
+ change_pct=change_pct,
32
+ volume=hist['Volume'].iloc[-1] if 'Volume' in hist else 0
33
+ )
34
+ except Exception as e:
35
+ print(f"Could not fetch data for {name}: {e}")
36
+ return indices_data
37
+
38
+ def _process_financial_df(self, df: pd.DataFrame, date_col_name: str = 'Date') -> List[Dict[str, Any]]:
39
+ """
40
+ Helper to process yfinance financial DataFrames (financials, balance_sheet, cashflow).
41
+ It transposes the DataFrame, resets the index, and converts the date column
42
+ to string format, then returns a list of dictionaries.
43
+ """
44
+ if df.empty:
45
+ return []
46
+
47
+ # Transpose the DataFrame so dates become the index, and metrics become columns
48
+ df_transposed = df.T
49
+
50
+ # Reset the index to turn the date index into a regular column,
51
+ # then convert that column to string format.
52
+ df_processed = df_transposed.reset_index()
53
+ df_processed.rename(columns={'index': date_col_name}, inplace=True)
54
+
55
+ # Ensure the date column is explicitly converted to string type (YYYY-MM-DD)
56
+ if pd.api.types.is_datetime64_any_dtype(df_processed[date_col_name]):
57
+ df_processed[date_col_name] = df_processed[date_col_name].dt.strftime('%Y-%m-%d')
58
+
59
+ # Fill any remaining NaN values with 0 to ensure Pydantic doesn't complain about None
60
+ df_processed.fillna(0, inplace=True)
61
+
62
+ return df_processed.to_dict('records')
63
+
64
+ def get_stock_data(self, symbol: str="RELIANCE", period: str = "1y") -> Optional[StockDataResponse]:
65
+ """Fetch stock data for analysis"""
66
+ if not symbol.endswith(".NS"):
67
+ symbol = f"{symbol}.NS"
68
+ try:
69
+ ticker = yf.Ticker(symbol)
70
+
71
+ # Get historical data
72
+ hist = ticker.history(period=period)
73
+ if not hist.empty:
74
+ # Reset index to make 'Date' a column, then convert to string
75
+ hist_processed = hist.reset_index()
76
+ hist_processed['Date'] = hist_processed['Date'].dt.strftime('%Y-%m-%d')
77
+ hist_dict = hist_processed.to_dict(orient='list')
78
+ else:
79
+ hist_dict = {}
80
+
81
+ # Get company info
82
+ info = ticker.info
83
+
84
+ # Process financials, balance sheet, and cash flow using the helper function
85
+ financials_list = self._process_financial_df(ticker.financials)
86
+ balance_sheet_list = self._process_financial_df(ticker.balance_sheet)
87
+ cash_flow_list = self._process_financial_df(ticker.cashflow)
88
+
89
+ return StockDataResponse(
90
+ history=hist_dict,
91
+ info=info,
92
+ financials=financials_list,
93
+ balance_sheet=balance_sheet_list,
94
+ cash_flow=cash_flow_list
95
+ )
96
+ except Exception as e:
97
+ print(f"Error fetching data for {symbol}: {e}")
98
+ return None
99
+
100
+ def get_sector_performance(self) -> Dict[str, SectorPerformance]:
101
+ """Get sector-wise performance"""
102
+ sector_data = {}
103
+ for etf, sector in SECTOR_ETFS.items():
104
+ try:
105
+ ticker = yf.Ticker(etf)
106
+ hist = ticker.history(period="5d")
107
+ if not hist.empty:
108
+ current = hist['Close'].iloc[-1]
109
+ prev = hist['Close'].iloc[-2] if len(hist) > 1 else current
110
+ change_pct = ((current - prev) / prev) * 100
111
+ sector_data[sector] = SectorPerformance(sector=sector, change_pct=change_pct)
112
+ except Exception as e:
113
+ print(f"Could not fetch sector data for {etf} ({sector}): {e}")
114
+ return sector_data
115
+
116
+ def get_company_name(self, symbol: str) -> Optional[str]:
117
+ """Get company name from symbol"""
118
+ return NIFTY50_COMPANIES.get(symbol)
services/economic_data.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # services/economic_data.py
2
+ import os
3
+ import requests
4
+ from config import FRED_API_KEY, FRED_BASE_URL, DEFAULT_REPO_RATE, DEFAULT_INFLATION_RATE
5
+ from models.market_data import EconomicIndicators
6
+
7
+ class EconomicDataFetcher:
8
+ """Fetch economic data from FRED API"""
9
+
10
+ def __init__(self):
11
+ self.fred_api_key = FRED_API_KEY
12
+ self.base_url = FRED_BASE_URL
13
+
14
+ def _fetch_fred_series(self, series_id: str) -> EconomicIndicators:
15
+ """Helper to fetch a single series from FRED"""
16
+ try:
17
+ params = {
18
+ 'series_id': series_id,
19
+ 'api_key': self.fred_api_key,
20
+ 'file_type': 'json',
21
+ 'sort_order': 'desc',
22
+ 'limit': 1
23
+ }
24
+
25
+ response = requests.get(self.base_url, params=params)
26
+ response.raise_for_status() # Raise an exception for bad status codes
27
+ data = response.json()
28
+
29
+ if 'observations' in data and len(data['observations']) > 0:
30
+ value_str = data['observations'][0]['value']
31
+ # Handle cases where FRED returns '.' for no data
32
+ if value_str != '.':
33
+ return float(value_str)
34
+ return None
35
+ except Exception as e:
36
+ print(f"Error fetching FRED series {series_id}: {e}") # Use logging
37
+ return None
38
+
39
+ def get_rbi_repo_rate(self) -> float:
40
+ """Fetch RBI repo rate approximation from FRED (INTDSRINM193N)"""
41
+ # Note: This might not be the exact repo rate, but a proxy
42
+ fetched_rate = self._fetch_fred_series('INTDSRINM193N')
43
+ return fetched_rate if fetched_rate is not None else DEFAULT_REPO_RATE
44
+
45
+ def get_indian_inflation_rate(self) -> float:
46
+ """Fetch Indian inflation rate from FRED (FPCPITOTLZGIND)"""
47
+ fetched_rate = self._fetch_fred_series('FPCPITOTLZGIND')
48
+ return fetched_rate if fetched_rate is not None else DEFAULT_INFLATION_RATE
services/swarm_service.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # services/swarm_service.py
2
+ import os
3
+ import requests
4
+ from config import SWARMS_API_KEY, SWARMS_BASE_URL
5
+ from models.analysis import SwarmAnalysisResponse
6
+
7
+ # Swarms API Integration
8
+ API_KEY = SWARMS_API_KEY
9
+ BASE_URL = SWARMS_BASE_URL
10
+
11
+ headers = {
12
+ "x-api-key": API_KEY,
13
+ "Content-Type": "application/json"
14
+ }
15
+
16
+ def create_indian_market_swarm(market_data: str, company_name: str) -> SwarmAnalysisResponse:
17
+ """Create swarm for Indian market analysis using Swarms API"""
18
+
19
+ INDIAN_MARKET_CONTROLLER_PROMPT = f"""
20
+ You are an Indian market financial controller with expertise in NSE, BSE, and Indian economic conditions.
21
+ Analyze the provided data considering:
22
+ - RBI monetary policy and repo rates
23
+ - Indian sectoral performance
24
+ - Monsoon and seasonal factors
25
+ - Government policy impacts
26
+ - FII/DII flows
27
+ Provide analysis in Indian Rupees and local market context.
28
+ Company: {company_name}
29
+ """
30
+
31
+ INDIAN_REVENUE_ANALYST_PROMPT = """
32
+ You are an Indian revenue analyst specializing in Indian companies.
33
+ Focus on:
34
+ - Quarterly vs Annual revenue patterns (Indian financial year: Apr-Mar)
35
+ - Domestic vs Export revenue mix
36
+ - GST impact analysis
37
+ - Rural vs Urban market performance
38
+ - Impact of Indian festivals and seasons
39
+ """
40
+
41
+ INDIAN_RATIO_ANALYST_PROMPT = """
42
+ You are an Indian financial ratio analyst.
43
+ Compare ratios with:
44
+ - Nifty 50 averages
45
+ - Sector-specific Indian benchmarks
46
+ - Historical Indian market multiples
47
+ - Consider Indian accounting standards (Ind AS)
48
+ """
49
+
50
+ swarm_config = {
51
+ "name": "Indian Market Analysis Swarm",
52
+ "description": "AI swarm specialized for Indian equity market analysis",
53
+ "agents": [
54
+ {
55
+ "agent_name": "Indian Market Controller",
56
+ "system_prompt": INDIAN_MARKET_CONTROLLER_PROMPT,
57
+ "model_name": "gpt-4o",
58
+ "role": "worker",
59
+ "max_loops": 1,
60
+ "max_tokens": 4096,
61
+ "temperature": 0.3,
62
+ },
63
+ {
64
+ "agent_name": "Indian Revenue Analyst",
65
+ "system_prompt": INDIAN_REVENUE_ANALYST_PROMPT,
66
+ "model_name": "gpt-4o",
67
+ "role": "worker",
68
+ "max_loops": 1,
69
+ "max_tokens": 4096,
70
+ "temperature": 0.3,
71
+ },
72
+ {
73
+ "agent_name": "Indian Ratio Analyst",
74
+ "system_prompt": INDIAN_RATIO_ANALYST_PROMPT,
75
+ "model_name": "gpt-4o",
76
+ "role": "worker",
77
+ "max_loops": 1,
78
+ "max_tokens": 4096,
79
+ "temperature": 0.3,
80
+ }
81
+ ],
82
+ "max_loops": 1,
83
+ "swarm_type": "SequentialWorkflow",
84
+ "task": f"Analyze the following Indian market data for {company_name}:\n\n{market_data}"
85
+ }
86
+
87
+ try:
88
+ response = requests.post(
89
+ f"{BASE_URL}/v1/swarm/completions",
90
+ headers=headers,
91
+ json=swarm_config,
92
+ timeout=120
93
+ )
94
+ response.raise_for_status()
95
+ # Assuming the response JSON matches your model structure
96
+ result_data = response.json()
97
+ # Map the response to your Pydantic model
98
+ # This might need adjustment based on the actual Swarms API response structure
99
+ if result_data.get("status") == "success":
100
+ # Ensure 'output' is a list of dicts with 'role' and 'content'
101
+ raw_outputs = result_data.get("output", [])
102
+ processed_outputs = [
103
+ {"role": out.get("role", f"Agent {i+1}"), "content": out.get("content", "")}
104
+ for i, out in enumerate(raw_outputs) if isinstance(out, dict)
105
+ ]
106
+ return SwarmAnalysisResponse(status="success", output=processed_outputs)
107
+ else:
108
+ return SwarmAnalysisResponse(status="error", error=result_data.get("error", "Unknown error from swarm service"))
109
+
110
+ except requests.exceptions.RequestException as e:
111
+ return SwarmAnalysisResponse(status="error", error=f"Network error calling swarm service: {str(e)}")
112
+ except Exception as e:
113
+ return SwarmAnalysisResponse(status="error", error=f"Swarm analysis failed: {str(e)}")
utils/__init__.py ADDED
File without changes
utils/helpers.py ADDED
File without changes