- Added FastAPI backend with core API endpoints for strategies, backtests, and data management. - Introduced Vue 3 frontend with a dark theme, enabling users to run backtests, adjust parameters, and compare results. - Implemented Pydantic schemas for request/response validation and SQLAlchemy models for database interactions. - Enhanced project structure with dedicated modules for services, routers, and components. - Updated dependencies in `pyproject.toml` and `frontend/package.json` to include FastAPI, SQLAlchemy, and Vue-related packages. - Improved `.gitignore` to exclude unnecessary files and directories.
100 lines
3.5 KiB
Python
100 lines
3.5 KiB
Python
"""
|
|
SQLAlchemy database models and session management for backtest run persistence.
|
|
"""
|
|
import json
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
from sqlalchemy import JSON, Column, DateTime, Float, Integer, String, Text, create_engine
|
|
from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker
|
|
|
|
# Database file location
|
|
DB_PATH = Path(__file__).parent.parent.parent / "data" / "backtest_runs.db"
|
|
DATABASE_URL = f"sqlite:///{DB_PATH}"
|
|
|
|
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
|
|
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
|
|
|
|
class Base(DeclarativeBase):
|
|
"""Base class for SQLAlchemy models."""
|
|
pass
|
|
|
|
|
|
class BacktestRun(Base):
|
|
"""
|
|
Persisted backtest run record.
|
|
|
|
Stores all information needed to display and compare runs.
|
|
"""
|
|
__tablename__ = "backtest_runs"
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
run_id = Column(String(36), unique=True, nullable=False, index=True)
|
|
|
|
# Configuration
|
|
strategy = Column(String(50), nullable=False, index=True)
|
|
symbol = Column(String(20), nullable=False, index=True)
|
|
exchange = Column(String(20), nullable=False, default="okx")
|
|
market_type = Column(String(20), nullable=False)
|
|
timeframe = Column(String(10), nullable=False)
|
|
leverage = Column(Integer, nullable=False, default=1)
|
|
params = Column(JSON, nullable=False, default=dict)
|
|
|
|
# Date range
|
|
start_date = Column(String(20), nullable=True)
|
|
end_date = Column(String(20), nullable=True)
|
|
|
|
# Metrics (denormalized for quick listing)
|
|
total_return = Column(Float, nullable=False)
|
|
benchmark_return = Column(Float, nullable=False, default=0.0)
|
|
alpha = Column(Float, nullable=False, default=0.0)
|
|
sharpe_ratio = Column(Float, nullable=False)
|
|
max_drawdown = Column(Float, nullable=False)
|
|
win_rate = Column(Float, nullable=False)
|
|
total_trades = Column(Integer, nullable=False)
|
|
profit_factor = Column(Float, nullable=True)
|
|
total_fees = Column(Float, nullable=False, default=0.0)
|
|
total_funding = Column(Float, nullable=False, default=0.0)
|
|
liquidation_count = Column(Integer, nullable=False, default=0)
|
|
liquidation_loss = Column(Float, nullable=False, default=0.0)
|
|
adjusted_return = Column(Float, nullable=True)
|
|
|
|
# Full data (JSON serialized)
|
|
equity_curve = Column(Text, nullable=False) # JSON array
|
|
trades = Column(Text, nullable=False) # JSON array
|
|
|
|
# Metadata
|
|
created_at = Column(DateTime, nullable=False, default=lambda: datetime.now(timezone.utc))
|
|
|
|
def set_equity_curve(self, data: list[dict]):
|
|
"""Serialize equity curve to JSON string."""
|
|
self.equity_curve = json.dumps(data)
|
|
|
|
def get_equity_curve(self) -> list[dict]:
|
|
"""Deserialize equity curve from JSON string."""
|
|
return json.loads(self.equity_curve) if self.equity_curve else []
|
|
|
|
def set_trades(self, data: list[dict]):
|
|
"""Serialize trades to JSON string."""
|
|
self.trades = json.dumps(data)
|
|
|
|
def get_trades(self) -> list[dict]:
|
|
"""Deserialize trades from JSON string."""
|
|
return json.loads(self.trades) if self.trades else []
|
|
|
|
|
|
def init_db():
|
|
"""Create database tables if they don't exist."""
|
|
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
Base.metadata.create_all(bind=engine)
|
|
|
|
|
|
def get_db() -> Session:
|
|
"""Get database session (dependency injection)."""
|
|
db = SessionLocal()
|
|
try:
|
|
yield db
|
|
finally:
|
|
db.close()
|