RivaCube/update_economics.py
2025-02-04 19:31:18 +01:00

285 lines
9.7 KiB
Python

# RivaCube/RivDroite/update_economics.py
import os
import logging
from datetime import datetime, timedelta
from dotenv import load_dotenv
from typing import Optional, Dict, List, Any
from utils.economics.collector import EconomicsCollector
from utils.economics.utils import (
DatabaseManager,
save_treasury_rates,
save_economic_indicator,
save_economic_calendar,
save_market_risk_premium
)
# Define indicator mappings with their API endpoints
INDICATOR_MAPPINGS = {
'GDP': 'GDP',
'INFLATION': 'inflationRate',
'UNEMPLOYMENT': 'unemploymentRate',
'INTEREST_RATE': 'federalFunds',
'RETAIL_SALES': 'retailSales',
'INDUSTRIAL_PRODUCTION': 'industrialProductionTotalIndex'
}
def setup_logging() -> None:
"""Configure logging with both file and console output."""
log_dir = 'logs'
os.makedirs(log_dir, exist_ok=True)
log_file = os.path.join(log_dir, f'economics_update_{datetime.now():%Y%m%d_%H%M%S}.log')
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S',
handlers=[
logging.FileHandler(log_file),
logging.StreamHandler()
]
)
def init_database(db_manager: DatabaseManager) -> bool:
"""Initialize database schema if not exists."""
conn = None
try:
conn = db_manager.get_connection()
if not conn:
raise ValueError("Failed to get database connection")
cursor = conn.cursor()
# Check if tables exist
cursor.execute("""
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'treasury_rates'
);
""")
tables_exist = cursor.fetchone()[0]
if not tables_exist:
schema_sql = """
CREATE TABLE IF NOT EXISTS treasury_rates (
id SERIAL PRIMARY KEY,
date DATE NOT NULL,
maturity VARCHAR(50) NOT NULL,
rate NUMERIC,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
last_updated TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uk_treasury_rates UNIQUE(date, maturity)
);
CREATE TABLE IF NOT EXISTS economic_indicators (
id SERIAL PRIMARY KEY,
date DATE NOT NULL,
indicator_name VARCHAR(100) NOT NULL,
value NUMERIC NOT NULL,
unit VARCHAR(50),
frequency VARCHAR(50),
country VARCHAR(50) DEFAULT 'US',
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
last_updated TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uk_economic_indicators UNIQUE(date, indicator_name)
);
CREATE TABLE IF NOT EXISTS economic_calendar (
id SERIAL PRIMARY KEY,
date DATE NOT NULL,
event VARCHAR(255) NOT NULL,
country VARCHAR(50) DEFAULT 'US',
impact VARCHAR(50),
actual VARCHAR(100),
previous VARCHAR(100),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
last_updated TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uk_economic_calendar UNIQUE(date, event, country)
);
CREATE TABLE IF NOT EXISTS market_risk_premium (
id SERIAL PRIMARY KEY,
date DATE NOT NULL,
country VARCHAR(100) NOT NULL,
country_risk_premium NUMERIC,
total_equity_risk_premium NUMERIC,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
last_updated TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uk_market_risk_premium UNIQUE(date, country)
);
CREATE INDEX IF NOT EXISTS idx_treasury_rates_date ON treasury_rates(date);
CREATE INDEX IF NOT EXISTS idx_economic_indicators_date ON economic_indicators(date);
CREATE INDEX IF NOT EXISTS idx_economic_calendar_date ON economic_calendar(date);
CREATE INDEX IF NOT EXISTS idx_market_risk_premium_date ON market_risk_premium(date);
"""
cursor.execute(schema_sql)
conn.commit()
logging.info("Database schema initialized successfully")
else:
logging.info("Database schema already exists")
return True
except Exception as e:
if conn:
conn.rollback()
logging.error(f"Database initialization failed: {e}")
return False
finally:
if cursor:
cursor.close()
if conn:
db_manager.return_connection(conn)
def process_market_risk_premium(collector: EconomicsCollector, conn: Any) -> bool:
"""Process market risk premium data with proper error handling."""
try:
logging.info("Updating market risk premium...")
data = collector.get_market_risk_premium()
if not data:
logging.info("No market risk premium data received")
return True
# Filter out entries with None values for both premiums
valid_data = [
item for item in data
if (item.get('countryRiskPremium') is not None or
item.get('totalEquityRiskPremium') is not None)
]
if valid_data:
save_market_risk_premium(conn, valid_data)
return True
else:
logging.info("No valid market risk premium data after filtering")
return True # Not considering this as an error
except Exception as e:
logging.error(f"Error processing market risk premium: {e}")
return False
def process_economic_data(
collector: EconomicsCollector,
db_name: str,
api_name: str,
conn: Any
) -> bool:
"""Process a single economic indicator."""
try:
logging.info(f"Updating {db_name} (API: {api_name})...")
data = collector.get_economic_indicator(api_name)
if not data:
logging.info(f"No data received for {db_name}")
return True
save_economic_indicator(conn, db_name, data)
return True
except Exception as e:
logging.error(f"Error processing {db_name}: {e}")
return False
def update_all_economics(db_manager: DatabaseManager, collector: EconomicsCollector) -> bool:
"""Update all economic data in the database."""
success = True
conn = None
try:
conn = db_manager.get_connection()
if not conn:
raise ValueError("Failed to get database connection")
# Get date range for data collection
to_date = datetime.now()
days_to_fetch = int(os.getenv('DAYS_TO_FETCH', '2'))
from_date = to_date - timedelta(days=days_to_fetch)
# Update treasury rates
try:
logging.info("Updating treasury rates...")
rates = collector.get_treasury_rates(
from_date.strftime('%Y-%m-%d'),
to_date.strftime('%Y-%m-%d')
)
if rates:
save_treasury_rates(conn, rates)
except Exception as e:
logging.error(f"Error updating treasury rates: {e}")
success = False
# Update economic indicators
for db_name, api_name in INDICATOR_MAPPINGS.items():
if not process_economic_data(collector, db_name, api_name, conn):
success = False
# Update economic calendar
try:
logging.info("Updating economic calendar...")
events = collector.get_economic_calendar(
from_date.strftime('%Y-%m-%d'),
to_date.strftime('%Y-%m-%d')
)
if events:
save_economic_calendar(conn, events)
except Exception as e:
logging.error(f"Error updating economic calendar: {e}")
success = False
# Update market risk premium
if not process_market_risk_premium(collector, conn):
success = False
return success
except Exception as e:
logging.error(f"Error in update_all_economics: {e}")
return False
finally:
if conn:
db_manager.return_connection(conn)
def main() -> None:
"""Main function to run the economic data update process."""
load_dotenv()
setup_logging()
start_time = datetime.now()
logging.info(f"Starting economic data update at {start_time}")
db_manager = None
success = False
try:
api_key = os.getenv('FMP_API_KEY')
if not api_key:
raise ValueError("FMP_API_KEY not found in environment variables")
db_manager = DatabaseManager()
if not init_database(db_manager):
raise ValueError("Failed to initialize database")
collector = EconomicsCollector(api_key)
success = update_all_economics(db_manager, collector)
except Exception as e:
logging.error(f"Fatal error in main execution: {e}")
finally:
if db_manager:
db_manager.close_all()
logging.info("Database connections closed")
end_time = datetime.now()
duration = end_time - start_time
status = "successfully" if success else "with errors"
logging.info(f"Economic data update completed {status} in {duration}")
if __name__ == "__main__":
main()