quant-web/strategy_db.py
epistemophiliac b5db15d6ab Initial QuantTrade stack for Coolify deployment.
Streamlit + VectorBT dashboard, Parquet harvester with nightly cron, Authentik header auth, SQLite strategy persistence, and Bugsink telemetry.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-19 00:46:51 -04:00

135 lines
3.4 KiB
Python

"""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()