Loan aqcuisition broken #149

Closed
opened 2026-10-02 19:12:55 +00:00 by robertismo · 5 comments

Okay currently filling loans does not at all work how it should. Review the code and give me the exact algorithm that defines how loans are filled, there is differently an issue with how it is functioning.

Okay currently filling loans does not at all work how it should. Review the code and give me the exact algorithm that defines how loans are filled, there is differently an issue with how it is functioning.
Owner

Reading the fill code end to end right now — I'll lay out the exact algorithm as implemented, with the line-level steps, and where the behavior you're seeing comes from.

Reading the fill code end to end right now — I'll lay out the exact algorithm as implemented, with the line-level steps, and where the behavior you're seeing comes from.
Owner

Read the code, master 1bb49d8. The exact fill algorithm, line by line:

PHASE 3 (bidding): every bank places bids into pendingLoanBids[bankID][loanID] = rate — the human via the sheet, the AIs via their hooks — all against the phase-start market. The sheet warns on overcommit but submits anyway; the engine's real enforcement happens at the award.

PHASE 3b (awardLoans, game.go:369): the shelf is processed in OFFER ORDER — the order the loans were drawn (loan ID order, the "market order"):

  1. Collect that loan's bids. Nobody bid → the loan dies unclaimed, next loan.
  2. Sort: lowest rate first; exact ties → the earliest seat.
  3. Winner = the lowest bidder. Rate = the second-lowest bid (Vickrey); with only one bidder, their own bid.
  4. FUNDING CHECK: if the winner is dead OR winner.Cash < loan.Amount → THE LOAN DIES UNCLAIMED. The runner-up is never considered — game.go:395-402: continue // cannot fund; offer dies unclaimed.
  5. Otherwise the winner's cash drops by the amount and the loan lands on their book at the rate.

Consequences of the current code:

  • The funding check uses LIVE cash, so a winner with a big shelf drains as the loans fund in order — fine — but when their cash runs out, their remaining wins die unclaimed EVEN WHEN the second-lowest bidder has the cash. The runner-up is never asked. That is the "loan acquisition broken" behavior: valid bids exist, the loan still dies.
  • The no-overcommit ruling is enforced only as "the winner can't pay, so the loan dies" — the bid itself was accepted, so the borrower also never gets the money.

The market summary line then reports the funded total, which hides the died-unclaimed loans (they count as neither taken nor funded). The human sees their own unfunded wins, but AI runners-up get no signal at all.

Read the code, master 1bb49d8. The exact fill algorithm, line by line: PHASE 3 (bidding): every bank places bids into pendingLoanBids[bankID][loanID] = rate — the human via the sheet, the AIs via their hooks — all against the phase-start market. The sheet warns on overcommit but submits anyway; the engine's real enforcement happens at the award. PHASE 3b (awardLoans, game.go:369): the shelf is processed in OFFER ORDER — the order the loans were drawn (loan ID order, the "market order"): 1. Collect that loan's bids. Nobody bid → the loan dies unclaimed, next loan. 2. Sort: lowest rate first; exact ties → the earliest seat. 3. Winner = the lowest bidder. Rate = the second-lowest bid (Vickrey); with only one bidder, their own bid. 4. FUNDING CHECK: if the winner is dead OR winner.Cash < loan.Amount → THE LOAN DIES UNCLAIMED. The runner-up is never considered — game.go:395-402: `continue // cannot fund; offer dies unclaimed`. 5. Otherwise the winner's cash drops by the amount and the loan lands on their book at the rate. Consequences of the current code: - The funding check uses LIVE cash, so a winner with a big shelf drains as the loans fund in order — fine — but when their cash runs out, their remaining wins die unclaimed EVEN WHEN the second-lowest bidder has the cash. The runner-up is never asked. That is the "loan acquisition broken" behavior: valid bids exist, the loan still dies. - The no-overcommit ruling is enforced only as "the winner can't pay, so the loan dies" — the bid itself was accepted, so the borrower also never gets the money. The market summary line then reports the funded total, which hides the died-unclaimed loans (they count as neither taken nor funded). The human sees their own unfunded wins, but AI runners-up get no signal at all.
Author

Yes I have seen that issue where the winner cannot commit so the loan just dies and nobody takes it, that is a bug.

Another one is the vickery should not be based on those who cannot fill the loan, this is a big issue where people are undercutting rates on loans that they themselves cannot fill. This cannot happen.

Explain how you plan on solving this.

Yes I have seen that issue where the winner cannot commit so the loan just dies and nobody takes it, that is a bug. Another one is the vickery should not be based on those who cannot fill the loan, this is a big issue where people are undercutting rates on loans that they themselves cannot fill. This cannot happen. Explain how you plan on solving this.
Owner

Here's the plan, one coherent rule that fixes both bugs at once:

ELIGIBILITY FIRST, THEN THE AUCTION. For every loan, the only bidders who exist are the ones who can actually fund it. The award walk keeps the current shelf order, but at each loan it first filters the bidders to the ELIGIBLE set: bid on this loan AND current cash >= the loan amount, where current cash is their live cash after any loans they've already won this walk. Then:

  • The winner = the lowest-rate eligible bidder.
  • The rate = the second-lowest ELIGIBLE bid — so the undercut-by-someone-who-can't-pay maneuver is structurally impossible: a bid from a bank that can't fund the loan is not in the set, so it can never drag the second price down.
  • If exactly one eligible bidder, they pay their own bid (unchanged).
  • The winner always has the cash by construction, so a loan dies unclaimed ONLY when no eligible bidder exists at all — the "winner can't fund, loan dies, runner-up ignored" bug disappears, because the runner-up is simply the next eligible bidder in the sort.

Consequences, all intended:

  • A bank whose cash is drained by its earlier wins in the walk drops out of later loans automatically — the no-overcommit ruling is enforced at the award, exactly as "funds in market order until cash runs out" always meant.
  • The human sheet's overcommit warning stays, and becomes accurate: the overcommitted bids simply stop being eligible once the cash is gone, with the per-loan win lines showing what actually funded.
  • Replay records the eligibility outcome (funded vs dropped-for-cash vs no-bidder).
  • No changes to the AI hook contract; the brains keep bidding rates, the engine now ignores the bids that can't clear. This will shift some bench numbers (brains that were gaming the second price will lose that edge), which is the point.

I'll implement it exactly this way on a branch, with a regression test covering: the runner-up inherits a loan the winner can't fund; an unfundable bid can't set the second price; a drained bank drops out of later loans; the no-eligible case still dies unclaimed. Say go.

Here's the plan, one coherent rule that fixes both bugs at once: ELIGIBILITY FIRST, THEN THE AUCTION. For every loan, the only bidders who exist are the ones who can actually fund it. The award walk keeps the current shelf order, but at each loan it first filters the bidders to the ELIGIBLE set: bid on this loan AND current cash >= the loan amount, where current cash is their live cash after any loans they've already won this walk. Then: - The winner = the lowest-rate eligible bidder. - The rate = the second-lowest ELIGIBLE bid — so the undercut-by-someone-who-can't-pay maneuver is structurally impossible: a bid from a bank that can't fund the loan is not in the set, so it can never drag the second price down. - If exactly one eligible bidder, they pay their own bid (unchanged). - The winner always has the cash by construction, so a loan dies unclaimed ONLY when no eligible bidder exists at all — the "winner can't fund, loan dies, runner-up ignored" bug disappears, because the runner-up is simply the next eligible bidder in the sort. Consequences, all intended: - A bank whose cash is drained by its earlier wins in the walk drops out of later loans automatically — the no-overcommit ruling is enforced at the award, exactly as "funds in market order until cash runs out" always meant. - The human sheet's overcommit warning stays, and becomes accurate: the overcommitted bids simply stop being eligible once the cash is gone, with the per-loan win lines showing what actually funded. - Replay records the eligibility outcome (funded vs dropped-for-cash vs no-bidder). - No changes to the AI hook contract; the brains keep bidding rates, the engine now ignores the bids that can't clear. This will shift some bench numbers (brains that were gaming the second price will lose that edge), which is the point. I'll implement it exactly this way on a branch, with a regression test covering: the runner-up inherits a loan the winner can't fund; an unfundable bid can't set the second price; a drained bank drops out of later loans; the no-eligible case still dies unclaimed. Say go.
Author

@Winnie do it.

@Winnie do it.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Winnie/bankwars#149
No description provided.