"""B6 frozen cost and source assumptions — the source-dated table of record.

The B6 pre-registration (STATE.md, 2026-07-15) requires per-instrument one-way
cost = half-spread + commission at the declared US$5,000 clip, frozen as a
source-dated table before the first run. This module is that table, plus the
frozen data-source decisions the verification gate produced. The TSMOM engine
imports its cost and cash assumptions from here and nowhere else.

Conservatism rule: where an issuer-published 30-day median bid/ask spread was
captured, that figure is frozen with its date; where it was not, a conservative
ceiling of one to three ticks at prevailing price levels is frozen instead.
Overstated spreads bias the survival test *against* passing, which is the
direction assumptions are allowed to err.

Commission model (IBKR Pro, Fixed, US stocks/ETFs; interactivebrokers.co.uk
pricing pages, checked 2026-07-15): USD 0.005 per share, minimum USD 1.00 per
order, maximum 1% of trade value; where the calculated maximum is below the
minimum, the maximum applies (IBKR's published sub-dollar example: 10 shares at
USD 0.20 -> USD 0.02).

Disclosed limitations, per the registration: today's spreads are applied across
the full historical sample, which understates pre-2010 trading costs — a 2x
cost stress may be *reported* as context, but the pass/fail criteria run on
this frozen table only. Q1 (long/short) short-borrow financing sits outside the
frozen cost model by registration; Q2 (long/flat) is unaffected.

Frozen source decisions from the 2026-07-15 verification gate (PASS):
prices — FMP stable family, dividend-adjusted, full history to inception on all
nine symbols; cash — FRED TB3MS monthly series (keyless CSV, earliest
1934-01-01), chosen because FMP's treasury endpoint served only a ~90-day
window on the current plan.
"""

from __future__ import annotations

from dataclasses import dataclass

CHECKED_UTC = "2026-07-15"
CLIP_USD = 5_000.0

COMMISSION_PER_SHARE_USD = 0.005
COMMISSION_MIN_USD = 1.00
COMMISSION_MAX_FRACTION = 0.01
COMMISSION_BASIS = (
    "IBKR Pro Fixed, US stocks/ETFs: USD 0.005/share, min USD 1.00/order, "
    "max 1% of trade value; sub-minimum cap rule per IBKR example "
    "(interactivebrokers.co.uk pricing pages, checked 2026-07-15)"
)

PRICE_SOURCE = (
    "FMP stable family, dividend-adjusted daily closes; verification gate PASS "
    "2026-07-15 (full history to inception on all nine symbols; GLD no-dividend "
    "control at +0.0pp; event ratios within tolerance)"
)
TBILL_SOURCE = (
    "FRED TB3MS monthly 3-month T-bill series, keyless CSV, earliest 1934-01-01 "
    "(checked 2026-07-15); chosen over FMP treasury, which served only a ~90-day "
    "window on the current plan"
)


@dataclass(frozen=True)
class SpreadAssumption:
    """A frozen one-way bid/ask spread assumption with its provenance."""

    bps: float
    basis: str


SPREADS: dict[str, SpreadAssumption] = {
    "SPY": SpreadAssumption(
        bps=1.0,
        basis=(
            "conservative ceiling ~3-5 ticks at >USD 500 price level; "
            "true median ~0.2 bps; frozen 2026-07-15"
        ),
    ),
    "EFA": SpreadAssumption(
        bps=1.0,
        basis="iShares fund page 30-day median bid/ask spread 0.01% as of 2026-06-16",
    ),
    "EEM": SpreadAssumption(
        bps=3.0,
        basis=(
            "conservative ceiling ~1.5 ticks at ~USD 50 price level; "
            "no published median captured; frozen 2026-07-15"
        ),
    ),
    "EWA": SpreadAssumption(
        bps=4.0,
        basis="iShares fund page 30-day median bid/ask spread 0.04% as of 2026-07-10",
    ),
    "IEF": SpreadAssumption(
        bps=1.5,
        basis=(
            "conservative ceiling ~1.5 ticks at ~USD 95 price level; "
            "no published median captured; frozen 2026-07-15"
        ),
    ),
    "TLT": SpreadAssumption(
        bps=1.5,
        basis=(
            "conservative ceiling ~1.5 ticks at ~USD 90 price level; "
            "no published median captured; frozen 2026-07-15"
        ),
    ),
    "GLD": SpreadAssumption(
        bps=1.0,
        basis=(
            "conservative ceiling ~3 ticks at ~USD 300 price level; "
            "no published median captured; frozen 2026-07-15"
        ),
    ),
    "DBC": SpreadAssumption(
        bps=6.0,
        basis=(
            "conservative ceiling ~1.5-2 ticks at USD 27.56 "
            "(Investing.com close, 2026-07-08); no published median captured; "
            "frozen 2026-07-15"
        ),
    ),
    "VNQ": SpreadAssumption(
        bps=2.0,
        basis=(
            "conservative ceiling ~2 ticks at ~USD 90 price level; "
            "no published median captured; frozen 2026-07-15"
        ),
    ),
}


def commission_usd(price: float, clip_usd: float = CLIP_USD) -> float:
    """IBKR Pro Fixed commission for one order of *clip_usd* notional at *price*.

    per-share x shares, floored at the order minimum, capped at 1% of trade
    value — and where the cap is below the minimum, the cap applies (IBKR's
    published sub-dollar example). Fractional shares are not modelled: shares
    are the whole-share count for the clip, minimum one share.
    """

    if price <= 0 or clip_usd <= 0:
        return 0.0
    shares = max(1, int(clip_usd / price))
    trade_value = shares * price
    per_share = shares * COMMISSION_PER_SHARE_USD
    cap = COMMISSION_MAX_FRACTION * trade_value
    if cap < COMMISSION_MIN_USD:
        return cap
    return min(max(per_share, COMMISSION_MIN_USD), cap)


def commission_bps(price: float, clip_usd: float = CLIP_USD) -> float:
    if price <= 0 or clip_usd <= 0:
        return 0.0
    shares = max(1, int(clip_usd / price))
    trade_value = shares * price
    if trade_value <= 0:
        return 0.0
    return commission_usd(price, clip_usd) / trade_value * 10_000.0


def one_way_cost_bps(symbol: str, price: float, clip_usd: float = CLIP_USD) -> float:
    """Frozen one-way trading cost in bps: half the frozen spread plus commission."""

    spread = SPREADS[symbol.strip().upper()]
    return spread.bps / 2.0 + commission_bps(price, clip_usd)


__all__ = [
    "CHECKED_UTC",
    "CLIP_USD",
    "COMMISSION_PER_SHARE_USD",
    "COMMISSION_MIN_USD",
    "COMMISSION_MAX_FRACTION",
    "COMMISSION_BASIS",
    "PRICE_SOURCE",
    "TBILL_SOURCE",
    "SpreadAssumption",
    "SPREADS",
    "commission_usd",
    "commission_bps",
    "one_way_cost_bps",
]
