From eed9265be711432f40247f149c07f9e33511192a Mon Sep 17 00:00:00 2001 From: Kostya Date: Sun, 16 Aug 2026 18:50:15 +0000 Subject: Split DTOs into api/model, extract order matching into service/matching Moves Event/Ledger DTOs out of the flat api package into api/model/{event,ledger}, and moves LocalCatalogContext and order matching logic into dedicated service/catalog and service/matching packages, replacing TradingOperation/TradingOperationImpl. --- api/src/main/java/market/guess/api/AccountDTO.java | 1 + .../main/java/market/guess/api/CatalogContext.java | 6 ++ .../main/java/market/guess/api/EventDetailDTO.java | 9 --- .../main/java/market/guess/api/EventStatus.java | 7 --- .../java/market/guess/api/EventSummaryDTO.java | 15 ----- .../java/market/guess/api/GuessMarketContext.java | 10 +--- .../main/java/market/guess/api/LedgerEntryDTO.java | 4 -- api/src/main/java/market/guess/api/LedgerType.java | 10 ---- .../main/java/market/guess/api/LoadResultDTO.java | 14 +---- .../java/market/guess/api/PurchaseReceiptDTO.java | 11 +--- api/src/main/java/market/guess/api/Result.java | 22 +++++-- .../market/guess/model/event/EventDetailDTO.java | 11 ++++ .../java/market/guess/model/event/EventStatus.java | 7 +++ .../market/guess/model/event/EventSummaryDTO.java | 17 ++++++ .../market/guess/model/ledger/LedgerEntryDTO.java | 4 ++ .../java/market/guess/model/ledger/LedgerType.java | 10 ++++ .../market/guess/service/LocalCatalogContext.java | 45 -------------- .../guess/service/LocalGuessMarketContext.java | 68 +++++++++++----------- .../market/guess/service/TradingOperation.java | 13 ----- .../market/guess/service/TradingOperationImpl.java | 22 ------- .../guess/service/catalog/LocalCatalogContext.java | 64 ++++++++++++++++++++ .../java/market/guess/service/domain/Event.java | 22 ++++--- .../market/guess/service/domain/MarketAction.java | 6 +- .../service/infrastructure/MarketContext.java | 18 +++--- .../infrastructure/mapper/v1/EventMapperV1.java | 2 +- .../infrastructure/mapper/v2/EventMapperV2.java | 2 +- .../infrastructure/provider/LoadValidator.java | 22 ------- .../service/infrastructure/provider/Loader.java | 3 +- .../infrastructure/provider/v1/XMLLoaderV1.java | 14 ++--- .../market/guess/service/ledger/LedgerContext.java | 2 +- .../market/guess/service/ledger/LedgerEntry.java | 2 +- .../service/matching/OrderMatchingContext.java | 13 +++++ .../mechanism/OrderBookTradingMechanism.java | 10 ++-- .../guess/service/mechanism/TradeExecution.java | 5 -- .../guess/service/mechanism/TradingMechanism.java | 3 +- .../mechanism/LmsrTradingMechanismTest.java | 37 ------------ .../mechanism/OrderBookTradingMechanismTest.java | 51 ---------------- .../src/main/java/market/guess/ui/console/App.java | 6 +- .../guess/ui/console/command/BuyMenuCommand.java | 14 +++-- .../console/command/EventDetailsMenuCommand.java | 39 ++++++++++++- .../ui/console/command/ListEventsMenuCommand.java | 1 + .../ui/console/command/LoadFileMenuCommand.java | 11 +--- .../command/LoadMarketStateMenuCommand.java | 10 ++-- .../command/SaveMarketStateMenuCommand.java | 10 ++-- .../market/guess/ui/console/io/InputProcessor.java | 11 +++- 45 files changed, 307 insertions(+), 377 deletions(-) delete mode 100644 api/src/main/java/market/guess/api/EventDetailDTO.java delete mode 100644 api/src/main/java/market/guess/api/EventStatus.java delete mode 100644 api/src/main/java/market/guess/api/EventSummaryDTO.java delete mode 100644 api/src/main/java/market/guess/api/LedgerEntryDTO.java delete mode 100644 api/src/main/java/market/guess/api/LedgerType.java create mode 100644 api/src/main/java/market/guess/model/event/EventDetailDTO.java create mode 100644 api/src/main/java/market/guess/model/event/EventStatus.java create mode 100644 api/src/main/java/market/guess/model/event/EventSummaryDTO.java create mode 100644 api/src/main/java/market/guess/model/ledger/LedgerEntryDTO.java create mode 100644 api/src/main/java/market/guess/model/ledger/LedgerType.java delete mode 100644 service/src/main/java/market/guess/service/LocalCatalogContext.java delete mode 100644 service/src/main/java/market/guess/service/TradingOperation.java delete mode 100644 service/src/main/java/market/guess/service/TradingOperationImpl.java create mode 100644 service/src/main/java/market/guess/service/catalog/LocalCatalogContext.java delete mode 100644 service/src/main/java/market/guess/service/infrastructure/provider/LoadValidator.java create mode 100644 service/src/main/java/market/guess/service/matching/OrderMatchingContext.java delete mode 100644 service/src/main/java/market/guess/service/mechanism/TradeExecution.java delete mode 100644 service/src/test/java/market/guess/service/mechanism/LmsrTradingMechanismTest.java delete mode 100644 service/src/test/java/market/guess/service/mechanism/OrderBookTradingMechanismTest.java diff --git a/api/src/main/java/market/guess/api/AccountDTO.java b/api/src/main/java/market/guess/api/AccountDTO.java index 8e7123e..134c88b 100644 --- a/api/src/main/java/market/guess/api/AccountDTO.java +++ b/api/src/main/java/market/guess/api/AccountDTO.java @@ -1,6 +1,7 @@ package market.guess.api; import java.util.List; +import market.guess.model.ledger.LedgerEntryDTO; public record AccountDTO( String owner, String balance, boolean blocked, List entries) {} diff --git a/api/src/main/java/market/guess/api/CatalogContext.java b/api/src/main/java/market/guess/api/CatalogContext.java index 7e80042..c980ba8 100644 --- a/api/src/main/java/market/guess/api/CatalogContext.java +++ b/api/src/main/java/market/guess/api/CatalogContext.java @@ -2,6 +2,8 @@ package market.guess.api; import java.nio.file.Path; import java.util.List; +import market.guess.model.event.EventDetailDTO; +import market.guess.model.event.EventSummaryDTO; public interface CatalogContext { Result loadEvents(Path path); @@ -9,4 +11,8 @@ public interface CatalogContext { Result> getAllEvents(); Result getEvent(String eventKey); + + Result saveState(Path path); + + Result restoreState(Path path); } diff --git a/api/src/main/java/market/guess/api/EventDetailDTO.java b/api/src/main/java/market/guess/api/EventDetailDTO.java deleted file mode 100644 index 893f153..0000000 --- a/api/src/main/java/market/guess/api/EventDetailDTO.java +++ /dev/null @@ -1,9 +0,0 @@ -package market.guess.api; - -import java.util.List; - -public record EventDetailDTO( - EventSummaryDTO summary, - MarketStateDTO state, - List history, - String winningOption) {} diff --git a/api/src/main/java/market/guess/api/EventStatus.java b/api/src/main/java/market/guess/api/EventStatus.java deleted file mode 100644 index 6580157..0000000 --- a/api/src/main/java/market/guess/api/EventStatus.java +++ /dev/null @@ -1,7 +0,0 @@ -package market.guess.api; - -public enum EventStatus { - DRAFT, - ACTIVE, - SETTLED -} diff --git a/api/src/main/java/market/guess/api/EventSummaryDTO.java b/api/src/main/java/market/guess/api/EventSummaryDTO.java deleted file mode 100644 index d185103..0000000 --- a/api/src/main/java/market/guess/api/EventSummaryDTO.java +++ /dev/null @@ -1,15 +0,0 @@ -package market.guess.api; - -import java.util.List; - -public record EventSummaryDTO( - String key, - int displayId, - String name, - String description, - int commissionPercent, - CommissionTiming commissionTiming, - MechanismType mechanism, - EventStatus status, - List optionNames, - String accountBalance) {} diff --git a/api/src/main/java/market/guess/api/GuessMarketContext.java b/api/src/main/java/market/guess/api/GuessMarketContext.java index 4eee37e..7ec7323 100644 --- a/api/src/main/java/market/guess/api/GuessMarketContext.java +++ b/api/src/main/java/market/guess/api/GuessMarketContext.java @@ -1,16 +1,10 @@ package market.guess.api; -import java.nio.file.Path; +import market.guess.model.event.EventDetailDTO; public interface GuessMarketContext { Result buyShares( String userName, String eventKey, String optionKey, int quantity); - Result closeEvent(String userName, String eventKey, String winningOptionKey); - - Result account(String userName); - - Result saveState(Path path); - - Result restoreState(Path path); + Result settleEvent(String userName, String eventKey, String winningOptionKey); } diff --git a/api/src/main/java/market/guess/api/LedgerEntryDTO.java b/api/src/main/java/market/guess/api/LedgerEntryDTO.java deleted file mode 100644 index b8f994c..0000000 --- a/api/src/main/java/market/guess/api/LedgerEntryDTO.java +++ /dev/null @@ -1,4 +0,0 @@ -package market.guess.api; - -public record LedgerEntryDTO( - long seq, String at, LedgerType type, String amount, String balanceAfter, String note) {} diff --git a/api/src/main/java/market/guess/api/LedgerType.java b/api/src/main/java/market/guess/api/LedgerType.java deleted file mode 100644 index 3402b84..0000000 --- a/api/src/main/java/market/guess/api/LedgerType.java +++ /dev/null @@ -1,10 +0,0 @@ -package market.guess.api; - -public enum LedgerType { - SUBSIDY, - PURCHASE, - COMMISSION, - PAYOUT, - REFUND, - DEPOSIT -} diff --git a/api/src/main/java/market/guess/api/LoadResultDTO.java b/api/src/main/java/market/guess/api/LoadResultDTO.java index 8cae3c0..95c23e7 100644 --- a/api/src/main/java/market/guess/api/LoadResultDTO.java +++ b/api/src/main/java/market/guess/api/LoadResultDTO.java @@ -1,15 +1,3 @@ package market.guess.api; -import java.util.List; - -public record LoadResultDTO( - boolean success, String source, int eventsLoaded, List issues) { - - public static LoadResultDTO success(String sourceLabel, int eventsLoaded) { - return new LoadResultDTO(true, sourceLabel, eventsLoaded, List.of()); - } - - public static LoadResultDTO failure(String sourceLabel, List problems) { - return new LoadResultDTO(false, sourceLabel, 0, List.copyOf(problems)); - } -} +public record LoadResultDTO(String source, int eventsLoaded) {} diff --git a/api/src/main/java/market/guess/api/PurchaseReceiptDTO.java b/api/src/main/java/market/guess/api/PurchaseReceiptDTO.java index bd5b2f7..e98a080 100644 --- a/api/src/main/java/market/guess/api/PurchaseReceiptDTO.java +++ b/api/src/main/java/market/guess/api/PurchaseReceiptDTO.java @@ -1,10 +1,5 @@ package market.guess.api; -public record PurchaseReceiptDTO( - String eventKey, - String optionName, - long quantity, - String sharesCost, - String commission, - String totalPaid, - MarketStateDTO stateAfter) {} +import java.util.List; + +public record PurchaseReceiptDTO(List trades, MarketStateDTO stateAfter) {} diff --git a/api/src/main/java/market/guess/api/Result.java b/api/src/main/java/market/guess/api/Result.java index 15ac5db..ff66761 100644 --- a/api/src/main/java/market/guess/api/Result.java +++ b/api/src/main/java/market/guess/api/Result.java @@ -5,30 +5,36 @@ import java.time.Instant; public final class Result { private final boolean success; private final String message; + private final String details; private final T data; private final Instant timeStamp; - private Result(boolean success, String message, T data) { + private Result(boolean success, String message, String details, T data) { this.success = success; this.message = message; this.data = data; + this.details = details; this.timeStamp = Instant.now(); } public static Result ok() { - return new Result(true, "Operation successful", null); + return new Result(true, "Operation successful", "", null); } public static Result ok(T data) { - return new Result<>(true, "Operation successful", data); + return new Result<>(true, "Operation successful", "", data); } - public static Result error(String message, T data) { - return new Result<>(false, message, data); + public static Result error(String message, String details, T data) { + return new Result<>(false, message, details, data); + } + + public static Result error(String message, String details) { + return error(message, details, null); } public static Result error(String message) { - return new Result<>(false, message, null); + return error(message, null, null); } public boolean isSuccess() { @@ -39,6 +45,10 @@ public final class Result { return message; } + public String getDetails() { + return details; + } + public T getData() { return data; } diff --git a/api/src/main/java/market/guess/model/event/EventDetailDTO.java b/api/src/main/java/market/guess/model/event/EventDetailDTO.java new file mode 100644 index 0000000..e45458b --- /dev/null +++ b/api/src/main/java/market/guess/model/event/EventDetailDTO.java @@ -0,0 +1,11 @@ +package market.guess.model.event; + +import java.util.List; +import market.guess.api.MarketStateDTO; +import market.guess.api.TradeRowDTO; + +public record EventDetailDTO( + EventSummaryDTO summary, + MarketStateDTO state, + List history, + String winningOption) {} diff --git a/api/src/main/java/market/guess/model/event/EventStatus.java b/api/src/main/java/market/guess/model/event/EventStatus.java new file mode 100644 index 0000000..c6460f9 --- /dev/null +++ b/api/src/main/java/market/guess/model/event/EventStatus.java @@ -0,0 +1,7 @@ +package market.guess.model.event; + +public enum EventStatus { + DRAFT, + ACTIVE, + SETTLED +} diff --git a/api/src/main/java/market/guess/model/event/EventSummaryDTO.java b/api/src/main/java/market/guess/model/event/EventSummaryDTO.java new file mode 100644 index 0000000..1595f81 --- /dev/null +++ b/api/src/main/java/market/guess/model/event/EventSummaryDTO.java @@ -0,0 +1,17 @@ +package market.guess.model.event; + +import java.util.List; +import market.guess.api.CommissionTiming; +import market.guess.api.MechanismType; + +public record EventSummaryDTO( + String key, + int displayId, + String name, + String description, + int commissionPercent, + CommissionTiming commissionTiming, + MechanismType mechanism, + EventStatus status, + List optionNames, + String accountBalance) {} diff --git a/api/src/main/java/market/guess/model/ledger/LedgerEntryDTO.java b/api/src/main/java/market/guess/model/ledger/LedgerEntryDTO.java new file mode 100644 index 0000000..733ad03 --- /dev/null +++ b/api/src/main/java/market/guess/model/ledger/LedgerEntryDTO.java @@ -0,0 +1,4 @@ +package market.guess.model.ledger; + +public record LedgerEntryDTO( + long seq, String at, LedgerType type, String amount, String balanceAfter, String note) {} diff --git a/api/src/main/java/market/guess/model/ledger/LedgerType.java b/api/src/main/java/market/guess/model/ledger/LedgerType.java new file mode 100644 index 0000000..b1fe15b --- /dev/null +++ b/api/src/main/java/market/guess/model/ledger/LedgerType.java @@ -0,0 +1,10 @@ +package market.guess.model.ledger; + +public enum LedgerType { + SUBSIDY, + PURCHASE, + COMMISSION, + PAYOUT, + REFUND, + DEPOSIT +} diff --git a/service/src/main/java/market/guess/service/LocalCatalogContext.java b/service/src/main/java/market/guess/service/LocalCatalogContext.java deleted file mode 100644 index 15a5890..0000000 --- a/service/src/main/java/market/guess/service/LocalCatalogContext.java +++ /dev/null @@ -1,45 +0,0 @@ -package market.guess.service; - -import java.nio.file.Path; -import java.util.List; -import market.guess.api.CatalogContext; -import market.guess.api.EventDetailDTO; -import market.guess.api.EventSummaryDTO; -import market.guess.api.LoadResultDTO; -import market.guess.api.Result; -import market.guess.service.domain.Event; -import market.guess.service.infrastructure.MarketContext; -import market.guess.service.infrastructure.provider.Loader; - -public final class LocalCatalogContext implements CatalogContext { - - private final MarketContext context; - private final Loader provider; - - public LocalCatalogContext(Loader provider, MarketContext context) { - super(); - this.provider = provider; - this.context = context; - } - - @Override - public Result loadEvents(Path path) { - return Result.ok(provider.load(path)); - } - - @Override - public Result> getAllEvents() { - return Result.ok(context.getEvents().getAll().stream().map(Event::toEventSummary).toList()); - } - - @Override - public Result getEvent(String eventKey) { - var optional = context.getEvents().get(eventKey); - - if (optional.isEmpty()) return Result.error("Unable to find event."); - var event = optional.get(); - - return Result.ok( - new EventDetailDTO(event.toEventSummary(), event.toMarketState(), event.getHistory(), "")); - } -} diff --git a/service/src/main/java/market/guess/service/LocalGuessMarketContext.java b/service/src/main/java/market/guess/service/LocalGuessMarketContext.java index 8f97432..3feef6a 100644 --- a/service/src/main/java/market/guess/service/LocalGuessMarketContext.java +++ b/service/src/main/java/market/guess/service/LocalGuessMarketContext.java @@ -1,25 +1,21 @@ package market.guess.service; -import java.nio.file.Path; -import java.util.List; -import market.guess.api.AccountDTO; -import market.guess.api.EventDetailDTO; import market.guess.api.GuessMarketContext; -import market.guess.api.LoadResultDTO; -import market.guess.api.ProblemDTO; import market.guess.api.PurchaseReceiptDTO; import market.guess.api.Result; +import market.guess.api.TradeRowDTO; +import market.guess.model.event.EventDetailDTO; import market.guess.service.infrastructure.MarketContext; +import market.guess.service.matching.OrderMatchingContext; public final class LocalGuessMarketContext implements GuessMarketContext { - - private final TradingOperation tradingOperation; + private final OrderMatchingContext orderMatching; private final MarketContext context; - public LocalGuessMarketContext(MarketContext context, TradingOperation tradingOperation) { + public LocalGuessMarketContext(MarketContext context, OrderMatchingContext orderMatching) { super(); this.context = context; - this.tradingOperation = tradingOperation; + this.orderMatching = orderMatching; } @Override @@ -28,42 +24,46 @@ public final class LocalGuessMarketContext implements GuessMarketContext { var optional = context.getEvents().get(eventKey); var userOptional = context.getUsers().get(userName); - if (optional.isEmpty() || userOptional.isEmpty()) Result.error("Unable to find user/event."); + if (optional.isEmpty() || userOptional.isEmpty()) + return Result.error("Unable to find user/event."); var event = optional.get(); var user = userOptional.get(); var option = event.getOption(optionKey); - var trade = tradingOperation.buy(event, user, option, quantity); - return Result.ok(new PurchaseReceiptDTO()); + + var trades = orderMatching.submitOrder(user, event, option, quantity); + return Result.ok( + new PurchaseReceiptDTO( + trades.stream() + .map( + t -> + new TradeRowDTO( + t.at().toString(), + t.userName(), + t.optionName(), + String.valueOf(t.quantity()), + t.totalPaid().toPlainString())) + .toList(), + event.toMarketState())); } @Override - public Result closeEvent( + public Result settleEvent( String userName, String eventKey, String winningOptionKey) { - throw new UnsupportedOperationException("Unimplemented method 'account'"); - } + var optional = context.getEvents().get(eventKey); - @Override - public Result account(String userName) { - throw new UnsupportedOperationException("Unimplemented method 'account'"); - } + if (optional.isEmpty()) return Result.error("Unknown event."); - @Override - public Result saveState(Path path) { - context.save(); - return Result.ok(); - } + var event = optional.get(); + if (event.canSettle(userName)) { - @Override - public Result restoreState(Path path) { + var payout = event.getPayout(); - try { - context.load(); - } catch (Exception e) { - return Result.error( - "Failed to load.", - LoadResultDTO.failure(path.toString(), List.of(new ProblemDTO("", e.getMessage(), "")))); + return Result.ok( + new EventDetailDTO( + event.toEventSummary(), event.toMarketState(), event.getHistory(), "")); } - return Result.ok(LoadResultDTO.success(path.toString(), context.getEvents().getAll().size())); + + return Result.error("Couldn't settle event."); } } diff --git a/service/src/main/java/market/guess/service/TradingOperation.java b/service/src/main/java/market/guess/service/TradingOperation.java deleted file mode 100644 index 63572c0..0000000 --- a/service/src/main/java/market/guess/service/TradingOperation.java +++ /dev/null @@ -1,13 +0,0 @@ -package market.guess.service; - -import market.guess.service.domain.Event; -import market.guess.service.domain.Market; -import market.guess.service.domain.Settlement; -import market.guess.service.domain.Trade; -import market.guess.service.domain.User; - -public interface TradingOperation { - Trade buy(Event event, User user, Market Option, int quantity); - - Settlement settle(Event event, String winningOption); -} diff --git a/service/src/main/java/market/guess/service/TradingOperationImpl.java b/service/src/main/java/market/guess/service/TradingOperationImpl.java deleted file mode 100644 index 641a3a9..0000000 --- a/service/src/main/java/market/guess/service/TradingOperationImpl.java +++ /dev/null @@ -1,22 +0,0 @@ -package market.guess.service; - -import market.guess.service.domain.Event; -import market.guess.service.domain.Market; -import market.guess.service.domain.Settlement; -import market.guess.service.domain.Trade; -import market.guess.service.domain.User; - -public final class TradingOperationImpl implements TradingOperation { - - @Override - public Trade buy(Event event, User user, Market Option, int quantity) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'buy'"); - } - - @Override - public Settlement settle(Event event, String winningOption) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'settle'"); - } -} diff --git a/service/src/main/java/market/guess/service/catalog/LocalCatalogContext.java b/service/src/main/java/market/guess/service/catalog/LocalCatalogContext.java new file mode 100644 index 0000000..6035ce7 --- /dev/null +++ b/service/src/main/java/market/guess/service/catalog/LocalCatalogContext.java @@ -0,0 +1,64 @@ +package market.guess.service.catalog; + +import java.nio.file.Path; +import java.util.List; +import market.guess.api.CatalogContext; +import market.guess.api.LoadResultDTO; +import market.guess.api.Result; +import market.guess.model.event.EventDetailDTO; +import market.guess.model.event.EventSummaryDTO; +import market.guess.service.domain.Event; +import market.guess.service.infrastructure.MarketContext; +import market.guess.service.infrastructure.provider.Loader; + +public final class LocalCatalogContext implements CatalogContext { + private final MarketContext context; + private final Loader provider; + + public LocalCatalogContext(Loader provider, MarketContext context) { + super(); + this.provider = provider; + this.context = context; + } + + @Override + public Result loadEvents(Path path) { + return provider.load(path); + } + + @Override + public Result> getAllEvents() { + return Result.ok(context.getEvents().getAll().stream().map(Event::toEventSummary).toList()); + } + + @Override + public Result getEvent(String eventKey) { + var optional = context.getEvents().get(eventKey); + + if (optional.isEmpty()) return Result.error("Unable to find event."); + var event = optional.get(); + + return Result.ok( + new EventDetailDTO(event.toEventSummary(), event.toMarketState(), event.getHistory(), "")); + } + + @Override + public Result saveState(Path path) { + try { + context.save(); + } catch (Exception e) { + return Result.error("Failed to save.", e.getMessage()); + } + return Result.ok(); + } + + @Override + public Result restoreState(Path path) { + try { + context.load(); + } catch (Exception e) { + return Result.error("Failed to load.", e.getMessage()); + } + return Result.ok(new LoadResultDTO(path.toString(), context.getEvents().getAll().size())); + } +} 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 5c98908..943328a 100644 --- a/service/src/main/java/market/guess/service/domain/Event.java +++ b/service/src/main/java/market/guess/service/domain/Event.java @@ -5,10 +5,10 @@ import java.time.Instant; import java.util.ArrayList; import java.util.List; import market.guess.api.CommissionTiming; -import market.guess.api.EventStatus; -import market.guess.api.EventSummaryDTO; 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.service.ledger.Ledger; import market.guess.service.mechanism.TradingMechanism; @@ -78,16 +78,12 @@ public final class Event { return commissionTiming; } - public TradingMechanism getMechanism() { - return mechanism; - } - public List getMarkets() { return markets; } public List getOptionsNames() { - return markets.stream().map(Market::getKey).toList(); + return markets.stream().map(Market::getName).toList(); } public Ledger getLedger() { @@ -190,4 +186,16 @@ public final class Event { .findFirst() .orElseThrow(() -> new IllegalArgumentException("No such option.")); } + + public boolean canSettle(String userName) { + if (state != EventStatus.ACTIVE) return false; + if (marketMaker.equalsIgnoreCase(userName)) return true; + + return false; + } + + public BigDecimal getPayout() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'getPayout'"); + } } diff --git a/service/src/main/java/market/guess/service/domain/MarketAction.java b/service/src/main/java/market/guess/service/domain/MarketAction.java index c0aabd1..d1a0ad5 100644 --- a/service/src/main/java/market/guess/service/domain/MarketAction.java +++ b/service/src/main/java/market/guess/service/domain/MarketAction.java @@ -5,7 +5,7 @@ public record MarketAction( public static final String BUY = "BUY"; public static final String SELL = "SELL"; - public static final String CLOSE = "CLOSE"; + public static final String SETTLE = "SETTLE"; public static MarketAction buy( String at, String userName, String eventKey, String optionKey, long quantity) { @@ -17,8 +17,8 @@ public record MarketAction( return new MarketAction(SELL, at, userName, eventKey, optionKey, quantity); } - public static MarketAction close( + public static MarketAction settle( String at, String userName, String eventKey, String winningOptionKey) { - return new MarketAction(CLOSE, at, userName, eventKey, winningOptionKey, 0); + return new MarketAction(SETTLE, at, userName, eventKey, winningOptionKey, 0); } } diff --git a/service/src/main/java/market/guess/service/infrastructure/MarketContext.java b/service/src/main/java/market/guess/service/infrastructure/MarketContext.java index d9fe3f8..7113803 100644 --- a/service/src/main/java/market/guess/service/infrastructure/MarketContext.java +++ b/service/src/main/java/market/guess/service/infrastructure/MarketContext.java @@ -10,6 +10,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.time.Instant; import java.util.List; +import market.guess.exception.GuessMarketException; import market.guess.service.domain.Event; import market.guess.service.domain.User; import market.guess.service.infrastructure.adapter.InstantTypeAdapter; @@ -41,21 +42,19 @@ public final class MarketContext { return users; } - public void save() { + public void save() throws GuessMarketException { var path = Paths.get("test-file.json"); try { if (path.getParent() != null) Files.createDirectories(path.getParent()); Files.writeString( - path, - GSON.toJson(new Wrapper(events.getAll(), users.getAll())), - StandardCharsets.UTF_8); + path, GSON.toJson(new Wrapper(events.getAll(), users.getAll())), StandardCharsets.UTF_8); } catch (IOException e) { - + throw new GuessMarketException("Failed to sove the file."); } } - public void load() { + public void load() throws GuessMarketException { try { var wrapper = GSON.fromJson( @@ -71,9 +70,10 @@ public final class MarketContext { users.add(user); } - } catch (JsonSyntaxException | IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + } catch (JsonSyntaxException e) { + throw new GuessMarketException("The JSON file is malformed."); + } catch (IOException e) { + throw new GuessMarketException("Unable the file."); } } } diff --git a/service/src/main/java/market/guess/service/infrastructure/mapper/v1/EventMapperV1.java b/service/src/main/java/market/guess/service/infrastructure/mapper/v1/EventMapperV1.java index 7984c41..f049a0b 100644 --- a/service/src/main/java/market/guess/service/infrastructure/mapper/v1/EventMapperV1.java +++ b/service/src/main/java/market/guess/service/infrastructure/mapper/v1/EventMapperV1.java @@ -3,7 +3,7 @@ package market.guess.service.infrastructure.mapper.v1; import java.util.ArrayList; import java.util.List; import market.guess.api.CommissionTiming; -import market.guess.api.EventStatus; +import market.guess.model.event.EventStatus; import market.guess.service.domain.Event; import market.guess.service.domain.Market; import market.guess.service.infrastructure.mapper.Mapper; diff --git a/service/src/main/java/market/guess/service/infrastructure/mapper/v2/EventMapperV2.java b/service/src/main/java/market/guess/service/infrastructure/mapper/v2/EventMapperV2.java index 0dd8e43..7037c02 100644 --- a/service/src/main/java/market/guess/service/infrastructure/mapper/v2/EventMapperV2.java +++ b/service/src/main/java/market/guess/service/infrastructure/mapper/v2/EventMapperV2.java @@ -3,7 +3,7 @@ package market.guess.service.infrastructure.mapper.v2; import java.util.ArrayList; import java.util.List; import market.guess.api.CommissionTiming; -import market.guess.api.EventStatus; +import market.guess.model.event.EventStatus; import market.guess.service.domain.Event; import market.guess.service.domain.Market; import market.guess.service.infrastructure.mapper.Mapper; diff --git a/service/src/main/java/market/guess/service/infrastructure/provider/LoadValidator.java b/service/src/main/java/market/guess/service/infrastructure/provider/LoadValidator.java deleted file mode 100644 index c1c3895..0000000 --- a/service/src/main/java/market/guess/service/infrastructure/provider/LoadValidator.java +++ /dev/null @@ -1,22 +0,0 @@ -package market.guess.service.infrastructure.provider; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import market.guess.api.ProblemDTO; - -public final class LoadValidator { - private final List problems = new ArrayList<>(); - - public void append(String code, String humanizedMessage, String location) { - problems.add(new ProblemDTO(code, humanizedMessage, location)); - } - - public boolean isValid() { - return problems.isEmpty(); - } - - public List getIssues() { - return Collections.unmodifiableList(problems); - } -} diff --git a/service/src/main/java/market/guess/service/infrastructure/provider/Loader.java b/service/src/main/java/market/guess/service/infrastructure/provider/Loader.java index 7588855..fcca92f 100644 --- a/service/src/main/java/market/guess/service/infrastructure/provider/Loader.java +++ b/service/src/main/java/market/guess/service/infrastructure/provider/Loader.java @@ -2,7 +2,8 @@ package market.guess.service.infrastructure.provider; import java.nio.file.Path; import market.guess.api.LoadResultDTO; +import market.guess.api.Result; public interface Loader { - LoadResultDTO load(Path path); + Result load(Path path); } diff --git a/service/src/main/java/market/guess/service/infrastructure/provider/v1/XMLLoaderV1.java b/service/src/main/java/market/guess/service/infrastructure/provider/v1/XMLLoaderV1.java index 0d5bb9d..412a5d2 100644 --- a/service/src/main/java/market/guess/service/infrastructure/provider/v1/XMLLoaderV1.java +++ b/service/src/main/java/market/guess/service/infrastructure/provider/v1/XMLLoaderV1.java @@ -5,9 +5,9 @@ import jakarta.xml.bind.JAXBException; import java.nio.file.Files; import java.nio.file.Path; import market.guess.api.LoadResultDTO; +import market.guess.api.Result; import market.guess.service.domain.User; import market.guess.service.infrastructure.mapper.v1.EventMapperV1; -import market.guess.service.infrastructure.provider.LoadValidator; import market.guess.service.infrastructure.provider.Loader; import market.guess.service.infrastructure.repository.EventRepository; import market.guess.service.infrastructure.repository.UserRepository; @@ -34,11 +34,11 @@ public final class XMLLoaderV1 implements Loader { } @Override - public LoadResultDTO load(Path path) { - var validator = new LoadValidator(); + public Result load(Path path) { + if (path.getFileName() == null || !path.getFileName().toString().toLowerCase().endsWith(".xml")) { - validator.append("NOT_XML", "The file must be an XML.", path.toString()); + return Result.error("NOT_XML", "The file must be an XML."); } try (var stream = Files.newInputStream(path)) { @@ -51,11 +51,9 @@ public final class XMLLoaderV1 implements Loader { userRepository.add(new User("Tester", 9_999_999)); - return LoadResultDTO.success(path.toString(), eventRepository.getAll().size()); + return Result.ok(new LoadResultDTO(path.toString(), eventRepository.getAll().size())); } catch (Exception e) { - validator.append("UNREADABLE", "The file could not be read.", path.toString()); + return Result.error("UNREADABLE", "The file could not be read."); } - - return LoadResultDTO.failure(path.toString(), validator.getIssues()); } } 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 caecf55..12ec614 100644 --- a/service/src/main/java/market/guess/service/ledger/LedgerContext.java +++ b/service/src/main/java/market/guess/service/ledger/LedgerContext.java @@ -2,7 +2,7 @@ package market.guess.service.ledger; import java.math.BigDecimal; import java.time.Clock; -import market.guess.api.LedgerType; +import market.guess.model.ledger.LedgerType; import market.guess.service.infrastructure.repository.UserRepository; public final class LedgerContext { diff --git a/service/src/main/java/market/guess/service/ledger/LedgerEntry.java b/service/src/main/java/market/guess/service/ledger/LedgerEntry.java index 8008f20..79f12bf 100644 --- a/service/src/main/java/market/guess/service/ledger/LedgerEntry.java +++ b/service/src/main/java/market/guess/service/ledger/LedgerEntry.java @@ -2,7 +2,7 @@ package market.guess.service.ledger; import java.math.BigDecimal; import java.time.Instant; -import market.guess.api.LedgerType; +import market.guess.model.ledger.LedgerType; public final record LedgerEntry( int id, diff --git a/service/src/main/java/market/guess/service/matching/OrderMatchingContext.java b/service/src/main/java/market/guess/service/matching/OrderMatchingContext.java new file mode 100644 index 0000000..8882432 --- /dev/null +++ b/service/src/main/java/market/guess/service/matching/OrderMatchingContext.java @@ -0,0 +1,13 @@ +package market.guess.service.matching; + +import java.util.List; +import market.guess.service.domain.Event; +import market.guess.service.domain.Market; +import market.guess.service.domain.Trade; +import market.guess.service.domain.User; + +public interface OrderMatchingContext { + List submitOrder(User user, Event event, Market market, int quantity); + + boolean cancelOrder(); +} diff --git a/service/src/main/java/market/guess/service/mechanism/OrderBookTradingMechanism.java b/service/src/main/java/market/guess/service/mechanism/OrderBookTradingMechanism.java index 91d5110..43f4843 100644 --- a/service/src/main/java/market/guess/service/mechanism/OrderBookTradingMechanism.java +++ b/service/src/main/java/market/guess/service/mechanism/OrderBookTradingMechanism.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.List; import market.guess.api.MechanismType; import market.guess.service.domain.BigDecimalOptions; +import market.guess.service.domain.Trade; public final class OrderBookTradingMechanism implements TradingMechanism { public enum Side { @@ -58,12 +59,12 @@ public final class OrderBookTradingMechanism implements TradingMechanism { } @Override - public TradeExecution buy(int optionIndex, int quantity) { + public Trade buy(int optionIndex, int quantity) { return match(optionIndex, Side.BUY, quantity, null, false); } /** Priced limit order — not on the shared interface, no engine call site yet. */ - public TradeExecution placeOrder(int optionIndex, Side side, int quantity, BigDecimal price) { + public Trade placeOrder(int optionIndex, Side side, int quantity, BigDecimal price) { if (price.signum() < 0 || price.compareTo(BigDecimal.valueOf(d)) > 0) { throw new IllegalArgumentException("Price must be between 0 and " + d); } @@ -97,7 +98,7 @@ public final class OrderBookTradingMechanism implements TradingMechanism { return book.isEmpty() ? null : book.get(0).price; } - private TradeExecution match( + private Trade match( int optionIndex, Side side, int quantity, BigDecimal limitPrice, boolean restRemainder) { var opposing = side == Side.BUY ? asks.get(optionIndex) : bids.get(optionIndex); @@ -127,7 +128,8 @@ public final class OrderBookTradingMechanism implements TradingMechanism { new RestingOrder(limitPrice, quantity - filled)); } - return new TradeExecution(filled, BigDecimalOptions.toMoney(proceeds)); + return new Trade( + filled, null, null, null, null, filled, BigDecimalOptions.toMoney(proceeds), proceeds); } private static boolean crosses(Side side, BigDecimal restingPrice, BigDecimal limitPrice) { diff --git a/service/src/main/java/market/guess/service/mechanism/TradeExecution.java b/service/src/main/java/market/guess/service/mechanism/TradeExecution.java deleted file mode 100644 index 3d8df1d..0000000 --- a/service/src/main/java/market/guess/service/mechanism/TradeExecution.java +++ /dev/null @@ -1,5 +0,0 @@ -package market.guess.service.mechanism; - -import java.math.BigDecimal; - -public record TradeExecution(int filledQuantity, BigDecimal cost) {} 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 fc915fb..204a170 100644 --- a/service/src/main/java/market/guess/service/mechanism/TradingMechanism.java +++ b/service/src/main/java/market/guess/service/mechanism/TradingMechanism.java @@ -2,6 +2,7 @@ package market.guess.service.mechanism; import java.math.BigDecimal; import market.guess.api.MechanismType; +import market.guess.service.domain.Trade; public sealed interface TradingMechanism permits LmsrTradingMechanism, OrderBookTradingMechanism { @@ -9,7 +10,7 @@ public sealed interface TradingMechanism permits LmsrTradingMechanism, OrderBook BigDecimal openingCost(); - TradeExecution buy(int optionIndex, int quantity); + Trade buy(int optionIndex, int quantity); BigDecimal[] prices(); } diff --git a/service/src/test/java/market/guess/service/mechanism/LmsrTradingMechanismTest.java b/service/src/test/java/market/guess/service/mechanism/LmsrTradingMechanismTest.java deleted file mode 100644 index f5c6af5..0000000 --- a/service/src/test/java/market/guess/service/mechanism/LmsrTradingMechanismTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package market.guess.service.mechanism; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; - -import java.math.BigDecimal; -import org.junit.Test; - -public class LmsrTradingMechanismTest { - - @Test - public void openingCostMatchesLmsrFormula() { - var mechanism = new LmsrTradingMechanism(100, 2); - - assertEquals(new BigDecimal("69.31"), mechanism.openingCost()); - } - - @Test - public void pricesStartEven() { - var mechanism = new LmsrTradingMechanism(100, 2); - - assertArrayEquals( - new BigDecimal[] {new BigDecimal("0.50"), new BigDecimal("0.50")}, mechanism.prices()); - } - - @Test - public void buyingSharesShiftsCostAndPrice() { - var mechanism = new LmsrTradingMechanism(100, 2); - - var execution = mechanism.buy(0, 100); - - assertEquals(100, execution.filledQuantity()); - assertEquals(new BigDecimal("62.01"), execution.cost()); - assertArrayEquals( - new BigDecimal[] {new BigDecimal("0.73"), new BigDecimal("0.27")}, mechanism.prices()); - } -} diff --git a/service/src/test/java/market/guess/service/mechanism/OrderBookTradingMechanismTest.java b/service/src/test/java/market/guess/service/mechanism/OrderBookTradingMechanismTest.java deleted file mode 100644 index b4b2da3..0000000 --- a/service/src/test/java/market/guess/service/mechanism/OrderBookTradingMechanismTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package market.guess.service.mechanism; - -import static org.junit.Assert.assertEquals; - -import java.math.BigDecimal; -import market.guess.service.mechanism.OrderBookTradingMechanism.Side; -import org.junit.Test; - -public class OrderBookTradingMechanismTest { - - @Test - public void limitBuyConsumesRestingAsksInFifoOrderAndLeavesRemainder() { - var mechanism = new OrderBookTradingMechanism(false, 100, 1, 2); - var price = new BigDecimal("0.60"); - - mechanism.placeOrder(0, Side.SELL, 20, price); - mechanism.placeOrder(0, Side.SELL, 40, price); - - var execution = mechanism.placeOrder(0, Side.BUY, 50, price); - - assertEquals(50, execution.filledQuantity()); - assertEquals(new BigDecimal("30.00"), execution.cost()); - - var followUp = mechanism.buy(0, 100); - assertEquals(10, followUp.filledQuantity()); - } - - @Test - public void marketBuySweepsMultiplePriceLevelsAndPartiallyFillsWhenBookRunsOut() { - var mechanism = new OrderBookTradingMechanism(false, 100, 1, 2); - mechanism.placeOrder(0, Side.SELL, 10, new BigDecimal("0.50")); - mechanism.placeOrder(0, Side.SELL, 10, new BigDecimal("0.70")); - - var execution = mechanism.buy(0, 15); - - assertEquals(15, execution.filledQuantity()); - assertEquals(new BigDecimal("8.50"), execution.cost()); - - var exhausted = mechanism.buy(0, 100); - assertEquals(5, exhausted.filledQuantity()); - } - - @Test - public void pricesReflectMidpointOfBestBidAndAsk() { - var mechanism = new OrderBookTradingMechanism(false, 100, 1, 2); - mechanism.placeOrder(0, Side.BUY, 10, new BigDecimal("0.40")); - mechanism.placeOrder(0, Side.SELL, 10, new BigDecimal("0.60")); - - assertEquals(new BigDecimal("0.50"), mechanism.prices()[0]); - } -} diff --git a/ui-console/src/main/java/market/guess/ui/console/App.java b/ui-console/src/main/java/market/guess/ui/console/App.java index 330498c..a628a36 100644 --- a/ui-console/src/main/java/market/guess/ui/console/App.java +++ b/ui-console/src/main/java/market/guess/ui/console/App.java @@ -1,9 +1,9 @@ package market.guess.ui.console; +import market.guess.api.CatalogContext; import market.guess.api.GuessMarketContext; import market.guess.service.LocalGuessMarketContext; -import market.guess.service.TradingOperation; -import market.guess.service.TradingOperationImpl; +import market.guess.service.catalog.LocalCatalogContext; import market.guess.service.infrastructure.MarketContext; import market.guess.service.infrastructure.mapper.v1.EventMapperV1; import market.guess.service.infrastructure.provider.Loader; @@ -31,10 +31,10 @@ public class App { pico.addComponent(EventRepository.class, InMemoryEventRepository.class); pico.addComponent(UserRepository.class, InMemoryUserRepository.class); pico.addComponent(MarketContext.class); - pico.addComponent(TradingOperation.class, TradingOperationImpl.class); pico.addComponent(EventMapperV1.class); pico.addComponent(Loader.class, XMLLoaderV1.class); pico.addComponent(GuessMarketContext.class, LocalGuessMarketContext.class); + pico.addComponent(CatalogContext.class, LocalCatalogContext.class); pico.addComponent(LoadFileMenuCommand.class); pico.addComponent(ListEventsMenuCommand.class); diff --git a/ui-console/src/main/java/market/guess/ui/console/command/BuyMenuCommand.java b/ui-console/src/main/java/market/guess/ui/console/command/BuyMenuCommand.java index df40aa8..87da340 100644 --- a/ui-console/src/main/java/market/guess/ui/console/command/BuyMenuCommand.java +++ b/ui-console/src/main/java/market/guess/ui/console/command/BuyMenuCommand.java @@ -1,8 +1,8 @@ package market.guess.ui.console.command; import market.guess.api.CatalogContext; -import market.guess.api.EventStatus; import market.guess.api.GuessMarketContext; +import market.guess.model.event.EventStatus; import market.guess.ui.console.io.InputProcessor; public final class BuyMenuCommand implements MenuCommand { @@ -59,14 +59,16 @@ public final class BuyMenuCommand implements MenuCommand { io.readSelect( "Choose a market: ", event.state().markets(), - o -> "%s [value %d, %d shares helds]".formatted(o.name(), o.price(), o.volume())); + o -> "%s [value %s, %s shares helds]".formatted(o.name(), o.price(), o.volume())); var amount = io.readInt("How many shares? ", 1, Integer.MAX_VALUE); - var receipt = context.buyShares("", event.summary().key(), market.key(), amount); - if (receipt.isSuccess()) return; - + var buyResult = context.buyShares("Tester", event.summary().key(), market.key(), amount); + if (buyResult.isSuccess()) { + io.printReceipt(buyResult.getData()); + return; + } io.newLine(); - io.println("Purchase declined: %s".formatted(receipt.getMessage())); + io.println("Purchase declined: %s".formatted(buyResult.getMessage())); } } diff --git a/ui-console/src/main/java/market/guess/ui/console/command/EventDetailsMenuCommand.java b/ui-console/src/main/java/market/guess/ui/console/command/EventDetailsMenuCommand.java index ab60a1f..881acfa 100644 --- a/ui-console/src/main/java/market/guess/ui/console/command/EventDetailsMenuCommand.java +++ b/ui-console/src/main/java/market/guess/ui/console/command/EventDetailsMenuCommand.java @@ -1,6 +1,18 @@ package market.guess.ui.console.command; +import market.guess.api.CatalogContext; +import market.guess.model.event.EventStatus; +import market.guess.ui.console.io.InputProcessor; + public final class EventDetailsMenuCommand implements MenuCommand { + private final CatalogContext catalog; + private final InputProcessor io; + + public EventDetailsMenuCommand(CatalogContext catalog, InputProcessor io) { + super(); + this.catalog = catalog; + this.io = io; + } @Override public int getIndex() { @@ -14,7 +26,30 @@ public final class EventDetailsMenuCommand implements MenuCommand { @Override public void execute() { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'execute'"); + var result = catalog.getAllEvents(); + + if (!result.isSuccess()) { + io.println("Failed to list events"); + return; + } + + var events = result.getData(); + var active = events.stream().filter(event -> event.status() == EventStatus.ACTIVE).toList(); + + if (active.isEmpty()) { + io.println("There are currently no active events to trade on."); + return; + } + + var pick = io.readSelect("Pick an event: ", active, event -> "%s".formatted(event.name())); + + var result2 = catalog.getEvent(pick.key()); + var event = result2.getData(); + if (event == null) { + io.println("That event is no longer available."); + return; + } + + io.printState(event.state()); } } diff --git a/ui-console/src/main/java/market/guess/ui/console/command/ListEventsMenuCommand.java b/ui-console/src/main/java/market/guess/ui/console/command/ListEventsMenuCommand.java index a97ab9f..755bf37 100644 --- a/ui-console/src/main/java/market/guess/ui/console/command/ListEventsMenuCommand.java +++ b/ui-console/src/main/java/market/guess/ui/console/command/ListEventsMenuCommand.java @@ -29,6 +29,7 @@ public final class ListEventsMenuCommand implements MenuCommand { var result = catalog.getAllEvents(); if (!result.isSuccess()) { + io.println("Failed to list events due to %s.".formatted(result.getMessage())); return; } var events = result.getData(); diff --git a/ui-console/src/main/java/market/guess/ui/console/command/LoadFileMenuCommand.java b/ui-console/src/main/java/market/guess/ui/console/command/LoadFileMenuCommand.java index 065ac6e..46312e4 100644 --- a/ui-console/src/main/java/market/guess/ui/console/command/LoadFileMenuCommand.java +++ b/ui-console/src/main/java/market/guess/ui/console/command/LoadFileMenuCommand.java @@ -25,8 +25,8 @@ public final class LoadFileMenuCommand implements MenuCommand { @Override public void execute() { - // var path = io.readPath("Pleae enter the full file path to the XML file: "); - // var result = context.loadEvents(Path.of(path)); + // var path = io.readPath("Pleae enter the full file path to the XML file: "); + // var result = context.loadEvents(Path.of(path)); var result = catalog.loadEvents( Path.of("/home/hex-dev/projects/guess-market/test-data/ex-1/multiple.xml")); @@ -38,11 +38,6 @@ public final class LoadFileMenuCommand implements MenuCommand { return; } - io.println( - "The file was not loaded. %d problem(s) were found: " - .formatted(result.getData().issues().size())); - for (var issue : result.getData().issues()) { - io.println(" - %s: %s".formatted(issue.location(), issue.humanizedMessage())); - } + io.println("The file was not loaded, %s ".formatted(result.getMessage())); } } diff --git a/ui-console/src/main/java/market/guess/ui/console/command/LoadMarketStateMenuCommand.java b/ui-console/src/main/java/market/guess/ui/console/command/LoadMarketStateMenuCommand.java index defb402..26d00c1 100644 --- a/ui-console/src/main/java/market/guess/ui/console/command/LoadMarketStateMenuCommand.java +++ b/ui-console/src/main/java/market/guess/ui/console/command/LoadMarketStateMenuCommand.java @@ -1,16 +1,16 @@ package market.guess.ui.console.command; import java.nio.file.Path; -import market.guess.api.GuessMarketContext; +import market.guess.api.CatalogContext; import market.guess.ui.console.io.InputProcessor; public final class LoadMarketStateMenuCommand implements MenuCommand { - private final GuessMarketContext context; + private final CatalogContext catalog; private final InputProcessor io; - public LoadMarketStateMenuCommand(GuessMarketContext context, InputProcessor io) { + public LoadMarketStateMenuCommand(CatalogContext catalog, InputProcessor io) { super(); - this.context = context; + this.catalog = catalog; this.io = io; } @@ -27,7 +27,7 @@ public final class LoadMarketStateMenuCommand implements MenuCommand { @Override public void execute() { var path = io.readPath("Which state file to load? "); - var result = context.restoreState(Path.of(path)); + var result = catalog.restoreState(Path.of(path)); if (result.isSuccess()) { io.println("Restored %d event(s) from %s.".formatted(result.getData().eventsLoaded(), path)); diff --git a/ui-console/src/main/java/market/guess/ui/console/command/SaveMarketStateMenuCommand.java b/ui-console/src/main/java/market/guess/ui/console/command/SaveMarketStateMenuCommand.java index 27dfdde..77400d2 100644 --- a/ui-console/src/main/java/market/guess/ui/console/command/SaveMarketStateMenuCommand.java +++ b/ui-console/src/main/java/market/guess/ui/console/command/SaveMarketStateMenuCommand.java @@ -1,16 +1,16 @@ package market.guess.ui.console.command; import java.nio.file.Path; -import market.guess.api.GuessMarketContext; +import market.guess.api.CatalogContext; import market.guess.ui.console.io.InputProcessor; public final class SaveMarketStateMenuCommand implements MenuCommand { - private final GuessMarketContext context; + private final CatalogContext catalog; private final InputProcessor io; - public SaveMarketStateMenuCommand(GuessMarketContext context, InputProcessor io) { + public SaveMarketStateMenuCommand(CatalogContext catalog, InputProcessor io) { super(); - this.context = context; + this.catalog = catalog; this.io = io; } @@ -29,7 +29,7 @@ public final class SaveMarketStateMenuCommand implements MenuCommand { var path = io.readPath("Save to: "); try { - context.saveState(Path.of(path)); + catalog.saveState(Path.of(path)); io.println("Saved current state to %s.".formatted(path)); } catch (Exception e) { io.println("Failed to save: %s".formatted(e.getMessage())); diff --git a/ui-console/src/main/java/market/guess/ui/console/io/InputProcessor.java b/ui-console/src/main/java/market/guess/ui/console/io/InputProcessor.java index 912ca25..d644a9b 100644 --- a/ui-console/src/main/java/market/guess/ui/console/io/InputProcessor.java +++ b/ui-console/src/main/java/market/guess/ui/console/io/InputProcessor.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Scanner; import java.util.function.Function; import market.guess.api.MarketStateDTO; +import market.guess.api.PurchaseReceiptDTO; public final class InputProcessor { private final Scanner scanner = new Scanner(System.in); @@ -32,6 +33,8 @@ public final class InputProcessor { .formatted( padAround("Option", 18), space(10), padLeft("Value", 8), padRight("Shares", 8))); + println("-".repeat(46)); + for (var market : state.markets()) { println( "%s|%s%s|%s" @@ -43,8 +46,8 @@ public final class InputProcessor { } newLine(); - print("Event ledger balance: ".formatted(state.accountBalance())); - print("Commission collected: ".formatted(state.commissionCollected())); + println("Event ledger balance: ".formatted(state.accountBalance())); + println("Commission collected: ".formatted(state.commissionCollected())); } private static String space(int width) { @@ -136,4 +139,8 @@ public final class InputProcessor { private String nextLine() { return scanner.hasNextLine() ? scanner.nextLine() : null; } + + public void printReceipt(PurchaseReceiptDTO receipt) { + println(receipt); + } } -- cgit v1.2.3