from __future__ import annotations from app.broker.base import Broker, Fill from app.models import OrderSide, utcnow class PaperBroker(Broker): """Simulated fills against a reference price, with fees and slippage. Buys fill slightly above and sells slightly below the reference price to approximate adverse slippage. """ def __init__(self, fee_bps: float = 10.0, slippage_bps: float = 5.0) -> None: self.fee_bps = fee_bps self.slippage_bps = slippage_bps def _fee(self, notional: float) -> float: return abs(notional) * self.fee_bps / 10_000 def fill(self, symbol: str, side: OrderSide, qty: float, ref_price: float) -> Fill: """Synchronous fill used by the backtester (same model as buy/sell).""" direction = 1 if side == OrderSide.BUY else -1 price = ref_price * (1 + direction * self.slippage_bps / 10_000) return Fill( symbol=symbol, side=side, qty=qty, price=price, fee=self._fee(qty * price), ts=utcnow(), ) async def buy(self, symbol: str, qty: float, ref_price: float) -> Fill: return self.fill(symbol, OrderSide.BUY, qty, ref_price) async def sell(self, symbol: str, qty: float, ref_price: float) -> Fill: return self.fill(symbol, OrderSide.SELL, qty, ref_price)