|
| 1 | +"""Broker execution cost helpers.""" |
| 2 | + |
| 3 | +from __future__ import annotations |
| 4 | + |
| 5 | +import math |
| 6 | +from dataclasses import dataclass |
| 7 | + |
| 8 | + |
| 9 | +__all__ = [ |
| 10 | + "BrokerCostProfile", |
| 11 | + "minimum_economic_order_notional_usd", |
| 12 | +] |
| 13 | + |
| 14 | + |
| 15 | +@dataclass(frozen=True) |
| 16 | +class BrokerCostProfile: |
| 17 | + """Small account broker cost inputs for economic order filtering.""" |
| 18 | + |
| 19 | + fixed_order_fee_usd: float = 0.0 |
| 20 | + minimum_order_fee_usd: float = 0.0 |
| 21 | + max_fixed_fee_bps: float = 100.0 |
| 22 | + explicit_min_order_notional_usd: float = 0.0 |
| 23 | + |
| 24 | + |
| 25 | +def _non_negative_finite(value: object, *, default: float = 0.0) -> float: |
| 26 | + try: |
| 27 | + numeric = float(value or 0.0) |
| 28 | + except (TypeError, ValueError): |
| 29 | + return float(default) |
| 30 | + if not math.isfinite(numeric): |
| 31 | + return float(default) |
| 32 | + return max(0.0, numeric) |
| 33 | + |
| 34 | + |
| 35 | +def minimum_economic_order_notional_usd(profile: BrokerCostProfile | None) -> float: |
| 36 | + """Return the minimum order notional implied by fixed order costs. |
| 37 | +
|
| 38 | + The helper intentionally models only fixed or minimum per-order fees. Per-share |
| 39 | + fees and sell-side regulatory fees do not produce a stable notional floor and |
| 40 | + should be handled by cost reporting/backtests rather than blocking risk exits. |
| 41 | + """ |
| 42 | + |
| 43 | + if profile is None: |
| 44 | + return 0.0 |
| 45 | + explicit_floor = _non_negative_finite(profile.explicit_min_order_notional_usd) |
| 46 | + fee_floor = max( |
| 47 | + _non_negative_finite(profile.fixed_order_fee_usd), |
| 48 | + _non_negative_finite(profile.minimum_order_fee_usd), |
| 49 | + ) |
| 50 | + max_fee_bps = _non_negative_finite(profile.max_fixed_fee_bps) |
| 51 | + if fee_floor <= 0.0 or max_fee_bps <= 0.0: |
| 52 | + return explicit_floor |
| 53 | + implied_floor = fee_floor / (max_fee_bps / 10_000.0) |
| 54 | + return max(explicit_floor, implied_floor) |
0 commit comments