spb/crypto-trade-bot
Public
Python 54.7%
TypeScript 42.5%
SQL 1.9%
1"""Execution cost & fill model shared by the backtester and the paper simulator."""23from __future__ import annotations45import math6from decimal import ROUND_DOWN, Decimal78from pydantic import BaseModel, Field91011class CostModel(BaseModel):12 taker_fee: float = Field(0.0040, description="Kraken spot taker fee at the lowest tier (verify with TradeVolume)")13 maker_fee: float = Field(0.0025)14 spread_bps: float = Field(5.0, description="Assumed quoted spread when no book data (bps of mid)")15 slippage_bps: float = Field(2.0, description="Extra adverse move per order")16 impact_coeff: float = Field(0.1, description="Impact (bps) = coeff × sqrt(qty / bar_volume) × 1e4")17 max_participation: float = Field(0.05, description="Max fraction of bar volume filled per bar (partial fills)")18 latency_bars: int = Field(0, description="Extra bars of delay beyond next-open execution")19 use_maker: bool = False20 fee_multiplier: float = 1.0 # stress: fee increases21 spread_multiplier: float = 1.0 # stress: spread widening222324def round_lot(qty: float, lot_decimals: int) -> float:25 if qty <= 0 or not math.isfinite(qty):26 return 0.027 q = Decimal(str(qty)).quantize(Decimal(1).scaleb(-lot_decimals), rounding=ROUND_DOWN)28 return float(q)293031def fill_price(side: str, ref_price: float, qty: float, bar_volume: float, cm: CostModel) -> float:32 half_spread = ref_price * (cm.spread_bps * cm.spread_multiplier) / 2e433 slip = ref_price * cm.slippage_bps / 1e434 impact = ref_price * cm.impact_coeff * math.sqrt(qty / bar_volume) if bar_volume > 0 else 0.035 adverse = (0.0 if cm.use_maker else half_spread) + slip + impact36 return ref_price + adverse if side == "buy" else max(ref_price - adverse, 1e-12)373839def fee_rate(cm: CostModel) -> float:40 return (cm.maker_fee if cm.use_maker else cm.taker_fee) * cm.fee_multiplier41