From 9dab2b3d624ecae08439532157fb8b9805fe9274 Mon Sep 17 00:00:00 2001 From: Kostya Date: Tue, 18 Aug 2026 09:07:57 +0000 Subject: Wire event ledger movements, show winning market by name, seed LMSR subsidy - Event ledger now grows on purchase and shrinks on payout, mirroring the exact amounts moved on the buyer's ledger (it previously had a Ledger object that nothing ever wrote to). - Settlement and event-detail views now show the winning market's display name instead of its raw key; event details are viewable for settled events too (the picker previously excluded anything not ACTIVE). - LMSR events seed their ledger with a SUBSIDY entry for the market maker's worst-case loss (b * ln(outcomes)), reusing the existing cost() function rather than recomputing the formula. --- .../guess/service/LocalGuessMarketContext.java | 2 +- .../guess/service/catalog/LocalCatalogContext.java | 6 ++++- .../infrastructure/provider/v1/XMLLoaderV1.java | 2 +- .../java/market/guess/service/domain/Event.java | 12 +++++++++ .../fulfillment/LocalFulfillmentContext.java | 5 ++++ .../market/guess/service/ledger/LedgerContext.java | 30 ++++++++++++---------- .../service/mechanism/LmsrTradingMechanism.java | 5 ++++ .../guess/service/mechanism/TradingMechanism.java | 6 +++++ .../service/settlement/LocalSettlementContext.java | 2 ++ 9 files changed, 54 insertions(+), 16 deletions(-) (limited to 'service') diff --git a/service/src/main/java/market/guess/service/LocalGuessMarketContext.java b/service/src/main/java/market/guess/service/LocalGuessMarketContext.java index 29e3f5c..909a808 100644 --- a/service/src/main/java/market/guess/service/LocalGuessMarketContext.java +++ b/service/src/main/java/market/guess/service/LocalGuessMarketContext.java @@ -86,7 +86,7 @@ public final class LocalGuessMarketContext implements GuessMarketContext { event.toEventSummary(), event.toMarketState(), event.getHistory(), - event.getWinningOptionKey())); + event.getWinningOptionName())); } return Result.error("Couldn't settle event."); diff --git a/service/src/main/java/market/guess/service/catalog/LocalCatalogContext.java b/service/src/main/java/market/guess/service/catalog/LocalCatalogContext.java index 270aadd..ea26107 100644 --- a/service/src/main/java/market/guess/service/catalog/LocalCatalogContext.java +++ b/service/src/main/java/market/guess/service/catalog/LocalCatalogContext.java @@ -42,7 +42,11 @@ public final class LocalCatalogContext implements CatalogContext { var event = optional.get(); return Result.ok( - new EventDetailDTO(event.toEventSummary(), event.toMarketState(), event.getHistory(), "")); + new EventDetailDTO( + event.toEventSummary(), + event.toMarketState(), + event.getHistory(), + event.getWinningOptionName())); } @Override diff --git a/service/src/main/java/market/guess/service/catalog/infrastructure/provider/v1/XMLLoaderV1.java b/service/src/main/java/market/guess/service/catalog/infrastructure/provider/v1/XMLLoaderV1.java index b97114d..bcae787 100644 --- a/service/src/main/java/market/guess/service/catalog/infrastructure/provider/v1/XMLLoaderV1.java +++ b/service/src/main/java/market/guess/service/catalog/infrastructure/provider/v1/XMLLoaderV1.java @@ -63,7 +63,7 @@ public final class XMLLoaderV1 implements Loader { eventRepository.add(mapper.toDomain(event)); } - userRepository.add(new User("Tester", 9_999_999)); + userRepository.add(new User("Tester", 500)); return Result.ok(new LoadResult(path.toString(), eventRepository.getAll().size())); } catch (Exception e) { diff --git a/service/src/main/java/market/guess/service/domain/Event.java b/service/src/main/java/market/guess/service/domain/Event.java index 4c78486..e1b366e 100644 --- a/service/src/main/java/market/guess/service/domain/Event.java +++ b/service/src/main/java/market/guess/service/domain/Event.java @@ -10,9 +10,11 @@ import market.guess.api.MarketStateDTO; import market.guess.api.TradeRowDTO; import market.guess.model.event.EventStatus; import market.guess.model.event.EventSummaryDTO; +import market.guess.model.ledger.LedgerType; import market.guess.service.helpers.BigDecimalOptions; import market.guess.service.helpers.InstantOptions; import market.guess.service.ledger.Ledger; +import market.guess.service.ledger.LedgerEntry; import market.guess.service.mechanism.TradingMechanism; public final class Event { @@ -54,6 +56,12 @@ public final class Event { this.markets = markets; this.marketMaker = marketMaker; this.ledger = new Ledger(eventKey, BigDecimalOptions.ZERO_MONEY); + var seed = mechanism.maxLoss(); + if (seed.signum() > 0) { + ledger.record( + new LedgerEntry( + 0, Instant.now(), LedgerType.SUBSIDY, seed, seed, "Market maker subsidy (max loss)")); + } this.state = status; } @@ -151,6 +159,10 @@ public final class Event { return winningOptionKey; } + public String getWinningOptionName() { + return winningOptionKey == null ? "" : getOption(winningOptionKey).getName(); + } + public void setWinningOptionKey(String winningOptionKey) { this.winningOptionKey = winningOptionKey; } diff --git a/service/src/main/java/market/guess/service/fulfillment/LocalFulfillmentContext.java b/service/src/main/java/market/guess/service/fulfillment/LocalFulfillmentContext.java index 64887dc..cbbfafd 100644 --- a/service/src/main/java/market/guess/service/fulfillment/LocalFulfillmentContext.java +++ b/service/src/main/java/market/guess/service/fulfillment/LocalFulfillmentContext.java @@ -43,6 +43,11 @@ public final class LocalFulfillmentContext implements FulfillmentContext { LedgerType.PURCHASE, trade.totalPaid(), "Purchase: " + order.market().getName()); + ledger.credit( + event.getLedger(), + LedgerType.PURCHASE, + trade.totalPaid(), + "Purchase: " + order.market().getName()); order.market().addVolume(trade.quantity()); recorded.add(trade); } diff --git a/service/src/main/java/market/guess/service/ledger/LedgerContext.java b/service/src/main/java/market/guess/service/ledger/LedgerContext.java index bb1bf97..fb58107 100644 --- a/service/src/main/java/market/guess/service/ledger/LedgerContext.java +++ b/service/src/main/java/market/guess/service/ledger/LedgerContext.java @@ -27,19 +27,23 @@ public final class LedgerContext { return true; } + public boolean credit(Ledger ledger, LedgerType type, BigDecimal amount, String note) { + record(ledger, type, amount, note); + return true; + } + + public boolean debit(Ledger ledger, LedgerType type, BigDecimal amount, String note) { + record(ledger, type, amount.negate(), note); + return true; + } + private void record(String name, LedgerType type, BigDecimal amount, String note) { - users - .get(name) - .ifPresent( - u -> - u.getLedger() - .record( - new LedgerEntry( - runningId++, - clock.instant(), - type, - amount, - u.getLedger().getBalance().add(amount), - note))); + users.get(name).ifPresent(u -> record(u.getLedger(), type, amount, note)); + } + + private void record(Ledger ledger, LedgerType type, BigDecimal amount, String note) { + ledger.record( + new LedgerEntry( + runningId++, clock.instant(), type, amount, ledger.getBalance().add(amount), note)); } } diff --git a/service/src/main/java/market/guess/service/mechanism/LmsrTradingMechanism.java b/service/src/main/java/market/guess/service/mechanism/LmsrTradingMechanism.java index 062a095..981e733 100644 --- a/service/src/main/java/market/guess/service/mechanism/LmsrTradingMechanism.java +++ b/service/src/main/java/market/guess/service/mechanism/LmsrTradingMechanism.java @@ -62,6 +62,11 @@ public final class LmsrTradingMechanism implements TradingMechanism { null)); } + @Override + public BigDecimal maxLoss() { + return BigDecimalOptions.toMoney(cost(new int[q.length])); + } + @Override public BigDecimal[] prices() { var max = Arrays.stream(q).mapToDouble(qi -> (double) qi / liquidity).max().orElse(0); diff --git a/service/src/main/java/market/guess/service/mechanism/TradingMechanism.java b/service/src/main/java/market/guess/service/mechanism/TradingMechanism.java index 6fc5a9a..30d076d 100644 --- a/service/src/main/java/market/guess/service/mechanism/TradingMechanism.java +++ b/service/src/main/java/market/guess/service/mechanism/TradingMechanism.java @@ -7,6 +7,7 @@ import market.guess.api.MechanismType; import market.guess.service.domain.Event; import market.guess.service.domain.Market; import market.guess.service.domain.Trade; +import market.guess.service.helpers.BigDecimalOptions; public sealed interface TradingMechanism permits LmsrTradingMechanism, OrderBookTradingMechanism { @@ -15,4 +16,9 @@ public sealed interface TradingMechanism permits LmsrTradingMechanism, OrderBook List buy(Event event, Market market, Instant at, long quantity); BigDecimal[] prices(); + + /** The subsidy needed to cover the market maker's worst-case loss, if any. */ + default BigDecimal maxLoss() { + return BigDecimalOptions.ZERO_MONEY; + } } diff --git a/service/src/main/java/market/guess/service/settlement/LocalSettlementContext.java b/service/src/main/java/market/guess/service/settlement/LocalSettlementContext.java index 118e349..f6e9b67 100644 --- a/service/src/main/java/market/guess/service/settlement/LocalSettlementContext.java +++ b/service/src/main/java/market/guess/service/settlement/LocalSettlementContext.java @@ -40,6 +40,8 @@ public final class LocalSettlementContext implements SettlementContext { var net = gross.subtract(commission); ledger.credit(entry.getKey(), LedgerType.PAYOUT, net, "Payout: " + winningOption.getName()); + ledger.debit( + event.getLedger(), LedgerType.PAYOUT, net, "Payout: " + winningOption.getName()); event.addSettlementCommission(commission); totalPayout = totalPayout.add(net); } -- cgit v1.2.3