Spaces:
Running
Running
File size: 12,727 Bytes
ddccbe9 2284f0e a5a14e0 2284f0e ddccbe9 87f0da7 ddccbe9 87f0da7 ddccbe9 2284f0e a5a14e0 5746ed9 ddccbe9 b1fdb40 ddccbe9 d705f63 ddccbe9 e2599a1 0c4e32b ddccbe9 d705f63 b1fdb40 e2599a1 d705f63 e2599a1 d705f63 e2599a1 0c4e32b fdda2e5 d705f63 e2599a1 d705f63 ddccbe9 0ae74ed | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 | from datetime import datetime, timezone
from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime
from sqlalchemy.orm import sessionmaker, declarative_base
from config import DATA_DIR, logger
import os
import threading
import time
import shutil
# Setup database path
DB_PATH = DATA_DIR / "alpha_results.db"
# Restore database from Hugging Face Dataset if applicable
HF_TOKEN = os.getenv("HF_TOKEN")
SPACE_ID = os.getenv("SPACE_ID")
DEFAULT_DATASET_ID = f"{SPACE_ID}-backup" if SPACE_ID else None
HF_DATASET_ID = os.getenv("HF_DATASET_ID") or DEFAULT_DATASET_ID
def restore_db_from_hf():
if not HF_TOKEN:
logger.warning("HF_TOKEN environment variable is not set. Database restore from Hugging Face Dataset skipped.")
return
if not HF_DATASET_ID:
logger.warning("HF_DATASET_ID and SPACE_ID environment variables are not set. Database restore skipped.")
return
logger.info(f"Checking for database backup in Hugging Face Dataset: {HF_DATASET_ID}...")
try:
from huggingface_hub import hf_hub_download
cached_path = hf_hub_download(
repo_id=HF_DATASET_ID,
filename="alpha_results.db",
repo_type="dataset",
token=HF_TOKEN
)
DATA_DIR.mkdir(parents=True, exist_ok=True)
shutil.copy(cached_path, DB_PATH)
logger.info(f"Successfully restored database from Hugging Face Dataset to {DB_PATH}.")
except ImportError:
logger.warning("huggingface_hub package is not installed. Database restore skipped.")
except Exception as e:
logger.warning(f"Could not restore database from Hugging Face Dataset (this is normal if it is the first run): {e}")
try:
from huggingface_hub import hf_hub_download
settings_path = DATA_DIR / "miner_settings.json"
cached_settings_path = hf_hub_download(
repo_id=HF_DATASET_ID,
filename="miner_settings.json",
repo_type="dataset",
token=HF_TOKEN
)
shutil.copy(cached_settings_path, settings_path)
logger.info("Successfully restored miner_settings.json from Hugging Face Dataset.")
except Exception as e:
logger.warning(f"Could not restore miner_settings.json from Hugging Face Dataset: {e}")
try:
from huggingface_hub import hf_hub_download
cache_path = DATA_DIR / "data_fields_cache.json"
cached_cache_path = hf_hub_download(
repo_id=HF_DATASET_ID,
filename="data_fields_cache.json",
repo_type="dataset",
token=HF_TOKEN
)
shutil.copy(cached_cache_path, cache_path)
logger.info("Successfully restored data_fields_cache.json from Hugging Face Dataset.")
except Exception as e:
logger.warning(f"Could not restore data_fields_cache.json from Hugging Face Dataset: {e}")
# Try to restore backup before SQLAlchemy engine is initialized
restore_db_from_hf()
DATABASE_URL = f"sqlite:///{DB_PATH}"
def backup_db_to_hf():
if not HF_TOKEN:
logger.warning("HF_TOKEN environment variable is not set. Database backup to Hugging Face Dataset skipped.")
return
if not HF_DATASET_ID:
logger.warning("HF_DATASET_ID and SPACE_ID environment variables are not set. Database backup skipped.")
return
try:
from huggingface_hub import HfApi
except ImportError:
logger.warning("huggingface_hub package is not installed. Background backup thread will not start.")
return
logger.info(f"Starting background database backup thread (interval: 15 mins) to: {HF_DATASET_ID}...")
api = HfApi(token=HF_TOKEN)
# Create the dataset repo if it doesn't exist
try:
api.create_repo(
repo_id=HF_DATASET_ID,
repo_type="dataset",
private=True,
exist_ok=True
)
except Exception as e:
logger.warning(f"Could not verify/create Hugging Face Dataset repository: {e}")
is_first_backup = True
while True:
try:
# Sleep for 60 seconds on the first loop, and 15 minutes thereafter
if is_first_backup:
time.sleep(60)
is_first_backup = False
else:
time.sleep(15 * 60)
if not DB_PATH.exists():
logger.debug("Database file does not exist yet. Skipping backup cycle.")
continue
logger.info(f"Backing up database to Hugging Face Dataset: {HF_DATASET_ID}...")
temp_db_path = DB_PATH.parent / "alpha_results_backup_temp.db"
try:
shutil.copy2(DB_PATH, temp_db_path)
api.upload_file(
path_or_fileobj=str(temp_db_path),
path_in_repo="alpha_results.db",
repo_id=HF_DATASET_ID,
repo_type="dataset",
)
logger.info("Database backup successfully uploaded to Hugging Face Dataset.")
except Exception as upload_error:
logger.error(f"Failed to upload database file to Hugging Face: {upload_error}")
finally:
if temp_db_path.exists():
os.remove(temp_db_path)
# Also backup miner_settings.json if it exists
settings_path = DATA_DIR / "miner_settings.json"
if settings_path.exists():
logger.info("Backing up miner_settings.json to Hugging Face Dataset...")
try:
api.upload_file(
path_or_fileobj=str(settings_path),
path_in_repo="miner_settings.json",
repo_id=HF_DATASET_ID,
repo_type="dataset",
)
logger.info("miner_settings.json backup successfully uploaded to Hugging Face Dataset.")
except Exception as upload_error:
logger.error(f"Failed to upload miner_settings.json to Hugging Face: {upload_error}")
# Also backup data_fields_cache.json if it exists
cache_path = DATA_DIR / "data_fields_cache.json"
if cache_path.exists():
logger.info("Backing up data_fields_cache.json to Hugging Face Dataset...")
try:
api.upload_file(
path_or_fileobj=str(cache_path),
path_in_repo="data_fields_cache.json",
repo_id=HF_DATASET_ID,
repo_type="dataset",
)
logger.info("data_fields_cache.json backup successfully uploaded to Hugging Face Dataset.")
except Exception as upload_error:
logger.error(f"Failed to upload data_fields_cache.json to Hugging Face: {upload_error}")
# Also backup miner_production.log if it exists
prod_log_path = DATA_DIR / "miner_production.log"
if prod_log_path.exists():
logger.info("Backing up miner_production.log to Hugging Face Dataset...")
try:
temp_log_path = DATA_DIR / "miner_production_backup_temp.log"
shutil.copy2(prod_log_path, temp_log_path)
api.upload_file(
path_or_fileobj=str(temp_log_path),
path_in_repo="miner_production.log",
repo_id=HF_DATASET_ID,
repo_type="dataset",
)
logger.info("miner_production.log backup successfully uploaded to Hugging Face Dataset.")
except Exception as upload_error:
logger.error(f"Failed to upload miner_production.log to Hugging Face: {upload_error}")
finally:
if temp_log_path.exists():
try:
os.remove(temp_log_path)
except Exception:
pass
except Exception as e:
logger.error(f"Error during background database backup to Hugging Face: {e}")
def start_backup_thread():
if HF_TOKEN and HF_DATASET_ID:
t = threading.Thread(target=backup_db_to_hf, daemon=True, name="HF_DB_Backup")
t.start()
logger.info("Database backup thread started successfully.")
else:
logger.warning("HF_TOKEN or HF_DATASET_ID/SPACE_ID not configured. Backup thread not started.")
# Start the periodic background backup thread
start_backup_thread()
# Initialize SQLAlchemy Engine and Base
engine = create_engine(DATABASE_URL, echo=False, connect_args={"timeout": 30})
Base = declarative_base()
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
class AlphaRecord(Base):
__tablename__ = "alpha_records"
id = Column(Integer, primary_key=True, index=True)
expression = Column(String, unique=True, nullable=False)
region = Column(String, default='USA')
universe = Column(String, default='TOP3000')
delay = Column(Integer, default=1)
decay = Column(Integer, default=10)
truncation = Column(Float, default=0.01)
neutralization = Column(String, default='SUBINDUSTRY')
# Results
sharpe = Column(Float, nullable=True)
fitness = Column(Float, nullable=True)
turnover = Column(Float, nullable=True)
returns = Column(Float, nullable=True)
# Metadata
status = Column(String, default='GENERATED') # e.g., 'GENERATED', 'SIMULATING', 'FAILED', 'PASSED', 'SUBMITTED'
error_log = Column(String, nullable=True)
alpha_id = Column(String, nullable=True)
date_created = Column(DateTime, default=lambda: datetime.now(timezone.utc))
date_submitted = Column(DateTime, nullable=True)
retry_count = Column(Integer, default=0)
def __repr__(self):
return f"<AlphaRecord(id={self.id}, expression='{self.expression}', status='{self.status}')>"
def init_db():
"""Initializes the SQLite database and creates all tables."""
logger.info(f"Initializing database at {DB_PATH}...")
Base.metadata.create_all(bind=engine)
# Enable WAL mode for better concurrency
try:
import sqlite3
conn = sqlite3.connect(str(DB_PATH))
cursor = conn.cursor()
cursor.execute("PRAGMA journal_mode=WAL")
cursor.execute("PRAGMA synchronous=NORMAL")
conn.commit()
conn.close()
logger.info("SQLite WAL mode and synchronous settings configured successfully.")
except Exception as e:
logger.error(f"Failed to configure SQLite WAL mode: {e}")
# Check if alpha_id or date_submitted columns exist, if not, add them safely
try:
import sqlite3
conn = sqlite3.connect(str(DB_PATH))
cursor = conn.cursor()
cursor.execute("PRAGMA table_info(alpha_records)")
columns = [col[1] for col in cursor.fetchall()]
if "alpha_id" not in columns:
logger.info("Adding alpha_id column to alpha_records table...")
cursor.execute("ALTER TABLE alpha_records ADD COLUMN alpha_id VARCHAR")
conn.commit()
logger.info("alpha_id column added successfully.")
if "date_submitted" not in columns:
logger.info("Adding date_submitted column to alpha_records table...")
cursor.execute("ALTER TABLE alpha_records ADD COLUMN date_submitted TIMESTAMP")
conn.commit()
logger.info("date_submitted column added successfully.")
if "retry_count" not in columns:
logger.info("Adding retry_count column to alpha_records table...")
cursor.execute("ALTER TABLE alpha_records ADD COLUMN retry_count INTEGER DEFAULT 0")
conn.commit()
logger.info("retry_count column added successfully.")
# Migrate SP500 to TOPSP500 in existing records
logger.info("Running database migration to replace SP500 with TOPSP500...")
cursor.execute("UPDATE alpha_records SET universe = 'TOPSP500' WHERE universe = 'SP500'")
conn.commit()
logger.info(f"Universe migration query completed. Rows modified: {cursor.rowcount}")
conn.close()
except Exception as e:
logger.error(f"Error checking/adding columns to database: {e}")
logger.info("Database initialized successfully.")
# Automatically initialize the database and apply migrations on import
init_db()
|