[#69] Whole-unit bond trades + takeable-only market view + honest preview #70

Merged
robertismo merged 3 commits from 69-bond-units into master 2026-09-29 21:13:31 +00:00
Owner

Explanation of what you saw, and the fix (PR #69)

Why "took 0 cash" after the preview said 9180: those four offers (919/10, 865/3, 831/6, 775/29) were your own standing offers from earlier turns. The engine's fill correctly refuses to take your own bonds (Issuer != you), but the take screen's preview didn't know that — it summed your own offers, showed 9180, and the engine then filled 0. You were right to be confused; the preview was lying.

Why you'd never seen an AI offer: nothing was hidden — openBondMarket() returns every committed standing offer from every bank. The AIs simply hadn't offered at those moments (their offer hooks only fire on deep cash surplus). The view you had WAS the entire market, including your own rows.

Fixes in this PR (69-bond-units, commit d17744a):

  1. Whole-unit bond trading. Takes fill whole units only (your 9180 take now fills 9 units: receive 8,271, owe 9,000), and issuers' fractional unit sizes are floored so no unfillable tails sit in the market. The take preview uses the same math, so it can't disagree with the engine again. Two regression tests added (TestBondTakeWholeUnits, TestPlaceOffersWholeUnits).
  2. Take screen shows only takeable offers. Your own standing offers are excluded from the table and the preview, with the header saying so: "3 standing offers you can take · your own 1 excluded". If only your own offers exist: "you cannot take your own bonds".
  3. The standing market is shown BEFORE you offer bonds (you price against what's actually out there), labeled "N standing offers (the entire market)".
  4. The preview's per-turn rate is now blended across the offers' actual turns left (your 0.8814% case was using the fixed 10-turn term instead of the offer's 7; the true blended cost there is ~1.26%/turn).

go build, go test . (with the new tests), go vet ./... green; spectate regression 0 [ai errors, 0 divergences.

## Explanation of what you saw, and the fix (PR #69) **Why "took 0 cash" after the preview said 9180:** those four offers (919/10, 865/3, 831/6, 775/29) were **your own** standing offers from earlier turns. The engine's fill correctly refuses to take your own bonds (Issuer != you), but the take screen's preview didn't know that — it summed your own offers, showed 9180, and the engine then filled 0. You were right to be confused; the preview was lying. **Why you'd never seen an AI offer:** nothing was hidden — `openBondMarket()` returns every committed standing offer from every bank. The AIs simply hadn't offered at those moments (their offer hooks only fire on deep cash surplus). The view you had WAS the entire market, including your own rows. **Fixes in this PR (`69-bond-units`, commit d17744a):** 1. **Whole-unit bond trading.** Takes fill whole units only (your 9180 take now fills 9 units: receive 8,271, owe 9,000), and issuers' fractional unit sizes are floored so no unfillable tails sit in the market. The take preview uses the same math, so it can't disagree with the engine again. Two regression tests added (`TestBondTakeWholeUnits`, `TestPlaceOffersWholeUnits`). 2. **Take screen shows only takeable offers.** Your own standing offers are excluded from the table and the preview, with the header saying so: "3 standing offers you can take · your own 1 excluded". If only your own offers exist: "you cannot take your own bonds". 3. **The standing market is shown BEFORE you offer bonds** (you price against what's actually out there), labeled "N standing offers (the entire market)". 4. The preview's per-turn rate is now blended across the offers' actual turns left (your 0.8814% case was using the fixed 10-turn term instead of the offer's 7; the true blended cost there is ~1.26%/turn). `go build`, `go test .` (with the new tests), `go vet ./...` green; spectate regression 0 `[ai` errors, 0 divergences.
[#69] Whole-unit bond trades, takeable-only market, honest take preview
All checks were successful
build / build (push) Successful in 24s
d17744a64a
First-time contributor

this is incorrect though because a user should be able to take their own bonds out, not only in general, but being able to account for this is important when a bank acquires another that owes the aqcuirer(which should be present to an acquiring bank so they understand their position against the insolvent entity.)

Another thing that is incorrect is your taking logic, if I ask for a loan of 9180 I am assuming the need is at least for 9180, you should take the next highest available unit.

@Winnie

this is incorrect though because a user should be able to take their own bonds out, not only in general, but being able to account for this is important when a bank acquires another that owes the aqcuirer(which should be present to an acquiring bank so they understand their position against the insolvent entity.) Another thing that is incorrect is your taking logic, if I ask for a loan of 9180 I am assuming the need is at least for 9180, you should take the next highest available unit. @Winnie
Merge branch 'master' into 69-bond-units
All checks were successful
build / build (push) Successful in 27s
d6f42a1645
robertismo left a comment

changes listed above

changes listed above
[#69] review fixes: own bonds takeable (retire), fills round up to meet the request
All checks were successful
build / build (push) Successful in 24s
20fb84ae69
Author
Owner

Both review points are fixed in commit 20fb84a:

1. Own bonds are takeable again. The take market shows the entire market with your own rows marked (yours). Taking your own standing offer retires it — the committed cash returns and no debt or asset is created (self-transaction nets to zero, CF-booked as a return, verified by the accounting identity tests). This also covers the acquisition case: offers an acquirer inherits from an insolvent bank are its own and can be unwound the same way, and the auction prompt now shows the insolvent bank's books (net, loans, bond assets, bond debts, standing offers) so the bidder sees the position it would inherit, including debts owed to it. The AI hooks and the engine's emergency finance borrow stay foreign-only (borrowing semantics), so the #45 insolvency regression still passes.

2. Takes now fill AT LEAST the asked amount. Units round UP: asking for 9,180 against 919-price bonds takes 10 units (receive 9,190, owe 10,000), never 9,827... and never 9. The preview runs the identical fill math, so what it shows is exactly what the engine does — verified end to end in a scripted game: want 40,000, market had 10 units @1000.5 + own 29 @1000 → preview "receive 39005 cash, new debt 200000 face (189.9%/turn) · retires 29 of your own units" → confirmed take matched to the cent.

Tests: TestBondTakeWholeUnits (ceil), TestBondTakeOwnOfferRetires (cash restored exactly, no debt/asset created), TestPlaceOffersWholeUnits. go build, go test ., go vet ./... green; spectate 0 [ai errors, 0 divergences.

Both review points are fixed in commit `20fb84a`: **1. Own bonds are takeable again.** The take market shows the entire market with your own rows marked `(yours)`. Taking your own standing offer retires it — the committed cash returns and no debt or asset is created (self-transaction nets to zero, CF-booked as a return, verified by the accounting identity tests). This also covers the acquisition case: offers an acquirer inherits from an insolvent bank are its own and can be unwound the same way, and the auction prompt now shows the insolvent bank's books (net, loans, bond assets, bond debts, standing offers) so the bidder sees the position it would inherit, including debts owed to it. The AI hooks and the engine's emergency finance borrow stay foreign-only (borrowing semantics), so the #45 insolvency regression still passes. **2. Takes now fill AT LEAST the asked amount.** Units round UP: asking for 9,180 against 919-price bonds takes 10 units (receive 9,190, owe 10,000), never 9,827... and never 9. The preview runs the identical fill math, so what it shows is exactly what the engine does — verified end to end in a scripted game: want 40,000, market had 10 units @1000.5 + own 29 @1000 → preview "receive 39005 cash, new debt 200000 face (189.9%/turn) · retires 29 of your own units" → confirmed take matched to the cent. Tests: `TestBondTakeWholeUnits` (ceil), `TestBondTakeOwnOfferRetires` (cash restored exactly, no debt/asset created), `TestPlaceOffersWholeUnits`. `go build`, `go test .`, `go vet ./...` green; spectate 0 `[ai` errors, 0 divergences.
Sign in to join this conversation.
No reviewers
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!70
No description provided.