from __future__ import annotations from datetime import datetime from typing import Any, Literal from pydantic import BaseModel, ConfigDict, Field class Candle(BaseModel): ts: int open: float high: float low: float close: float volume: float class ParamSpec(BaseModel): name: str type: Literal["int", "float", "bool", "str"] default: Any description: str = "" min: float | None = None max: float | None = None class StrategyOut(BaseModel): key: str name: str description: str params: list[ParamSpec] class PositionOut(BaseModel): model_config = ConfigDict(from_attributes=True) id: int strategy_key: str symbol: str side: str qty: float entry_price: float status: str stop_loss: float | None = None take_profit: float | None = None exit_price: float | None = None realized_pnl: float | None = None unrealized_pnl: float | None = None opened_at: datetime closed_at: datetime | None = None class OrderOut(BaseModel): model_config = ConfigDict(from_attributes=True) id: int position_id: int | None strategy_key: str symbol: str side: str qty: float price: float fee: float reason: str created_at: datetime class EventOut(BaseModel): model_config = ConfigDict(from_attributes=True) id: int level: str message: str strategy_key: str | None created_at: datetime class EquityPointOut(BaseModel): model_config = ConfigDict(from_attributes=True) equity: float cash: float price: float created_at: datetime class StatusOut(BaseModel): running: bool mode: str exchange: str symbol: str timeframe: str strategy_key: str | None strategy_name: str | None params: dict[str, Any] = Field(default_factory=dict) price: float | None cash: float equity: float starting_balance: float realized_pnl: float unrealized_pnl: float total_pnl: float open_positions: int last_tick: datetime | None error: str | None = None halted: bool = False halt_reason: str | None = None trades_today: int = 0 day_pnl: float = 0.0 drawdown_pct: float = 0.0 class KillRequest(BaseModel): reason: str = "manual kill switch" class StrategyConfigIn(BaseModel): name: str = Field(default="", max_length=80) strategy_key: str symbol: str timeframe: str params: dict[str, Any] = Field(default_factory=dict) class StrategyConfigOut(BaseModel): model_config = ConfigDict(from_attributes=True) id: int name: str strategy_key: str symbol: str timeframe: str params: dict[str, Any] created_at: datetime class StartRequest(BaseModel): strategy_key: str symbol: str | None = None timeframe: str | None = None params: dict[str, Any] = Field(default_factory=dict) starting_balance: float | None = None class ManualOrderRequest(BaseModel): side: Literal["buy", "sell"] qty: float | None = None quote_amount: float | None = None reason: str = "manual" class ClosePositionRequest(BaseModel): reason: str = "manual" class BacktestRequest(BaseModel): strategy_key: str params: dict[str, Any] = Field(default_factory=dict) symbol: str | None = None timeframe: str | None = None limit: int = Field(default=500, ge=20, le=1000) starting_balance: float | None = None class BacktestTradeOut(BaseModel): side: str entry_ts: int exit_ts: int entry_price: float exit_price: float qty: float pnl: float return_pct: float reason: str class BacktestPointOut(BaseModel): ts: int equity: float buy_hold: float price: float class BacktestMetricsOut(BaseModel): initial_equity: float final_equity: float total_return_pct: float buy_hold_return_pct: float max_drawdown_pct: float num_trades: int win_rate_pct: float profit_factor: float | None avg_trade_pct: float best_trade_pct: float worst_trade_pct: float sharpe: float | None class BacktestResult(BaseModel): strategy_key: str strategy_name: str symbol: str timeframe: str starting_balance: float metrics: BacktestMetricsOut equity_curve: list[BacktestPointOut] trades: list[BacktestTradeOut] error: str | None = None