Coverage for src/finbot/execution/fill.py: 100%

38 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-21 17:12 +0000

1import math 

2from dataclasses import dataclass 

3from datetime import datetime 

4from typing import Any 

5 

6from finbot.execution.order import OrderSide 

7 

8 

9@dataclass(frozen=True) 

10class Fill: 

11 symbol: str 

12 side: OrderSide 

13 quantity: float 

14 price: float 

15 timestamp: datetime 

16 fee: float = 0.0 

17 slippage_cost: float = 0.0 

18 

19 def __post_init__(self): 

20 if not isinstance(self.symbol, str) or not self.symbol.strip(): 

21 raise ValueError("Symbol must be a non-empty string.") 

22 

23 if not math.isfinite(self.quantity): 

24 raise ValueError("Quantity must be a valid number.") 

25 if not math.isfinite(self.price): 

26 raise ValueError("Price must be a valid number.") 

27 if not math.isfinite(self.fee): 

28 raise ValueError("Fee must be a valid number.") 

29 if not math.isfinite(self.slippage_cost): 

30 raise ValueError("Slippage cost must be a valid number.") 

31 

32 if self.quantity <= 0: 

33 raise ValueError("Quantity must be greater than zero.") 

34 if self.price <= 0: 

35 raise ValueError("Price must be greater than zero.") 

36 if self.fee < 0: 

37 raise ValueError("Fee cannot be negative.") 

38 if self.slippage_cost < 0: 

39 raise ValueError("Slippage cost cannot be negative.") 

40 

41 def serialize(self) -> dict[str, Any]: 

42 return { 

43 "symbol": self.symbol, 

44 "side": self.side.value, 

45 "quantity": self.quantity, 

46 "price": self.price, 

47 "timestamp": self.timestamp.isoformat(), 

48 "fee": self.fee, 

49 "slippage_cost": self.slippage_cost, 

50 } 

51 

52 @classmethod 

53 def deserialize(cls, data: dict[str, Any]) -> "Fill": 

54 return cls( 

55 symbol=data["symbol"], 

56 side=OrderSide(data["side"]), 

57 quantity=data["quantity"], 

58 price=data["price"], 

59 timestamp=datetime.fromisoformat(data["timestamp"]), 

60 fee=data.get("fee", 0.0), 

61 slippage_cost=data.get("slippage_cost", 0.0), 

62 )