import time import requests import yfinance as yf import pandas as pd from datetime import datetime, timedelta from typing import List, Dict, Any, Optional, Tuple from app.config import settings from app.models.fund_models import FundMeta, NAVData, FundNAVResponse, MarketIndex, MarketIndicesResponse class MutualFundDataFetcher: """Fetch mutual fund data from MFAPI.in and Yahoo Finance for indices""" def __init__(self): self.mfapi_base = settings.MFAPI_BASE_URL self._schemes_cache = None self._schemes_cache_time = None self._cache_ttl = settings.CACHE_TTL # 1 hour cache def get_all_schemes(self, force_refresh:bool=False) -> List[Dict[str, Any]]: """Fetch all mutual fund schemes""" # Check if cache is valid if (not force_refresh and self._schemes_cache is not None and self._schemes_cache_time is not None and (time.time() - self._schemes_cache_time) < self._cache_ttl): return self._schemes_cache # Fetch from API try: response = requests.get(self.mfapi_base) if response.status_code == 200: schemes = response.json() # Update cache self._schemes_cache = schemes self._schemes_cache_time = time.time() return schemes else: return [] except Exception as e: print(f"Error fetching schemes: {e}") return [] def search_schemes(self, name: Optional[str] = None, code: Optional[str] = None) -> List[Dict[str, Any]]: """Search schemes by name or code""" schemes = self.get_all_schemes() if not name and not code: return schemes filtered_schemes = [] for scheme in schemes: # Check name match (case insensitive) if name: scheme_name = scheme.get('schemeName', '').lower() if name.lower() not in scheme_name: continue # Check code match if code: scheme_code = str(scheme.get('schemeCode', '')) if scheme_code != str(code): continue filtered_schemes.append(scheme) return filtered_schemes def get_fund_nav_history(self, scheme_code: str) -> Tuple[pd.DataFrame, FundMeta]: """Fetch NAV history for a specific scheme""" try: url = f"{self.mfapi_base}/{scheme_code}" response = requests.get(url) if response.status_code == 200: data = response.json() nav_data = data.get('data', []) # Convert to DataFrame df = pd.DataFrame(nav_data) if not df.empty: df['date'] = pd.to_datetime(df['date'], format='%d-%m-%Y') df['nav'] = pd.to_numeric(df['nav']) df = df.sort_values('date') # Create FundMeta object meta_data = data.get('meta', {}) fund_meta = FundMeta( fund_house=meta_data.get('fund_house'), scheme_type=meta_data.get('scheme_type'), scheme_category=meta_data.get('scheme_category'), scheme_code=str(meta_data.get('scheme_code', scheme_code)) ) return df, fund_meta else: return pd.DataFrame(), FundMeta() except Exception as e: print(f"Error fetching NAV data: {e}") return pd.DataFrame(), FundMeta() def get_market_indices(self) -> MarketIndicesResponse: """Fetch Indian market indices using Yahoo Finance""" indices = { '^NSEI': 'Nifty 50', '^BSESN': 'BSE Sensex', '^NSEBANK': 'Nifty Bank', '^CNXIT': 'Nifty IT', '^NSEMDCP50': 'Nifty Midcap 50', 'NIFTYSMLCAP50.NS': 'Nifty Smallcap 50', '^CNXPHARMA': 'Nifty Pharma', '^CNXAUTO': 'Nifty Auto', '^CNXFMCG': 'Nifty FMCG', '^CNXENERGY': 'Nifty Energy', '^CNXREALTY': 'Nifty Realty', '^NSMIDCP': 'Nifty Next 50', } indices_data = [] for symbol, name in indices.items(): try: ticker = yf.Ticker(symbol) hist = ticker.history(period="5d") if not hist.empty: current_price = hist['Close'].iloc[-1] previous_close = hist['Close'].iloc[-2] if len(hist) > 1 else current_price change = current_price - previous_close change_pct = (change / previous_close) * 100 indices_data.append(MarketIndex( name=name, symbol=symbol, current_price=current_price, change=change, change_pct=change_pct )) except Exception as e: print(f"Could not fetch {name}: {e}") return MarketIndicesResponse( indices=indices_data, last_updated=datetime.now() )