FastAPI backend with a single-strategy async engine, paper and Binance brokers, pure-python indicators, a backtester that shares the engine's portfolio math, and a Vue dashboard with a live WebSocket feed. Adds the Gitea Actions CI/CD workflow that deploys to Traefik via docker_default.
39 lines
1.3 KiB
Python
39 lines
1.3 KiB
Python
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)
|