"""ORM 模型。 Candle 表设计与 TimescaleDB hypertable 完全兼容:将来在目标 PG 库执行 SELECT create_hypertable('candles', 'ts'); 即可升级为时序表 + Continuous Aggregates 多周期预聚合,无需改表结构。 """ from datetime import datetime from sqlalchemy import DateTime, Float, Integer, String, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column from .db import Base def _utcnow() -> datetime: # naive UTC,避免 SQLite 存储时区带来的麻烦 from datetime import timezone return datetime.now(timezone.utc).replace(tzinfo=None) class Candle(Base): __tablename__ = "candles" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) symbol: Mapped[str] = mapped_column(String(16), index=True) timeframe: Mapped[str] = mapped_column(String(4), default="1d", index=True) ts: Mapped[datetime] = mapped_column(DateTime, index=True) # bar 开始时间 open: Mapped[float] = mapped_column(Float) high: Mapped[float] = mapped_column(Float) low: Mapped[float] = mapped_column(Float) close: Mapped[float] = mapped_column(Float) volume: Mapped[float] = mapped_column(Float) __table_args__ = ( UniqueConstraint("symbol", "timeframe", "ts", name="uq_candle_sym_tf_ts"), ) class BacktestRun(Base): """回测运行注册表(可复现/可审计/可回归对比的基础)。 完整版应记录 策略版本 + 参数快照 + 数据快照(复权/数据源/库版本)+ 环境指纹 + 结果指纹。 MVP 先落关键字段,结构就位。 """ __tablename__ = "backtest_runs" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) created_at: Mapped[datetime] = mapped_column(DateTime, default=_utcnow) symbol: Mapped[str] = mapped_column(String(16)) strategy: Mapped[str] = mapped_column(String(64)) timeframe: Mapped[str] = mapped_column(String(4), default="1d") params_json: Mapped[str] = mapped_column(String, default="{}") initial_cash: Mapped[float] = mapped_column(Float, default=100000.0) total_return: Mapped[float] = mapped_column(Float, default=0.0) max_drawdown: Mapped[float] = mapped_column(Float, default=0.0) sharpe: Mapped[float] = mapped_column(Float, default=0.0) num_trades: Mapped[int] = mapped_column(Integer, default=0)