gotti_signal_gen / src /db /adapters.py
Papaflessas's picture
Deploy Signal Generator app
3fe0726
"""
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)