Coverage for src/finbot/risk/position_sizer.py: 96%
31 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-04 12:05 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-04 12:05 +0000
1import math
3from finbot.execution.models import OrderSide
4from finbot.risk.models import SizingDecision
7class PositionSizer:
8 def size(
9 self,
10 order_side: OrderSide,
11 market_price: float,
12 order_value_budget: float,
13 fee_rate: float = 0.0,
14 slippage_rate: float = 0.0,
15 max_quantity: float | None = None,
16 ) -> SizingDecision:
17 self._validate_inputs(
18 market_price=market_price,
19 order_value_budget=order_value_budget,
20 fee_rate=fee_rate,
21 slippage_rate=slippage_rate,
22 max_quantity=max_quantity,
23 )
25 if order_side == OrderSide.BUY:
26 execution_price = market_price * (1.0 + slippage_rate)
27 cost_per_unit = execution_price * (1.0 + fee_rate)
29 elif order_side == OrderSide.SELL:
30 execution_price = market_price * (1.0 - slippage_rate)
31 cost_per_unit = execution_price
33 else:
34 return SizingDecision.reject(f"Unsupported order side: {order_side}")
36 quantity = order_value_budget / cost_per_unit
38 if max_quantity is not None:
39 quantity = min(quantity, max_quantity)
41 if quantity <= 0: 41 ↛ 42line 41 didn't jump to line 42 because the condition on line 41 was never true
42 return SizingDecision.reject(
43 "Order value budget is too small to produce a positive quantity.",
44 )
46 return SizingDecision.accept(quantity)
48 @staticmethod
49 def _validate_inputs(
50 *,
51 market_price: float,
52 order_value_budget: float,
53 fee_rate: float,
54 slippage_rate: float,
55 max_quantity: float | None,
56 ) -> None:
57 if not math.isfinite(market_price) or market_price <= 0:
58 raise ValueError("market_price must be a positive finite number.")
60 if not math.isfinite(order_value_budget) or order_value_budget <= 0:
61 raise ValueError("order_value_budget must be a positive finite number.")
63 if not math.isfinite(fee_rate) or fee_rate < 0:
64 raise ValueError("fee_rate must be a non-negative finite number.")
66 if not math.isfinite(slippage_rate) or not 0.0 <= slippage_rate <= 1.0:
67 raise ValueError("slippage_rate must be between 0.0 and 1.0.")
69 if max_quantity is not None and (not math.isfinite(max_quantity) or max_quantity <= 0):
70 raise ValueError("max_quantity must be a positive finite number.")