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