"""Pure decomposed-slippage measurement model (design §3) — the testnet real-paper leg's first-class output.

Three pure functions over floats (no I/O, no network) so the bps math is unit-tested in isolation. The Bybit
execution adapter supplies `fill_price`, `mid_at_order` (the live order-book mid AT order time) and `book_mark`
(the book's mark/close the targets were sized off); these split the total fill-vs-mark move into:

  * `execution_slippage_bps` — fill vs the live mid: what the A8 cost model claims to capture (the 5/8/6/35bp
    taker+spread tiers). >0 = paid worse than mid.
  * `timing_drift_bps` — the live mid vs the book mark: the close→execution price move, a SEPARATE effect.
    Conflating the two (v1's bug) made the number meaningless; the decomposition is the whole point.

`slippage_vs_assumed_bps` is the validation delta vs the per-symbol assumed cost tier. Sign convention:
`+1` for BUY, `-1` for SELL, so a positive bp is always an adverse cost regardless of side. Guards: a
non-positive `mid_at_order`/`book_mark` (empty/one-sided thin testnet book) returns `None` — never a
divide-by-zero or a garbage bp; the caller flags such a fill `low_confidence` and excludes it from the verdict.
"""
from __future__ import annotations


def _sign(side: str) -> int:
    return 1 if side.upper() == "BUY" else -1


def execution_slippage_bps(fill: float, mid_at_order: float, side: str) -> float | None:
    """Fill vs the live mid at order time, in bps (>0 = paid worse than mid). `None` if `mid_at_order <= 0`."""
    if mid_at_order <= 0.0:
        return None
    return _sign(side) * (fill - mid_at_order) / mid_at_order * 1e4


def timing_drift_bps(mid_at_order: float, book_mark: float, side: str) -> float | None:
    """The live mid vs the book mark, in bps (>0 = market moved adversely since the mark). `None` if
    `book_mark <= 0`."""
    if book_mark <= 0.0:
        return None
    return _sign(side) * (mid_at_order - book_mark) / book_mark * 1e4


def slippage_vs_assumed_bps(execution_slippage: float, assumed_tier_bps: float) -> float:
    """The validation delta: measured execution slippage minus the assumed A8 cost tier (>0 = worse than
    assumed)."""
    return execution_slippage - assumed_tier_bps
