From 13a221ffaefc169d028c7eab6f21ac88052d1d40 Mon Sep 17 00:00:00 2001 From: Kostya Date: Mon, 17 Aug 2026 11:15:59 +0000 Subject: Replace order matching with risk/matching/fulfillment/settlement pipeline Move XML loading infrastructure and models under service/catalog, and split order execution into a real pipeline: RiskEngine checks the order, MatchingEngine matches it, FulfillmentContext books the resulting trades, and SettlementContext resolves winning markets. GuessMarketContext.buyShares becomes placeOrder(price, quantity), and TradingMechanism.buy now returns matched trades directly instead of a single TradeExecution. --- .../src/main/java/market/guess/ui/console/App.java | 33 +++++++--- .../guess/ui/console/command/BuyMenuCommand.java | 5 +- .../console/command/EventDetailsMenuCommand.java | 1 + .../ui/console/command/LoadFileMenuCommand.java | 7 +- .../ui/console/command/SettleEventMenuCommand.java | 46 ++++++++++++- .../market/guess/ui/console/io/InputProcessor.java | 77 +++++++++++++++------- 6 files changed, 130 insertions(+), 39 deletions(-) (limited to 'ui-console/src/main/java') 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 a628a36..5ee5aaf 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,17 +1,27 @@ package market.guess.ui.console; +import java.time.Clock; import market.guess.api.CatalogContext; import market.guess.api.GuessMarketContext; import market.guess.service.LocalGuessMarketContext; 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; -import market.guess.service.infrastructure.provider.v1.XMLLoaderV1; -import market.guess.service.infrastructure.repository.EventRepository; -import market.guess.service.infrastructure.repository.InMemoryEventRepository; -import market.guess.service.infrastructure.repository.InMemoryUserRepository; -import market.guess.service.infrastructure.repository.UserRepository; +import market.guess.service.catalog.infrastructure.MarketContext; +import market.guess.service.catalog.infrastructure.mapper.v1.EventMapperV1; +import market.guess.service.catalog.infrastructure.provider.Loader; +import market.guess.service.catalog.infrastructure.provider.v1.XMLLoaderV1; +import market.guess.service.catalog.infrastructure.repository.EventRepository; +import market.guess.service.catalog.infrastructure.repository.InMemoryEventRepository; +import market.guess.service.catalog.infrastructure.repository.InMemoryUserRepository; +import market.guess.service.catalog.infrastructure.repository.UserRepository; +import market.guess.service.fulfillment.FulfillmentContext; +import market.guess.service.fulfillment.LocalFulfillmentContext; +import market.guess.service.ledger.LedgerContext; +import market.guess.service.matching.LocalMatchingEngine; +import market.guess.service.matching.MatchingEngine; +import market.guess.service.risk.LocalRiskEngine; +import market.guess.service.risk.RiskEngine; +import market.guess.service.settlement.LocalSettlementContext; +import market.guess.service.settlement.SettlementContext; import market.guess.ui.console.command.BuyMenuCommand; import market.guess.ui.console.command.EventDetailsMenuCommand; import market.guess.ui.console.command.ExitMenuCommand; @@ -35,6 +45,13 @@ public class App { pico.addComponent(Loader.class, XMLLoaderV1.class); pico.addComponent(GuessMarketContext.class, LocalGuessMarketContext.class); pico.addComponent(CatalogContext.class, LocalCatalogContext.class); + pico.addComponent(LedgerContext.class); + pico.addComponent(RiskEngine.class, LocalRiskEngine.class); + pico.addComponent(MatchingEngine.class, LocalMatchingEngine.class); + pico.addComponent(FulfillmentContext.class, LocalFulfillmentContext.class); + pico.addComponent(SettlementContext.class, LocalSettlementContext.class); + + pico.addComponent(Clock.class, Clock.systemUTC()); 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 87da340..bee2351 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,5 +1,6 @@ package market.guess.ui.console.command; +import java.math.BigDecimal; import market.guess.api.CatalogContext; import market.guess.api.GuessMarketContext; import market.guess.model.event.EventStatus; @@ -63,7 +64,9 @@ public final class BuyMenuCommand implements MenuCommand { var amount = io.readInt("How many shares? ", 1, Integer.MAX_VALUE); - var buyResult = context.buyShares("Tester", event.summary().key(), market.key(), amount); + var buyResult = + context.placeOrder( + "Tester", event.summary().key(), market.key(), new BigDecimal(market.price()), amount); if (buyResult.isSuccess()) { io.printReceipt(buyResult.getData()); return; 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 881acfa..c9e7585 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 @@ -51,5 +51,6 @@ public final class EventDetailsMenuCommand implements MenuCommand { } io.printState(event.state()); + io.printHistory(event.history()); } } 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 46312e4..024f1f7 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,11 +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 result = - catalog.loadEvents( - Path.of("/home/hex-dev/projects/guess-market/test-data/ex-1/multiple.xml")); + var path = io.readPath("Pleae enter the full file path to the XML file: "); + var result = catalog.loadEvents(Path.of(path)); if (result.isSuccess()) { io.println( diff --git a/ui-console/src/main/java/market/guess/ui/console/command/SettleEventMenuCommand.java b/ui-console/src/main/java/market/guess/ui/console/command/SettleEventMenuCommand.java index b7344e3..8cc7b02 100644 --- a/ui-console/src/main/java/market/guess/ui/console/command/SettleEventMenuCommand.java +++ b/ui-console/src/main/java/market/guess/ui/console/command/SettleEventMenuCommand.java @@ -1,14 +1,19 @@ package market.guess.ui.console.command; +import market.guess.api.CatalogContext; import market.guess.api.GuessMarketContext; +import market.guess.model.event.EventStatus; import market.guess.ui.console.io.InputProcessor; public final class SettleEventMenuCommand implements MenuCommand { private final GuessMarketContext context; + private final CatalogContext catalog; private final InputProcessor io; - public SettleEventMenuCommand(GuessMarketContext context, InputProcessor io) { + public SettleEventMenuCommand( + GuessMarketContext context, CatalogContext catalog, InputProcessor io) { this.context = context; + this.catalog = catalog; this.io = io; } @@ -24,7 +29,42 @@ public final class SettleEventMenuCommand 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 settle."); + 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()); + + var winner = + io.readSelect("Which option won? ", event.state().markets(), o -> "%s".formatted(o.name())); + + var settleResult = context.settleEvent("Tester", event.summary().key(), winner.key()); + if (settleResult.isSuccess()) { + io.newLine(); + io.println("Settled. Winning option: %s".formatted(settleResult.getData().winningOption())); + return; + } + io.newLine(); + io.println("Settlement failed: %s".formatted(settleResult.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 d644a9b..7587768 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 @@ -5,8 +5,16 @@ import java.util.Scanner; import java.util.function.Function; import market.guess.api.MarketStateDTO; import market.guess.api.PurchaseReceiptDTO; +import market.guess.api.TradeRowDTO; public final class InputProcessor { + private static final int OPTION_WIDTH = 18; + private static final int VALUE_WIDTH = 10; + private static final int SHARES_WIDTH = 8; + private static final int QTY_WIDTH = 6; + private static final int PAID_WIDTH = 8; + private static final int AT_WIDTH = 20; + private final Scanner scanner = new Scanner(System.in); public void print(Object object) { @@ -28,34 +36,24 @@ public final class InputProcessor { public void printState(MarketStateDTO state) { newLine(); - println( - "%s|%s%s|%s" - .formatted( - padAround("Option", 18), space(10), padLeft("Value", 8), padRight("Shares", 8))); - - println("-".repeat(46)); + println(marketRow("Option", "Value", "Shares")); + println("-".repeat(OPTION_WIDTH + VALUE_WIDTH + SHARES_WIDTH + 2)); for (var market : state.markets()) { - println( - "%s|%s%s|%s" - .formatted( - padAround(market.name(), 18), - space(10), - padLeft(market.price(), 8), - padRight(market.volume(), 8))); + println(marketRow(market.name(), market.price(), market.volume())); } newLine(); - println("Event ledger balance: ".formatted(state.accountBalance())); - println("Commission collected: ".formatted(state.commissionCollected())); + println("Event ledger balance: %s".formatted(state.accountBalance())); + println("Commission collected: %s".formatted(state.commissionCollected())); } - private static String space(int width) { - return " ".repeat(width); - } - - private static String padAround(String input, int width) { - return input.length() >= width ? input : padRight(padLeft(input, width / 2), width / 2); + private static String marketRow(String option, String value, String shares) { + return "%s|%s|%s" + .formatted( + padRight(option, OPTION_WIDTH), + padLeft(value, VALUE_WIDTH), + padLeft(shares, SHARES_WIDTH)); } private static String padRight(String input, int width) { @@ -141,6 +139,41 @@ public final class InputProcessor { } public void printReceipt(PurchaseReceiptDTO receipt) { - println(receipt); + newLine(); + println("Purchase confirmed:"); + println(tradeRow("Option", "Qty", "Paid", "At")); + println("-".repeat(OPTION_WIDTH + QTY_WIDTH + PAID_WIDTH + AT_WIDTH + 3)); + + for (var trade : receipt.trades()) { + println(tradeRow(trade.optionName(), trade.quantity(), trade.pricePaid(), trade.at())); + } + + printState(receipt.stateAfter()); + } + + public void printHistory(List history) { + newLine(); + println("Trade history (newest first):"); + + if (history.isEmpty()) { + println("No trades yet."); + return; + } + + println(tradeRow("Option", "Qty", "Paid", "At")); + println("-".repeat(OPTION_WIDTH + QTY_WIDTH + PAID_WIDTH + AT_WIDTH + 3)); + + for (var trade : history) { + println(tradeRow(trade.optionName(), trade.quantity(), trade.pricePaid(), trade.at())); + } + } + + private static String tradeRow(String option, String qty, String paid, String at) { + return "%s|%s|%s|%s" + .formatted( + padRight(option, OPTION_WIDTH), + padLeft(qty, QTY_WIDTH), + padLeft(paid, PAID_WIDTH), + padRight(at, AT_WIDTH)); } } -- cgit v1.2.3