from __future__ import annotations from sqlalchemy import select from sqlalchemy.orm import sessionmaker from app.db import SessionLocal from app.models import ( EquitySnapshot, Event, Order, OrderSide, Position, PositionSide, PositionStatus, StrategyConfig, ) class Store: """Thin persistence layer around the trading tables.""" def __init__(self, session_factory: sessionmaker = SessionLocal) -> None: self._session_factory: sessionmaker = session_factory def create_position( self, *, strategy_key: str, symbol: str, side: PositionSide, qty: float, entry_price: float, entry_fee: float, stop_loss: float | None = None, take_profit: float | None = None, ) -> Position: with self._session_factory() as session: position = Position( strategy_key=strategy_key, symbol=symbol, side=side, qty=qty, entry_price=entry_price, entry_fee=entry_fee, stop_loss=stop_loss, take_profit=take_profit, status=PositionStatus.OPEN, ) session.add(position) session.commit() return position def close_position( self, position: Position, *, exit_price: float, exit_fee: float, realized_pnl: float ) -> Position: from app.models import utcnow with self._session_factory() as session: merged = session.merge(position) merged.status = PositionStatus.CLOSED merged.exit_price = exit_price merged.exit_fee = exit_fee merged.realized_pnl = realized_pnl merged.closed_at = utcnow() session.commit() return merged def abandon_open_positions(self, strategy_key: str, reason: str = "reset") -> int: """Mark lingering open positions closed at entry price (used on restart).""" from app.models import utcnow with self._session_factory() as session: positions = list( session.scalars( select(Position).where( Position.strategy_key == strategy_key, Position.status == PositionStatus.OPEN, ) ).all() ) for position in positions: position.status = PositionStatus.CLOSED position.exit_price = position.entry_price position.exit_fee = 0.0 position.realized_pnl = -position.entry_fee position.closed_at = utcnow() session.add_all(positions) session.commit() return len(positions) def open_position_for(self, strategy_key: str) -> Position | None: with self._session_factory() as session: return session.scalars( select(Position) .where( Position.strategy_key == strategy_key, Position.status == PositionStatus.OPEN, ) .order_by(Position.id.desc()) ).first() def record_order( self, *, position_id: int | None, strategy_key: str, symbol: str, side: OrderSide, qty: float, price: float, fee: float, reason: str, ) -> Order: with self._session_factory() as session: order = Order( position_id=position_id, strategy_key=strategy_key, symbol=symbol, side=side, qty=qty, price=price, fee=fee, reason=reason, ) session.add(order) session.commit() return order def record_event(self, level: str, message: str, strategy_key: str | None = None) -> Event: with self._session_factory() as session: event = Event(level=level, message=message, strategy_key=strategy_key) session.add(event) session.commit() return event def record_equity(self, equity: float, cash: float, price: float) -> None: with self._session_factory() as session: session.add(EquitySnapshot(equity=equity, cash=cash, price=price)) session.commit() def list_positions(self, limit: int = 50) -> list[Position]: with self._session_factory() as session: return list( session.scalars(select(Position).order_by(Position.id.desc()).limit(limit)).all() ) def list_orders(self, limit: int = 50) -> list[Order]: with self._session_factory() as session: return list(session.scalars(select(Order).order_by(Order.id.desc()).limit(limit)).all()) def list_events(self, limit: int = 50) -> list[Event]: with self._session_factory() as session: return list(session.scalars(select(Event).order_by(Event.id.desc()).limit(limit)).all()) def list_equity(self, limit: int = 500) -> list[EquitySnapshot]: with self._session_factory() as session: rows = list( session.scalars( select(EquitySnapshot).order_by(EquitySnapshot.id.desc()).limit(limit) ).all() ) return list(reversed(rows)) def save_strategy_config( self, strategy_key: str, symbol: str, timeframe: str, params: dict ) -> StrategyConfig: with self._session_factory() as session: config = StrategyConfig( strategy_key=strategy_key, symbol=symbol, timeframe=timeframe, params=params ) session.add(config) session.commit() return config