Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions src/quant_platform_kit/common/broker_costs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""Broker execution cost helpers."""

from __future__ import annotations

import math
from dataclasses import dataclass


__all__ = [
"BrokerCostProfile",
"minimum_economic_order_notional_usd",
]


@dataclass(frozen=True)
class BrokerCostProfile:
"""Small account broker cost inputs for economic order filtering."""

fixed_order_fee_usd: float = 0.0
minimum_order_fee_usd: float = 0.0
max_fixed_fee_bps: float = 100.0
explicit_min_order_notional_usd: float = 0.0


def _non_negative_finite(value: object, *, default: float = 0.0) -> float:
try:
numeric = float(value or 0.0)
except (TypeError, ValueError):
return float(default)
if not math.isfinite(numeric):
return float(default)
return max(0.0, numeric)


def minimum_economic_order_notional_usd(profile: BrokerCostProfile | None) -> float:
"""Return the minimum order notional implied by fixed order costs.

The helper intentionally models only fixed or minimum per-order fees. Per-share
fees and sell-side regulatory fees do not produce a stable notional floor and
should be handled by cost reporting/backtests rather than blocking risk exits.
"""

if profile is None:
return 0.0
explicit_floor = _non_negative_finite(profile.explicit_min_order_notional_usd)
fee_floor = max(
_non_negative_finite(profile.fixed_order_fee_usd),
_non_negative_finite(profile.minimum_order_fee_usd),
)
max_fee_bps = _non_negative_finite(profile.max_fixed_fee_bps)
if fee_floor <= 0.0 or max_fee_bps <= 0.0:
return explicit_floor
implied_floor = fee_floor / (max_fee_bps / 10_000.0)
return max(explicit_floor, implied_floor)
48 changes: 48 additions & 0 deletions tests/test_broker_costs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import unittest

from quant_platform_kit.common.broker_costs import (
BrokerCostProfile,
minimum_economic_order_notional_usd,
)


class BrokerCostTests(unittest.TestCase):
def test_zero_fixed_fee_keeps_explicit_floor(self):
self.assertEqual(
minimum_economic_order_notional_usd(
BrokerCostProfile(explicit_min_order_notional_usd=25.0)
),
25.0,
)

def test_minimum_order_fee_sets_economic_floor(self):
self.assertEqual(
minimum_economic_order_notional_usd(
BrokerCostProfile(minimum_order_fee_usd=0.35, max_fixed_fee_bps=50.0)
),
70.0,
)

def test_longbridge_like_fixed_fee_uses_bps_limit(self):
self.assertEqual(
minimum_economic_order_notional_usd(
BrokerCostProfile(fixed_order_fee_usd=0.99, max_fixed_fee_bps=100.0)
),
99.0,
)

def test_explicit_floor_can_be_more_conservative_than_fee_floor(self):
self.assertEqual(
minimum_economic_order_notional_usd(
BrokerCostProfile(
fixed_order_fee_usd=0.99,
max_fixed_fee_bps=100.0,
explicit_min_order_notional_usd=150.0,
)
),
150.0,
)


if __name__ == "__main__":
unittest.main()