Spaces:
Running
Running
| """ | |
| Module-specific adapters for integrating the local database | |
| with calendar_scraper, fundamental_analysis, and news_scraper | |
| """ | |
| from datetime import datetime | |
| from typing import Dict, Any, List, Optional | |
| from pathlib import Path | |
| import sys | |
| # Add src to path if needed | |
| sys.path.append(str(Path(__file__).parent.parent)) | |
| from db.local_database import LocalDatabase, DatabaseEntry, DataType | |
| class CalendarAdapter: | |
| """ | |
| Adapter for calendar_scraper module | |
| Handles earnings_events and economic_events | |
| """ | |
| def __init__(self, db: Optional[LocalDatabase] = None): | |
| self.db = db or LocalDatabase() | |
| ### EARINGS ### | |
| def save_earnings_event(self, date: str, ticker: str, event_data: Dict[str, Any], | |
| expiry_days: int = 30) -> bool: | |
| """ | |
| Save earnings event to database | |
| Args: | |
| date: Event date (YYYY-MM-DD) | |
| ticker: Stock ticker | |
| event_data: Event details (company, time, eps, revenue, market_cap) | |
| expiry_days: Data expiry in days | |
| Returns: | |
| True if successful | |
| """ | |
| entry = DatabaseEntry( | |
| date=date, | |
| data_type=DataType.EARNINGS.value, | |
| ticker=ticker.upper(), | |
| data={ | |
| 'event_type': 'earnings', | |
| **event_data | |
| }, | |
| metadata={ | |
| 'source': 'calendar_scraper', | |
| 'scraper': 'earnings' | |
| } | |
| ) | |
| return self.db.save(entry, expiry_days=expiry_days) | |
| def get_earnings_events(self, ticker: str, date_from: str = None, | |
| date_to: str = None) -> List[DatabaseEntry]: | |
| """Get earnings events for ticker""" | |
| entries = self.db.query( | |
| ticker=ticker.upper(), | |
| data_type=DataType.EARNINGS.value, | |
| date_from=date_from, | |
| date_to=date_to | |
| ) | |
| # Filter for earnings events only | |
| return [e for e in entries if e.data.get('event_type') == 'earnings'] | |
| ### ECONOMIC EVENTS ### | |
| def save_economic_event(self, date: str, event_data: Dict[str, Any], | |
| expiry_days: int = 7) -> bool: | |
| """ | |
| Save economic event to database | |
| Args: | |
| date: Event date (YYYY-MM-DD) | |
| event_data: Event details (country, importance, event, actual, forecast, previous) | |
| expiry_days: Data expiry in days | |
| Returns: | |
| True if successful | |
| """ | |
| # Use country as ticker for economic events | |
| ticker = event_data.get('country', 'GLOBAL').upper().replace(' ', '_') | |
| entry = DatabaseEntry( | |
| date=date, | |
| data_type=DataType.ECONOMIC_EVENTS.value, | |
| ticker=ticker, | |
| data={ | |
| 'event_type': 'economic', | |
| **event_data | |
| }, | |
| metadata={ | |
| 'source': 'calendar_scraper', | |
| 'scraper': 'economic' | |
| } | |
| ) | |
| return self.db.save(entry, expiry_days=expiry_days) | |
| def get_economic_events(self, country: str = None, date_from: str = None, | |
| date_to: str = None) -> List[DatabaseEntry]: | |
| """Get economic events""" | |
| ticker = country.upper().replace(' ', '_') if country else None | |
| entries = self.db.query( | |
| ticker=ticker, | |
| data_type=DataType.ECONOMIC_EVENTS.value, | |
| date_from=date_from, | |
| date_to=date_to | |
| ) | |
| # Filter for economic events only | |
| return [e for e in entries if e.data.get('event_type') == 'economic'] | |
| ### DIVIDENDS ### | |
| def get_dividends_events(self, ticker: str, date_from: str = None, | |
| date_to: str = None) -> List[DatabaseEntry]: | |
| """Get dividend events for ticker""" | |
| entries = self.db.query( | |
| ticker=ticker.upper(), | |
| data_type=DataType.DIVIDENDS.value, | |
| date_from=date_from, | |
| date_to=date_to | |
| ) | |
| # Filter for earnings events only | |
| return [e for e in entries if e.data.get('event_type') == 'dividend'] | |
| ### IPOs ### | |
| def get_ipo_events(self, ticker: str, date_from: str = None, | |
| date_to: str = None) -> List[DatabaseEntry]: | |
| """Get ipo events for ticker""" | |
| entries = self.db.query( | |
| ticker=ticker.upper(), | |
| data_type=DataType.IPO.value, | |
| date_from=date_from, | |
| date_to=date_to | |
| ) | |
| # Filter for earnings events only | |
| return [e for e in entries if e.data.get('event_type') == 'ipo'] | |
| ## STOCK SPLITS ### | |
| def get_stock_split_events(self, ticker: str, date_from: str = None, | |
| date_to: str = None) -> List[DatabaseEntry]: | |
| """Get stock split events for ticker""" | |
| entries = self.db.query( | |
| ticker=ticker.upper(), | |
| data_type=DataType.STOCK_SPLIT.value, | |
| date_from=date_from, | |
| date_to=date_to | |
| ) | |
| # Filter for earnings events only | |
| return [e for e in entries if e.data.get('event_type') == 'stock_split'] | |
| class FundamentalAdapter: | |
| """ | |
| Adapter for fundamental_analysis module | |
| Handles financial metrics and investment decisions | |
| """ | |
| def __init__(self, db: Optional[LocalDatabase] = None): | |
| self.db = db or LocalDatabase() | |
| def save_financial_metrics(self, date: str, ticker: str, metrics: Dict[str, Any], | |
| expiry_days: int = 1) -> bool: | |
| """ | |
| Save financial metrics to database | |
| Args: | |
| date: Analysis date (YYYY-MM-DD) | |
| ticker: Stock ticker | |
| metrics: Financial metrics from calculator.py | |
| expiry_days: Data expiry in days (financial data changes daily) | |
| Returns: | |
| True if successful | |
| """ | |
| entry = DatabaseEntry( | |
| date=date, | |
| data_type=DataType.FUNDAMENTAL.value, | |
| ticker=ticker.upper(), | |
| data={ | |
| 'analysis_type': 'metrics', | |
| 'metrics': metrics | |
| }, | |
| metadata={ | |
| 'source': 'fundamental_analysis', | |
| 'module': 'calculator' | |
| } | |
| ) | |
| return self.db.save(entry, expiry_days=expiry_days) | |
| def save_investment_decision(self, date: str, ticker: str, decision: Dict[str, Any], | |
| expiry_days: int = 1) -> bool: | |
| """ | |
| Save investment decision to database | |
| Args: | |
| date: Decision date (YYYY-MM-DD) | |
| ticker: Stock ticker | |
| decision: Investment decision from decision_maker.py | |
| expiry_days: Data expiry in days | |
| Returns: | |
| True if successful | |
| """ | |
| entry = DatabaseEntry( | |
| date=date, | |
| data_type=DataType.FUNDAMENTAL.value, | |
| ticker=ticker.upper(), | |
| data={ | |
| 'analysis_type': 'decision', | |
| 'recommendation': decision.get('recommendation'), | |
| 'score': decision.get('final_score'), | |
| 'confidence': decision.get('confidence'), | |
| 'reasoning': decision.get('reasoning'), | |
| 'key_metrics': decision.get('key_metrics'), | |
| 'category_scores': decision.get('category_scores') | |
| }, | |
| metadata={ | |
| 'source': 'fundamental_analysis', | |
| 'module': 'decision_maker' | |
| } | |
| ) | |
| return self.db.save(entry, expiry_days=expiry_days) | |
| def save_sector_analysis(self, date: str, sector: str, analysis: Dict[str, Any], | |
| expiry_days: int = 7) -> bool: | |
| """ | |
| Save sector analysis to database | |
| Args: | |
| date: Analysis date (YYYY-MM-DD) | |
| sector: Sector name (e.g., "Technology") | |
| analysis: Sector comparison data | |
| expiry_days: Data expiry in days | |
| Returns: | |
| True if successful | |
| """ | |
| # Use sector name as ticker | |
| ticker = f"SECTOR_{sector.upper().replace(' ', '_')}" | |
| entry = DatabaseEntry( | |
| date=date, | |
| data_type=DataType.FUNDAMENTAL.value, | |
| ticker=ticker, | |
| data={ | |
| 'analysis_type': 'sector', | |
| 'sector': sector, | |
| **analysis | |
| }, | |
| metadata={ | |
| 'source': 'fundamental_analysis', | |
| 'module': 'sector_analyzer' | |
| } | |
| ) | |
| return self.db.save(entry, expiry_days=expiry_days) | |
| def get_financial_metrics(self, ticker: str, date: str = None) -> Optional[DatabaseEntry]: | |
| """Get latest financial metrics for ticker""" | |
| if date: | |
| return self.db.get(date, DataType.FUNDAMENTAL.value, ticker.upper()) | |
| # Get most recent | |
| entries = self.db.query( | |
| ticker=ticker.upper(), | |
| data_type=DataType.FUNDAMENTAL.value, | |
| limit=1 | |
| ) | |
| return entries[0] if entries else None | |
| def get_investment_decisions(self, ticker: str, date_from: str = None, | |
| date_to: str = None) -> List[DatabaseEntry]: | |
| """Get investment decision history for ticker""" | |
| entries = self.db.query( | |
| ticker=ticker.upper(), | |
| data_type=DataType.FUNDAMENTAL.value, | |
| date_from=date_from, | |
| date_to=date_to | |
| ) | |
| # Filter for decisions only | |
| return [e for e in entries if e.data.get('analysis_type') == 'decision'] | |
| class NewsAdapter: | |
| """ | |
| Adapter for news_scraper module | |
| Handles news articles and sentiment analysis | |
| """ | |
| def __init__(self, db: Optional[LocalDatabase] = None): | |
| self.db = db or LocalDatabase() | |
| def save_news_article(self, date: str, ticker: str, article: Dict[str, Any], | |
| expiry_days: int = 30) -> bool: | |
| """ | |
| Save news article to database | |
| Args: | |
| date: Article date (YYYY-MM-DD) | |
| ticker: Stock ticker | |
| article: Article data (title, content, source, url, etc.) | |
| expiry_days: Data expiry in days | |
| Returns: | |
| True if successful | |
| """ | |
| entry = DatabaseEntry( | |
| date=date, | |
| data_type=DataType.NEWS.value, | |
| ticker=ticker.upper(), | |
| data={ | |
| 'content_type': 'article', | |
| **article | |
| }, | |
| metadata={ | |
| 'source': 'news_scraper', | |
| 'scraper': article.get('source', 'unknown') | |
| } | |
| ) | |
| return self.db.save(entry, expiry_days=expiry_days) | |
| def save_sentiment_analysis(self, date: str, ticker: str, sentiment: Dict[str, Any], | |
| expiry_days: int = 7) -> bool: | |
| """ | |
| Save sentiment analysis to database | |
| Args: | |
| date: Analysis date (YYYY-MM-DD) | |
| ticker: Stock ticker | |
| sentiment: Sentiment analysis results | |
| expiry_days: Data expiry in days | |
| Returns: | |
| True if successful | |
| """ | |
| entry = DatabaseEntry( | |
| date=date, | |
| data_type=DataType.NEWS.value, | |
| ticker=ticker.upper(), | |
| data={ | |
| 'content_type': 'sentiment', | |
| **sentiment | |
| }, | |
| metadata={ | |
| 'source': 'news_scraper', | |
| 'module': 'sentiment_analysis' | |
| } | |
| ) | |
| return self.db.save(entry, expiry_days=expiry_days) | |
| def get_news_articles(self, ticker: str, date_from: str = None, | |
| date_to: str = None) -> List[DatabaseEntry]: | |
| """Get news articles for ticker""" | |
| entries = self.db.query( | |
| ticker=ticker.upper(), | |
| data_type=DataType.NEWS.value, | |
| date_from=date_from, | |
| date_to=date_to | |
| ) | |
| # Filter for articles only | |
| return [e for e in entries if e.data.get('content_type') == 'article'] | |
| def get_sentiment_history(self, ticker: str, date_from: str = None, | |
| date_to: str = None) -> List[DatabaseEntry]: | |
| """Get sentiment analysis history for ticker""" | |
| entries = self.db.query( | |
| ticker=ticker.upper(), | |
| data_type=DataType.NEWS.value, | |
| date_from=date_from, | |
| date_to=date_to | |
| ) | |
| # Filter for sentiment only | |
| return [e for e in entries if e.data.get('content_type') == 'sentiment'] | |
| class TechnicalAnalysisAdapter: | |
| """ | |
| Adapter for technical analysis data | |
| Can be used for price data, indicators, signals | |
| """ | |
| def __init__(self, db: Optional[LocalDatabase] = None): | |
| self.db = db or LocalDatabase() | |
| def save_technical_indicators(self, date: str, ticker: str, indicators: Dict[str, Any], | |
| expiry_days: int = 1) -> bool: | |
| """ | |
| Save technical indicators to database | |
| Args: | |
| date: Analysis date (YYYY-MM-DD) | |
| ticker: Stock ticker | |
| indicators: Technical indicators (RSI, MACD, etc.) | |
| expiry_days: Data expiry in days | |
| Returns: | |
| True if successful | |
| """ | |
| entry = DatabaseEntry( | |
| date=date, | |
| data_type=DataType.TECHNICAL_ANALYSIS.value, | |
| ticker=ticker.upper(), | |
| data={ | |
| 'analysis_type': 'indicators', | |
| **indicators | |
| }, | |
| metadata={ | |
| 'source': 'technical_analysis' | |
| } | |
| ) | |
| return self.db.save(entry, expiry_days=expiry_days) | |
| def save_trading_signal(self, date: str, ticker: str, signal: Dict[str, Any], | |
| expiry_days: int = 1) -> bool: | |
| """ | |
| Save trading signal to database | |
| Args: | |
| date: Signal date (YYYY-MM-DD) | |
| ticker: Stock ticker | |
| signal: Trading signal data | |
| expiry_days: Data expiry in days | |
| Returns: | |
| True if successful | |
| """ | |
| entry = DatabaseEntry( | |
| date=date, | |
| data_type=DataType.TECHNICAL_ANALYSIS.value, | |
| ticker=ticker.upper(), | |
| data={ | |
| 'analysis_type': 'signal', | |
| **signal | |
| }, | |
| metadata={ | |
| 'source': 'technical_analysis' | |
| } | |
| ) | |
| return self.db.save(entry, expiry_days=expiry_days) | |
| def get_technical_indicators(self, ticker: str, date_from: str = None, | |
| date_to: str = None) -> List[DatabaseEntry]: | |
| """Get technical indicators for ticker""" | |
| entries = self.db.query( | |
| ticker=ticker.upper(), | |
| data_type=DataType.TECHNICAL_ANALYSIS.value, | |
| date_from=date_from, | |
| date_to=date_to | |
| ) | |
| # Filter for indicators only | |
| return [e for e in entries if e.data.get('analysis_type') == 'indicators'] | |
| def get_trading_signals(self, ticker: str, date_from: str = None, | |
| date_to: str = None) -> List[DatabaseEntry]: | |
| """Get trading signals for ticker""" | |
| entries = self.db.query( | |
| ticker=ticker.upper(), | |
| data_type=DataType.TECHNICAL_ANALYSIS.value, | |
| date_from=date_from, | |
| date_to=date_to | |
| ) | |
| # Filter for signals only | |
| return [e for e in entries if e.data.get('analysis_type') == 'signal'] | |
| # Additional methods for CalendarAdapter | |
| def _add_calendar_methods(): | |
| """Add missing methods to CalendarAdapter""" | |
| def save_ipo_event(self, date: str, ticker: str, event_data: Dict[str, Any], | |
| expiry_days: int = 90) -> bool: | |
| """Save IPO event to database""" | |
| entry = DatabaseEntry( | |
| date=date, | |
| data_type=DataType.IPO.value, | |
| ticker=ticker.upper(), | |
| data={ | |
| 'event_type': 'ipo', | |
| **event_data | |
| }, | |
| metadata={ | |
| 'source': 'calendar_scraper', | |
| 'scraper': 'ipo' | |
| } | |
| ) | |
| return self.db.save(entry, expiry_days=expiry_days) | |
| def save_stock_split_event(self, date: str, ticker: str, event_data: Dict[str, Any], | |
| expiry_days: int = 90) -> bool: | |
| """Save stock split event to database""" | |
| entry = DatabaseEntry( | |
| date=date, | |
| data_type=DataType.STOCK_SPLIT.value, | |
| ticker=ticker.upper(), | |
| data={ | |
| 'event_type': 'stock_split', | |
| **event_data | |
| }, | |
| metadata={ | |
| 'source': 'calendar_scraper', | |
| 'scraper': 'stock_split' | |
| } | |
| ) | |
| return self.db.save(entry, expiry_days=expiry_days) | |
| def save_dividend_event(self, date: str, ticker: str, event_data: Dict[str, Any], | |
| expiry_days: int = 90) -> bool: | |
| """Save dividend event to database""" | |
| entry = DatabaseEntry( | |
| date=date, | |
| data_type=DataType.DIVIDENDS.value, | |
| ticker=ticker.upper(), | |
| data={ | |
| 'event_type': 'dividend', | |
| **event_data | |
| }, | |
| metadata={ | |
| 'source': 'calendar_scraper', | |
| 'scraper': 'dividend' | |
| } | |
| ) | |
| return self.db.save(entry, expiry_days=expiry_days) | |
| # Add methods to CalendarAdapter class | |
| CalendarAdapter.save_ipo_event = save_ipo_event | |
| CalendarAdapter.save_stock_split_event = save_stock_split_event | |
| CalendarAdapter.save_dividend_event = save_dividend_event | |
| _add_calendar_methods() | |
| # Additional method for FundamentalAdapter | |
| def _add_fundamental_methods(): | |
| """Add missing methods to FundamentalAdapter""" | |
| def save_fundamental_analysis(self, date: str, ticker: str, analysis_data: Dict[str, Any], | |
| expiry_days: int = 30) -> bool: | |
| """ | |
| Save complete fundamental analysis to database | |
| Includes last_processed_datetime for tracking | |
| """ | |
| entry = DatabaseEntry( | |
| date=date, | |
| data_type=DataType.FUNDAMENTAL.value, | |
| ticker=ticker.upper(), | |
| data={ | |
| 'analysis_type': 'complete', | |
| 'last_processed_datetime': datetime.now().isoformat(), | |
| **analysis_data | |
| }, | |
| metadata={ | |
| 'source': 'fundamental_analysis', | |
| 'module': 'complete_analysis' | |
| } | |
| ) | |
| return self.db.save(entry, expiry_days=expiry_days) | |
| def get_fundamental_analysis(self, ticker: str, date_from: str = None, | |
| date_to: str = None) -> List[DatabaseEntry]: | |
| """Get fundamental analysis for ticker""" | |
| entries = self.db.query( | |
| ticker=ticker.upper(), | |
| data_type=DataType.FUNDAMENTAL.value, | |
| date_from=date_from, | |
| date_to=date_to | |
| ) | |
| # Filter for complete analysis | |
| return [e for e in entries if e.data.get('analysis_type') == 'complete'] | |
| # Add methods to FundamentalAdapter class | |
| FundamentalAdapter.save_fundamental_analysis = save_fundamental_analysis | |
| FundamentalAdapter.get_fundamental_analysis = get_fundamental_analysis | |
| _add_fundamental_methods() | |
| # Convenience functions for quick access | |
| def get_calendar_adapter(db: Optional[LocalDatabase] = None) -> CalendarAdapter: | |
| """Get calendar adapter instance""" | |
| return CalendarAdapter(db) | |
| def get_fundamental_adapter(db: Optional[LocalDatabase] = None) -> FundamentalAdapter: | |
| """Get fundamental analysis adapter instance""" | |
| return FundamentalAdapter(db) | |
| def get_news_adapter(db: Optional[LocalDatabase] = None) -> NewsAdapter: | |
| """Get news adapter instance""" | |
| return NewsAdapter(db) | |
| def get_technical_adapter(db: Optional[LocalDatabase] = None) -> TechnicalAnalysisAdapter: | |
| """Get technical analysis adapter instance""" | |
| return TechnicalAnalysisAdapter(db) | |