Streamlit + VectorBT dashboard, Parquet harvester with nightly cron, Authentik header auth, SQLite strategy persistence, and Bugsink telemetry. Co-authored-by: Cursor <cursoragent@cursor.com>
135 lines
3.4 KiB
Python
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()
|