from fxhnt.domain.paper import Position, target_positions, diff_trades, mark_to_market


def test_target_positions_notional_split():
    # sleeve weight 0.5 of 100k = 50k; symbol weight -1.0 (short) at price 2.0 -> qty -25000
    pos = target_positions(sleeve="stablecoin_rotation", sleeve_weight=0.5, capital=100_000.0,
                           symbol_weights={"USDTUSD": -1.0}, prices={"USDTUSD": 2.0}, date="2026-06-21")
    assert pos[0].symbol == "USDTUSD"
    assert pos[0].qty == -25_000.0
    assert pos[0].entry_price == 2.0


def test_diff_trades_entry_exit_resize():
    prior = [Position("s", "BTC", 1.0, 100.0, "2026-06-20")]
    target = [Position("s", "BTC", 1.5, 110.0, "2026-06-21"), Position("s", "ETH", 2.0, 50.0, "2026-06-21")]
    trades = diff_trades(prior, target, ts="2026-06-21T00:00:00Z")
    kinds = {(t.symbol, t.side, t.qty) for t in trades}
    assert ("BTC", "BUY", 0.5) in kinds        # resize up
    assert ("ETH", "BUY", 2.0) in kinds        # new entry
    # a full exit of a prior symbol yields a SELL of its qty
    trades2 = diff_trades([Position("s", "BTC", 1.0, 100.0, "d")], [], ts="t")
    assert (trades2[0].symbol, trades2[0].side, trades2[0].qty) == ("BTC", "SELL", 1.0)


def test_mark_to_market():
    pos = [Position("s", "BTC", 2.0, 100.0, "d")]
    mtm = mark_to_market(pos, prices={"BTC": 110.0})
    assert mtm.unrealized == 20.0     # 2*(110-100)
    assert mtm.per_symbol["BTC"] == 20.0


def test_target_positions_zero_price_skipped_but_negative_weight_kept():
    # px missing/<=0 is skipped; but a legitimate price with a SHORT (negative) weight must NOT be dropped.
    # (regression guard for the `if not px` -> `if px is None` fix; `not px` also fires on 0.0 but the real
    #  hazard is treating a falsy-but-valid value wrongly; the explicit None check is the intent.)
    pos = target_positions(sleeve="s", sleeve_weight=1.0, capital=100.0,
                           symbol_weights={"A": -1.0, "B": 1.0},
                           prices={"A": 2.0, "B": 0.0}, date="d")  # B has zero price -> skip
    syms = {p.symbol for p in pos}
    assert syms == {"A"}          # B skipped (price <= 0), A kept (short, valid price)
    assert pos[0].qty == -50.0    # -1.0 * 100 / 2.0
