"""SQLite persistence for user-saved strategies.""" from __future__ import annotations import json import os import sqlite3 from contextlib import contextmanager from dataclasses import dataclass from datetime import datetime, timezone from typing import Any @dataclass(frozen=True) class SavedStrategy: id: int username: str name: str ticker: str params: dict[str, Any] created_at: str def _db_path() -> str: return os.environ.get("STRATEGY_DB_PATH", "/data/strategies/strategies.db") def init_db() -> None: path = _db_path() os.makedirs(os.path.dirname(path), exist_ok=True) with _connect() as conn: conn.execute( """ CREATE TABLE IF NOT EXISTS strategies ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL, name TEXT NOT NULL, ticker TEXT NOT NULL, params_json TEXT NOT NULL, created_at TEXT NOT NULL, UNIQUE(username, name) ) """ ) conn.commit() @contextmanager def _connect(): conn = sqlite3.connect(_db_path()) conn.row_factory = sqlite3.Row try: yield conn finally: conn.close() def save_strategy( username: str, name: str, ticker: str, params: dict[str, Any], ) -> None: created_at = datetime.now(timezone.utc).isoformat() with _connect() as conn: conn.execute( """ INSERT INTO strategies (username, name, ticker, params_json, created_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(username, name) DO UPDATE SET ticker = excluded.ticker, params_json = excluded.params_json, created_at = excluded.created_at """, (username, name.strip(), ticker.upper(), json.dumps(params), created_at), ) conn.commit() def list_strategies(username: str) -> list[SavedStrategy]: with _connect() as conn: rows = conn.execute( """ SELECT id, username, name, ticker, params_json, created_at FROM strategies WHERE username = ? ORDER BY created_at DESC """, (username,), ).fetchall() return [ SavedStrategy( id=row["id"], username=row["username"], name=row["name"], ticker=row["ticker"], params=json.loads(row["params_json"]), created_at=row["created_at"], ) for row in rows ] def load_strategy(username: str, name: str) -> SavedStrategy | None: with _connect() as conn: row = conn.execute( """ SELECT id, username, name, ticker, params_json, created_at FROM strategies WHERE username = ? AND name = ? """, (username, name), ).fetchone() if row is None: return None return SavedStrategy( id=row["id"], username=row["username"], name=row["name"], ticker=row["ticker"], params=json.loads(row["params_json"]), created_at=row["created_at"], ) def delete_strategy(username: str, name: str) -> None: with _connect() as conn: conn.execute( "DELETE FROM strategies WHERE username = ? AND name = ?", (username, name), ) conn.commit()