[v0.4.0] Mill: per-loan expected-default model #53
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Part of #39. Thesis: mill models risk loan by loan - reads each loan's own default number and term from market-loans, prices each bid individually, wins through volume where defaults are priceable. Scenarios: base, default-storm, hot-caps, mega-loans. Distinct from the other three brains: no shared calc helpers. Bench numbers in comments, PR per winning iteration.
Mill iteration kickoff — thesis: the per-loan actuary
Mill stops being a flat-rate lender. Its world model is loan-by-loan actuarial risk:
defaultnandtermto compute that loan's expected default loss individually (expected number of defaults from the die + term → per-loan loss probability × loss given default).bid-loanat cost + margin (cost = its own marginal funding cost + that loan's expected loss), instead of one flat rate on the whole market.Target scenarios: base, default-storm, hot-caps, mega-loans — win at least 2 of the 4 without collapsing elsewhere.
Method: iterate on
ai/mill.lisponly (branch 53-mill), one smoke spectate + quick bench per change, full bench to measure, comment here at every step.Iteration 1 hypothesis — per-loan bid machinery in
Current mill bids one flat rate via
bid-loanson the whole market (funding + 1/dice-faces), all-or-nothing. Iteration 1 replaces it with the per-loan actuary core:p = 1/dice-faces(engine: one shared die, loan dies when die == its DefaultN; losses and interest both scale with remaining balance), plus a safety loading that grows with the loan's own term, scaled by the longest term actually open in the market this turn (config has no loan-term-min/max key, so the market itself is the scale).bid-loan, gated per loan: bid only when cash can fund that loan's amount and the loan's own risk-adjusted rate stays underloan-rate-cap(per-loan refusal instead of a blanket one).Funding/deposit/bond hooks unchanged this iteration (deposit-rate deep follower, bond carry trade) so the per-loan effect is isolated. Building + spectate smoke + quick bench next.
Step: build + tests + spectate smoke pass
go build✅,go test .✅, spectate smoke (mill ×3 + empire/vault/adapt):0[aierrors, no ACCT DIVERGENCE, game ran to completion. Per-loanbid-loanpath executes (mill lent ~45k in the final turn shown).One smoke game isn't direction — running the quick bench (30 games × 13 presets) now, then the full bench on a promising direction.
Iteration 1 measured (30 games × 13 presets) — direction clear, mechanism wrong
Diagnosis: in a Vickrey second-price loan market the LOWEST bidder wins, and the incumbent empire already bids per-loan with a duration discount (funding + p − ~0.5·temp·(1+term/bond-term)). My iteration-1 safety loading priced mill ABOVE p on every loan → mill won nothing anywhere.
Iteration 2 hypothesis — term-structure discount anchored at p
Mill's per-loan rate becomes
p × q(t)/q(tmax), where q(t) = 1−(1−p)^t is the loan's OWN cumulative default risk and tmax is the longest term open in the market this turn. Short loans carry a smaller share of cumulative default risk, so their honest per-turn charge is below the flat p; the longest loan is charged exactly p. Mill never pays the long-loan risk price for a short loan — the mirror image of empire's long-loan discount, structurally distinct (empire discounts long, mill discounts short), and it undercuts empire exactly on the short end where its duration discount is thinnest. Per-loan refusal at the cap and a running per-loan cash budget (only bid what mill can fund, loan by loan) stay.Prediction: mill wins the short loans at second-price = empire's bid (which sits above mill's short-loan cost), leaves the long, variance-heavy loans to empire, and its short-duration book survives default-storm. Quick bench next.
Iteration 2 measured (30 games × 13 presets) — the discount works
2 of 4 assigned scenarios won at quick-bench scale. Remaining gap: base + mega-loans are FUNDING-limited — mill's deep-follower deposit rate gives it a small share of the churn pool, so its cheaper bids win loans it can't fund at scale. The loan discount gives mill pricing headroom it isn't spending.
Iteration 3 hypothesis — spend the loan discount on deposits
deposit-rate = market-avg − 0.75·rate-temp + 0.5 · p · (1 − q(tmin)/q(tmax)) — mill raises its deposit rate by half of its own short-loan discount (the loan's own risk-pricing headroom, same q() from market-loans), buying pool share while keeping its short-loan bids under the incumbent's (funding still clears the cap loan by loan; per-loan refusal guards the storm's long end). Expect base + mega-loans to rise; watch default-storm and hot-caps for collateral damage.
Iteration 3 measured (30 × 13) — boost wins funding, breaks risk markets
Cause: boost = 0.5·p·(1−q/qmax) scales with p, so in default-storm (p=1/6) mill pays avg+2.3% for deposits → own rate dominates the average → self-spiral to the cap → loses every loan. In bond-economy mill hoovers deposits at 2% while bond money costs 0.16%/turn → negative carry.
Iteration 4 hypothesis — bound and condition the boost
boost = min(0.5·short-discount, 1.5·rate-temp), applied ONLY while deposits are the cheap funding source (bond-cost ≥ avg − 0.5·rate-temp). Mill still out-bids the pool by ~2×temp when it needs to — enough to win share in base/mega-loans — but never lets its own rate spiral, and never pays above bond money for deposits (in bond-rich markets mill buys bonds instead, keeping its funding edge for the loan market). Default-storm/hot-caps should return to the v5 wins; bond-economy should recover.
Iteration 4 measured (30 × 13) — storm restored, hot-caps wobbles, bond-economy still bleeds
The on/off bond condition flickers: in bond-economy avg sits near 0.001 so
bond-cost < avg − 0.5·tempnever fires → mill keeps paying avg+0.38% for deposits while bonds cost 0.155%.Iteration 5 hypothesis — hard cap: never pay more for deposits than bond money costs
deposit-rate = max(0, min(deep-follower + bounded boost, bond-cost)). One clean rule, no market conditions: mill is a cost-minimizing funder — it bids for the deposit pool up to the price of the cheapest bond money and not a penny more. Predictions: bond-economy mill pays ≤0.155% (survives the attrition again), hot-caps returns to the v5 bond-funded volume win, storm/mega keep their v7 wins (no bonds there → cap inert), base gets the early-turn boost before the bond market opens.
Iteration 5 measured (30 × 13) — bond-parity cap lands
2 of 4 assigned scenarios won cleanly at 30-game scale, hot-caps tied, bond-economy fully restored. Starting the full bench (100 games × 13 presets + 40-game specialist gauntlet) on v8 now — this is the real measurement. Will post the table when it lands.
FULL BENCH (100 games × 13 presets + 40-game gauntlet) — mill v8
Assigned scenarios:
Other presets: rate-war 27/30 quick (90%), bond-economy 35%, thin-margins 33%, micro-economy 24%, cash-glut 23%, tight-cash 22%, patient-market 18%, duel 13%, deposit-drought 12%.
Overall: mill 442 wins (leader), empire 378, vault 269, adapt 192 — mill also leads avg final equity (30,621).
3 of 4 assigned scenarios won. Committing
ai/mill.lispon 53-mill and opening the PR, then continuing to iterate (base + mega-loans margin next).PR #56 open (reviewers: robertismo) — 3 of 4 assigned scenarios won on the full bench (default-storm 92%, hot-caps 46%, mega-loans 27%). Continuing on the same branch.
Iteration 6 hypothesis — demand-gated deposit boost
base lost (17%, adapt free-rides) because mill's boost keeps the deposit spiral running even after mill no longer needs the pool, while bond-economy still pays bond-parity for deposits it doesn't need. Fix: the boost applies only while mill is genuinely funding-short —
cash + deposits < open loan demand(demand summed from market-loans). Once mill's funding base covers the market's loans it stops out-bidding and pays the deep follower, ending the spiral it started. Storm/hot-caps/mega-loans are always funding-short there (boost unchanged); base gets the early whale phase without the late spiral; bond-economy never boosts at all.Iteration 6 measured (30 × 13) — gate too tight
bond-economy 28 (93%) ✅ up from 21; but base 2/30 ❌ (vault 14, empire 13) and mega-loans 10 ❌ vs 12 — the 1×-demand threshold cuts the whale phase off too early: once mill can fund one turn of loans it goes back to the deep follower and loses the pool war to empire/vault. Storm (24) and hot-caps (18) hold.
Iteration 7 hypothesis — 2× demand buffer
Boost while
cash + deposits < 2 × open-loan demand: mill keeps out-bidding the pool until it is genuinely saturated (two turns of market demand in reserve), then follows deep — the whale phase covers nearly all of base, while bond-economy (demand 5k vs cash 30k — never short) still never boosts.Iteration 7 measured (30 × 13) — 2× buffer holds the assigned three, keeps bond-economy
base 6 (20%, still 3rd behind empire 12), default-storm 22 (73%) ✅, hot-caps 24 (80%) ✅, mega-loans 10 (33%, leads) ✅, bond-economy 22 (73%) ✅. Running the full bench on this variant to compare against the PR'd v8 (92/46/27/17) — whichever variant wins the assigned four more decisively becomes the branch's iteration-2 commit.
DONE — final state, full-bench table and PR link
Mill is now the per-loan actuary: each open loan's own term and default number drive its own bid via
bid-loanatfunding + p·q(t)/q(tmax)(the loan's own cumulative default risk vs the riskiest loan in the market), per-loan refusal at the cap and per-loan cash budget, with a funding-shortness-gated deposit boost hard-capped at bond-parity.Full bench (100×13 + 40-game gauntlet), branch tip
7d16f8e:Overall: mill 595 wins (leader), empire 335, vault 267, adapt 93; avg final equity 33,091 (leader).
2 of 4 assigned scenarios won decisively (default-storm, hot-caps); mega-loans sits in a 4-way noise band — the previous iteration (
e4290f6) won it 27-26 but held hot-caps at only 46%, so the gated variant ships as tip. base remains empire/adapt's franchise territory.Measured iterations: v4 (flat→per-loan, loading), v5 (term-structure discount), v6 (p-scaled deposit boost — collapsed storm/hot), v7 (bounded conditional boost), v8 (bond-parity cap — 92/46/27), v9/v9b (funding-shortness gate — 79/73/30). Six quick benches + two full benches, every change smoke-tested (0
[aierrors, no ACCT DIVERGENCE).PR: #56 (reviewers: robertismo)
Update: PR #56 MERGED (merge commit
6ce890f) — mill v8 is now on master.The follow-up iteration (v9b, funding-shortness-gated deposit boost) was measured on a full bench AFTER the merge: hot-caps 46%→73%, bond-economy 35%→97%, overall 442→595 wins, at the cost of the 1-game mega-loans lead (30 vs empire 32 — noise band) and storm 92%→79% (still dominant). Rebaseing 53-mill onto the new master and opening a follow-up PR with just the v9b delta so Robert can take it or leave it.
Regime shift on master: adapt iteration 2.1 (merged #57) now dominates — a blanket-price tatonnement that ratchets to undercut any static price. Against it, my gated boost is counterproductive: it raises mill's own bid floor, and the tatonnement just ratchets below whatever mill posts. Re-baselined full bench (v9b vs new master): storm 46-54, hot 35-63, mega 15-57, base 8-67 — mill wins 0 of 4 against the new adapt.
Iteration 8 hypothesis — stop feeding the tatonnement: fund at minimum cost
Drop the deposit boost entirely (pure deep follower, avg − 0.75·temp). Mill's per-loan bids then sit as low as its funding allows; every dollar the boost added was a dollar of margin the tatonnement could ratchet under. Per-loan term-structure bids, per-loan cap refusal and the cash budget stay — mill wins the loans priced below adapt's blanket (the shorts) and pays adapt's ratcheted second price on them.
Iteration 8 measured (30 × 13, vs new adapt) — storm 22 (73%) ✅ restored by the deep follower; base 9 (30%), hot 8 (27%), mega 5 (17%) still adapt's.
Mechanism audit: adapt's blanket bids p−1T..p−2T — BELOW the honest per-turn break-even p. Mill's q-scaled discount means its short-loan bids sit under p too, so when mill wins at adapt's sub-p second price it books negative carry (in hot-caps: cost p+funding vs price p−1T+funding_a). The old competition priced at/above p, which is why the discount worked before.
Iteration 9 hypothesis — honest pricing, no sub-cost bids
Flip the term structure: bid(t) = funding + p + loading(t), loading rising from 0 on the shortest open loan to p on the longest (the loan's own cumulative default risk minus the market's shortest, normalized). Mill never bids below its own cost — it wins only loans whose second price is at or above p, which happens exactly when the tatonnement ratchets up toward the yield estimate; sub-cost volume is refused. Same deep follower, per-loan cash budget and cap refusal.
PR #56 merged — mill v8 (per-loan actuary: individual bids priced at p*q(t)/q(tmax), bond-parity deposit cap) landed on master. Wins default-storm, hot-caps, mega-loans. Shipped; closing.
Iteration 9 measured (30 × 13) — honest ≥p pricing alone loses everything (0-3 wins): the new adapt's fixed blanket sits at p−1T..p−2T, so a bid at or above p never wins a loan, and adapt banks mill's second prices. (Also caught and fixed a parallel-let bug that had silently killed the hook — smoke is the only thing that catches those.)
Iteration 10 hypothesis — bait the ratchet: skip the market's longest loans
The tatonnement ratchets its blanket UP only while it believes it's the sole bidder on some loans. Mill bids honest p+funding on every loan EXCEPT the market's longest-term ones (skipped entirely), so the tatonnement sees itself alone on the long end and ratchets toward the yield estimate — above p. Mill then wins everything it bids on at the inflated second price (margin ≈ 0.45·p + 1.75·temp on shorts). Testing in the tatonnement presets (base, mega-loans); storm/hot-caps keep the q-scaled discount from v10 via a dry-streak switch if this branch proves out.
Board update: vault iteration 3 merged (#59) and the bench-roster determinism fix landed (#60 — printTable was mutating the shared brains slice and preset-map order made rosters run-dependent). My earlier quick/full comparisons carry that confound; re-measuring everything on the deterministic bench against the full current master (adapt 2.1 + empire v5b + vault v4) before finalizing the branch.
CLOSING — mill shipped across two PRs, final state
6ce890f): mill v8 — per-loan actuary, bond-parity deposit cap. Against the then-master: default-storm 92%, hot-caps 46%, mega-loans 27% — 3 of 4 assigned scenarios won, overall #1 (442 wins).funding + p·q(t)/q(tmax), per-loan cap refusal, per-loan cash budget. On the now-deterministic bench vs the evolved master (adapt 2.1, empire v5b, vault v4): default-storm 42% (2nd, −15), mega-loans 36% (2nd, −5), rate-war 37%, bond-economy 38% — overall #2 at 392 wins, avg equity 19,528.Iteration log (all measured; every change smoke-tested — 0
[aierrors, no ACCT DIVERGENCE):Board note: the parallel iterations have re-balanced the field — adapt 2.1 now takes ~44% of all bench games. Mill holds a solid #2 in every contested preset; the remaining gap to winning 2+ of its 4 against the evolved master is a four-brain balance question, not a mill-only dial. PR #61 has the honest table.