"""Tests for the budget rule -- the arithmetic that decides whether real money
completes a book.

The reference case is the Alaves book of 2026-08-15, which the maker completed
at 0.99 against a 1.00 payout: a locked loss. These tests exist so that book
cannot happen again silently.
"""
import sys
sys.path.insert(0, r'E:\soccer\main')
import mm_maker_live as ML

fails = []


def check(label, cond, extra=''):
    print(f'  [{"pass" if cond else "FAIL"}] {label}{("  " + extra) if extra else ""}')
    if not cond:
        fails.append(label)


def leg(px=None, qty=0.0, logged=False, cents=None):
    s = {}
    if px is not None:
        s['fill_px'] = px
    if qty:
        s['fill_qty'] = qty
    if logged:
        s['fill_logged'] = True
    if cents is not None:
        s['price_cents'] = cents
    return s


print('=== the Alaves book: 0.40 + 0.34 + 0.25 = 0.99 vs a 1.00 payout ===')
# as quoted at the time the first leg was posted
alaves = {'ALA': 0.40, 'TIE': 0.34, 'GET': 0.25}
b = ML.book_budget(alaves, {})
check('sum 0.99 is refused outright', b['decision'] == 'gate',
      f"decision={b['decision']} sum={sum(alaves.values()):.2f} "
      f"target={b['target']:.4f}")

# and once ALA had already filled at 0.40, the rest cannot be sold at the touch
b = ML.book_budget(alaves, {'ALA': leg(px=0.40, qty=1.0)})
check('after ALA fills, the touch is not enough', b['scale'] > 1.0,
      f"collected={b['collected']:.2f} need={b['need']:.4f} scale={b['scale']:.3f}")
check('and TIE/GET are priced above the touch',
      b['floor_for']('TIE') > alaves['TIE'] and b['floor_for']('GET') > alaves['GET'],
      f"TIE {alaves['TIE']:.2f}->{b['floor_for']('TIE'):.4f}  "
      f"GET {alaves['GET']:.2f}->{b['floor_for']('GET'):.4f}")

print('\n=== a book with real overround is quoted at the touch ===')
good = {'H': 0.42, 'D': 0.34, 'A': 0.28}     # sums to 1.04
b = ML.book_budget(good, {})
check('decision is quote', b['decision'] == 'quote',
      f"sum={sum(good.values()):.2f} target={b['target']:.4f}")
check('scale <= 1 so legs sit at the touch', b['scale'] <= 1.0,
      f"scale={b['scale']:.4f}")
check('floor equals the touch', all(
    abs(b['floor_for'](t) - good[t]) < 1e-12 for t in good))

print('\n=== fractional fills ===')
# FCKKSC-FCK really did fill 0.50 of 1 at 0.47
b = ML.book_budget(good, {'H': leg(px=0.42, qty=0.5)})
check('half a leg banks half the premium',
      abs(b['collected'] - 0.21) < 1e-9, f"collected={b['collected']:.4f}")
check('the half-filled leg is still open', 'H' in b['open_legs'],
      f"open={b['open_legs']}")
check('and only its remainder counts',
      abs(b['open_qty']['H'] - 0.5) < 1e-9, f"open_qty[H]={b['open_qty']['H']}")

full = ML.book_budget(good, {'H': leg(px=0.42, qty=1.0)})
check('a whole leg banks the whole premium',
      abs(full['collected'] - 0.42) < 1e-9, f"collected={full['collected']:.4f}")
check('and drops out of the open legs', 'H' not in full['open_legs'])
check('half-filled needs MORE than fully-filled', b['need'] > full['need'],
      f"half need={b['need']:.4f} vs full need={full['need']:.4f}")

print('\n=== abandonment ===')
# one leg banked cheap, the rest could never make it up
b = ML.book_budget({'H': 0.42, 'D': 0.34, 'A': 0.28},
                   {'H': leg(px=0.10, qty=1.0)})
check('a book that cannot recover is abandoned', b['decision'] == 'abandon',
      f"collected={b['collected']:.2f} worst={b['worst_over_touch']*100:.1f}c "
      f"over touch (cap {ML.MAX_ABOVE_TOUCH*100:.0f}c)")
check('abandon beats gate once money is committed', b['collected'] > 0)

print('\n=== completed book ===')
done = {t: leg(px=v, qty=1.0) for t, v in good.items()}
b = ML.book_budget(good, done)
check('no open legs', b['open_legs'] == [])
check('collected the full 1.04', abs(b['collected'] - 1.04) < 1e-9,
      f"collected={b['collected']:.4f}")
check('and it beat the target', b['collected'] > b['target'],
      f"{b['collected']:.4f} > {b['target']:.4f}, "
      f"net {(b['collected']-b['target'])*100:+.2f}c")

print('\n=== the ratchet: never re-quote a leg below where it started ===')
# The real ALAGET order log: ALA 40 / TIE 34 / GET 26 held for 29 minutes, then
# GET was re-posted at 25 and filled there. 0.99 collected against 1.00 owed.
alaget = {'ALA': 0.40, 'TIE': 0.34, 'GET': 0.26}
opened = {t: {'floor_px': v} for t, v in alaget.items()}   # latched at entry
drifted = dict(alaget, GET=0.25)                            # touch falls a cent
b = ML.book_budget(drifted, opened)
check('GET is NOT followed down to 0.25',
      abs(b['floor_for']('GET') - 0.26) < 1e-9,
      f"touch 0.25 -> quote {b['floor_for']('GET'):.2f}")
# Asserted "prices to exactly the entry sum" first, which failed at 1.0197 --
# and the code was right. ALAGET's entry sum of 1.00 never cleared target, so
# the budget rule lifts the legs ABOVE the ratchet. The two act together: the
# ratchet stops the quote falling, the budget pushes it up when 1.00 is not
# enough. What must hold is that the quoted sum never drops below either.
quoted = sum(b['floor_for'](t) for t in drifted)
check('quoted sum never falls below the entry sum',
      quoted >= sum(alaget.values()) - 1e-9,
      f"{quoted:.4f} vs entry {sum(alaget.values()):.4f}, "
      f"drifted touch {sum(drifted.values()):.4f}")
check('and never below target once quoting',
      quoted >= b['target'] - 1e-9, f"{quoted:.4f} vs target {b['target']:.4f}")

# and it must still follow the touch UP -- asking more is strictly better
risen = dict(alaget, ALA=0.43)
b = ML.book_budget(risen, opened)
check('a rising touch IS followed up',
      abs(b['floor_for']('ALA') - 0.43) < 1e-9,
      f"touch 0.43 -> quote {b['floor_for']('ALA'):.2f}")

# a ratcheted book that has partly filled still respects the budget floor
b = ML.book_budget(drifted, dict(opened, ALA={'floor_px': 0.40,
                                              'fill_px': 0.40, 'fill_qty': 1.0}))
check('after a fill, floors are the higher of ratchet and budget',
      b['floor_for']('GET') >= 0.26,
      f"GET floor {b['floor_for']('GET'):.4f} (ratchet 0.26)")

print('\n=== the gate only applies before any fill ===')
b = ML.book_budget(alaves, {'ALA': leg(px=0.40, qty=1.0)})
check('a partially filled bad book is never "gated"', b['decision'] != 'gate',
      f"decision={b['decision']}")

print('\n=== a leg closed outside the maker must not stay "collected" ===')
# 2026-08-17: the machine crashed mid-session and six legs were flattened by
# hand in the app. State kept fill_qty=1 and fill_logged=True, so the budget
# went on believing the premium was banked and priced the rest of the book to
# fetch only the remainder of a dollar it owed in full.
phantom = {'H': 0.42, 'D': 0.34, 'A': 0.28}
stale = {'H': leg(px=0.42, qty=1.0, logged=True)}
b = ML.book_budget(phantom, stale)
check('a stale fill is still counted as collected',
      abs(b['collected'] - 0.42) < 1e-9, f"collected={b['collected']:.4f}")
check('and its leg is not offered', 'H' not in b['open_legs'],
      f"open={b['open_legs']}")

# exactly what the [RESET] branch in cycle() writes back
healed = {'H': {'fill_qty': 0.0, 'fill_cash': 0.0, 'fill_px': None,
                'logged_qty': 0.0, 'fill_logged': False, 'floor_px': 0.42}}
b = ML.book_budget(phantom, healed)
check('after the reset nothing is collected', abs(b['collected']) < 1e-9,
      f"collected={b['collected']:.4f}")
check('the leg is open again', 'H' in b['open_legs'], f"open={b['open_legs']}")
check('the full dollar is owed again', b['need'] > 1.0, f"need={b['need']:.4f}")
check('and the ratchet still holds it at its first price',
      abs(b['floor_for']('H') - 0.42) < 1e-9,
      f"H floor {b['floor_for']('H'):.4f}")

print('\n' + ('ALL PASSED' if not fails else f'FAILURES: {fails}'))
sys.exit(1 if fails else 0)
