From db4317bbe0ff5709fa54ade5bec112186ca9aca3 Mon Sep 17 00:00:00 2001 From: Kostya Date: Fri, 14 Aug 2026 09:13:01 +0000 Subject: Add ledgered credit/debit to Account and trade recording to Event Drops AccountType (accounts no longer need to self-identify), gives Account credit()/debit() that append LedgerEntry rows, adds Event.recordTrade()/tradeVolumeOf()/getCommission(), and implements LocalGuessMarketContext.listEvents() via the repository. --- .../guess/service/LocalGuessMarketContext.java | 3 +- .../java/market/guess/service/domain/Account.java | 31 ++++++++++--- .../market/guess/service/domain/AccountType.java | 6 --- .../java/market/guess/service/domain/Event.java | 54 +++++++++++++++------- .../market/guess/service/domain/LedgerEntry.java | 7 ++- .../java/market/guess/service/domain/User.java | 2 +- 6 files changed, 72 insertions(+), 31 deletions(-) delete mode 100644 service/src/main/java/market/guess/service/domain/AccountType.java diff --git a/service/src/main/java/market/guess/service/LocalGuessMarketContext.java b/service/src/main/java/market/guess/service/LocalGuessMarketContext.java index a01d6d7..67e263f 100644 --- a/service/src/main/java/market/guess/service/LocalGuessMarketContext.java +++ b/service/src/main/java/market/guess/service/LocalGuessMarketContext.java @@ -9,6 +9,7 @@ import market.guess.api.GuessMarketContext; import market.guess.api.LoadResultDTO; import market.guess.api.PurchaseReceiptDTO; import market.guess.exception.GuessMarketException; +import market.guess.service.domain.Event; import market.guess.service.repository.EventRepository; public final class LocalGuessMarketContext implements GuessMarketContext { @@ -26,7 +27,7 @@ public final class LocalGuessMarketContext implements GuessMarketContext { @Override public List listEvents() { - throw new UnsupportedOperationException("Unimplemented method 'listEvents'"); + return eventRepository.getAllEvents().stream().map(Event::toEventSummary).toList(); } @Override diff --git a/service/src/main/java/market/guess/service/domain/Account.java b/service/src/main/java/market/guess/service/domain/Account.java index 274260e..7e24cdb 100644 --- a/service/src/main/java/market/guess/service/domain/Account.java +++ b/service/src/main/java/market/guess/service/domain/Account.java @@ -4,28 +4,47 @@ import java.math.BigDecimal; import java.time.Clock; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import market.guess.api.LedgerType; public final class Account { - - private final AccountType accountType; private final String owner; private final Clock clock; private final List entries = new ArrayList<>(); private BigDecimal balance; + private AtomicInteger runningId = new AtomicInteger(); - public Account(AccountType accountType, String owner, BigDecimal initialBalance) { - this(accountType, owner, initialBalance, Clock.systemUTC()); + public Account(String owner, BigDecimal initialBalance) { + this(owner, initialBalance, Clock.systemUTC()); } - public Account(AccountType accountType, String owner, BigDecimal initialBalance, Clock clock) { + public Account(String owner, BigDecimal initialBalance, Clock clock) { super(); - this.accountType = accountType; this.owner = owner; this.balance = BigDecimalOptions.toMoney(initialBalance); this.clock = clock; } + public String getOwner() { + return owner; + } + public BigDecimal getBalance() { return balance; } + + public boolean credit(LedgerType type, BigDecimal amount, String note) { + record(type, amount, note); + return true; + } + + public boolean debit(LedgerType type, BigDecimal amount, String note) { + record(type, amount.negate(), note); + return true; + } + + private void record(LedgerType type, BigDecimal amount, String note) { + entries.add( + new LedgerEntry(runningId.incrementAndGet(), clock.instant(), type, amount, balance, note)); + } } diff --git a/service/src/main/java/market/guess/service/domain/AccountType.java b/service/src/main/java/market/guess/service/domain/AccountType.java deleted file mode 100644 index 293ff4e..0000000 --- a/service/src/main/java/market/guess/service/domain/AccountType.java +++ /dev/null @@ -1,6 +0,0 @@ -package market.guess.service.domain; - -public enum AccountType { - EVENT, - USER -} 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 383126f..2d7ac39 100644 --- a/service/src/main/java/market/guess/service/domain/Event.java +++ b/service/src/main/java/market/guess/service/domain/Event.java @@ -1,8 +1,10 @@ package market.guess.service.domain; import java.math.BigDecimal; +import java.time.Instant; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import market.guess.api.CommissionTiming; import market.guess.api.EventStatus; import market.guess.api.EventSummaryDTO; @@ -20,10 +22,10 @@ public final class Event { private final Account account; private final String marketMaker; + private final AtomicInteger runningId = new AtomicInteger(); private final List trades = new ArrayList<>(); private EventStatus status; private String winningOptionKey; - private long nextTradeSeq = 1; private BigDecimal settlementCommission = BigDecimalOptions.ZERO_MONEY; public Event( @@ -46,7 +48,7 @@ public final class Event { this.mechanism = mechanism; this.options = List.copyOf(options); this.marketMaker = marketMaker; - this.account = new Account(AccountType.EVENT, eventKey, BigDecimalOptions.ZERO_MONEY); + this.account = new Account(eventKey, BigDecimalOptions.ZERO_MONEY); this.status = status; } @@ -114,20 +116,40 @@ public final class Event { this.winningOptionKey = winningOptionKey; } - public long getNextTradeSeq() { - return nextTradeSeq; - } - - public void setNextTradeSeq(long nextTradeSeq) { - this.nextTradeSeq = nextTradeSeq; - } - - public BigDecimal getSettlementCommission() { - return settlementCommission; - } - - public void setSettlementCommission(BigDecimal settlementCommission) { - this.settlementCommission = settlementCommission; + public Trade recordTrade( + Instant time, + String user, + Option option, + int quantity, + BigDecimal cost, + BigDecimal commission) { + var trade = + new Trade( + runningId.incrementAndGet(), + time, + user, + option.getKey(), + option.getName(), + quantity, + cost, + commission); + trades.add(trade); + + return trade; + } + + public BigDecimal tradeVolumeOf(String option) { + return trades.stream() + .filter(trade -> trade.optionKey().equalsIgnoreCase(option)) + .map(Trade::sharesCost) + .reduce(BigDecimalOptions.ZERO_MONEY, BigDecimal::add); + } + + public BigDecimal getCommission() { + return trades.stream() + .map(Trade::commission) + .reduce(BigDecimalOptions.ZERO_MONEY, BigDecimal::add) + .add(settlementCommission); } public EventSummaryDTO toEventSummary() { diff --git a/service/src/main/java/market/guess/service/domain/LedgerEntry.java b/service/src/main/java/market/guess/service/domain/LedgerEntry.java index b9173c8..4ca4b92 100644 --- a/service/src/main/java/market/guess/service/domain/LedgerEntry.java +++ b/service/src/main/java/market/guess/service/domain/LedgerEntry.java @@ -5,4 +5,9 @@ import java.time.Instant; import market.guess.api.LedgerType; public record LedgerEntry( - Instant time, LedgerType type, BigDecimal amount, BigDecimal balanceAfter, String note) {} + int id, + Instant time, + LedgerType type, + BigDecimal amount, + BigDecimal balanceAfter, + String note) {} diff --git a/service/src/main/java/market/guess/service/domain/User.java b/service/src/main/java/market/guess/service/domain/User.java index 3ae1d0f..af9ac78 100644 --- a/service/src/main/java/market/guess/service/domain/User.java +++ b/service/src/main/java/market/guess/service/domain/User.java @@ -8,7 +8,7 @@ public final class User { public User(String name, BigDecimal initialBalance) { this.name = name; - this.account = new Account(AccountType.USER, name, initialBalance); + this.account = new Account(name, initialBalance); } public String getName() { -- cgit v1.2.3