From 755d2265054e5140e82d322681566c3034583c95 Mon Sep 17 00:00:00 2001 From: Kostya Date: Tue, 18 Aug 2026 18:19:50 +0300 Subject: Break down purchase receipts by cost/commission, harden XML validation and state I/O Adds a full pre-load validation pass for GM XML seeds (ids, names, options, commission, LMSR liquidity), replaces IO.print with System.out, and surfaces detailed error messages across load/save commands instead of generic failures. --- .../console/command/EventDetailsMenuCommand.java | 31 ++++++++++++++++++---- .../ui/console/command/ListEventsMenuCommand.java | 19 ++++++++----- .../ui/console/command/LoadFileMenuCommand.java | 10 ++++--- .../command/LoadMarketStateMenuCommand.java | 10 +++++-- .../ui/console/command/PlaceOrderMenuCommand.java | 17 ++++++++---- .../command/SaveMarketStateMenuCommand.java | 16 ++++++----- .../ui/console/command/SettleEventMenuCommand.java | 20 +++++++++++--- .../market/guess/ui/console/io/InputProcessor.java | 31 +++++++++++----------- 8 files changed, 107 insertions(+), 47 deletions(-) (limited to 'ui-console/src/main/java/market') 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 2ad7a53..bdd3eb1 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 @@ -29,7 +29,7 @@ public final class EventDetailsMenuCommand implements MenuCommand { var result = catalog.getAllEvents(); if (!result.isSuccess()) { - io.println("Failed to list events"); + io.println("Failed to list events: %s.".formatted(result.getMessage())); return; } @@ -37,11 +37,17 @@ public final class EventDetailsMenuCommand implements MenuCommand { var viewable = events.stream().filter(event -> event.status() != EventStatus.DRAFT).toList(); if (viewable.isEmpty()) { - io.println("There are currently no events to show."); + io.println("No events are currently loaded. Please load a valid XML file first."); return; } - var pick = io.readSelect("Pick an event: ", viewable, event -> "%s".formatted(event.name())); + io.newLine(); + io.println("Available events:"); + var pick = + io.readSelect( + "Pick an event: ", + viewable, + event -> "[ID: %d] %s (%s)".formatted(event.displayId(), event.name(), event.status())); var result2 = catalog.getEvent(pick.key()); var event = result2.getData(); @@ -50,11 +56,26 @@ public final class EventDetailsMenuCommand implements MenuCommand { return; } + io.newLine(); + io.println("=== Event Details ==="); + io.println("Event ID: %d".formatted(event.summary().displayId())); + io.println("Name: %s".formatted(event.summary().name())); + io.println("Description: %s".formatted(event.summary().description())); + io.println( + "Commission: %d%% (%s)" + .formatted( + event.summary().commissionPercent(), + event.summary().commissionTiming() == market.guess.api.CommissionTiming.ON_CLOSE + ? "on-close" + : "on-purchase")); + io.println("Status: %s".formatted(event.summary().status())); + io.printState(event.state()); io.printHistory(event.history()); - if (!event.settledMarket().isBlank()) { - io.println("Winning market: %s".formatted(event.settledMarket())); + if (event.settledMarket() != null && !event.settledMarket().isBlank()) { + io.newLine(); + io.println("Winning option: %s".formatted(event.settledMarket())); } } } 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 755bf37..01e93bd 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,24 +29,29 @@ 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())); + io.println("Failed to list events: %s.".formatted(result.getMessage())); return; } var events = result.getData(); + if (events.isEmpty()) { + io.println("No events are currently loaded. Please load a valid XML file first."); + return; + } + for (var event : events) { io.newLine(); - io.println(" %s".formatted(event.name())); - io.println(" %s".formatted(event.description())); + io.println(" [%d] %s (ID: %d)".formatted(event.displayId(), event.name(), event.displayId())); + io.println(" Description: %s".formatted(event.description())); io.println( - " Commission: %d%% (%s)" + " Commission: %d%% (%s)" .formatted(event.commissionPercent(), timing(event.commissionTiming()))); - io.println(" Options: " + String.join(" | ", event.optionNames())); - io.println(" Status: " + event.status()); + io.println(" Options: " + String.join(" | ", event.optionNames())); + io.println(" Status: " + event.status()); } } private static String timing(CommissionTiming timing) { - return timing == CommissionTiming.ON_CLOSE ? "charged on close" : "charged on purchase"; + return timing == CommissionTiming.ON_CLOSE ? "on-close" : "on-purchase"; } } 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 c5b3c77..c36df90 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 @@ -24,16 +24,20 @@ 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 path = io.readPath("Please enter the full file path to the XML file: "); var result = catalog.loadEvents(path); if (result.isSuccess()) { io.println( - "Loaded %d event(s) from %s." + "Successfully loaded %d event(s) from %s." .formatted(result.getData().eventsLoaded(), result.getData().source())); return; } - io.println("The file was not loaded, %s ".formatted(result.getDetails())); + var errorMsg = + result.getDetails() != null && !result.getDetails().isBlank() + ? result.getDetails() + : result.getMessage(); + io.println("The file was not loaded: %s".formatted(errorMsg)); } } 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 30acef3..8fa77c2 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 @@ -25,11 +25,17 @@ public final class LoadMarketStateMenuCommand implements MenuCommand { @Override public void execute() { - var path = io.readPath("Which state file to load? "); + var path = io.readPath("Please enter full path to state file (without or with .json extension): "); var result = catalog.restoreState(path); if (result.isSuccess()) { - io.println("Restored %d event(s) from %s.".formatted(result.getData().eventsLoaded(), path)); + io.println("Successfully restored %d event(s) from %s.".formatted(result.getData().eventsLoaded(), path)); + } else { + var msg = + result.getDetails() != null && !result.getDetails().isBlank() + ? result.getDetails() + : result.getMessage(); + io.println("Failed to restore state: %s".formatted(msg)); } } } diff --git a/ui-console/src/main/java/market/guess/ui/console/command/PlaceOrderMenuCommand.java b/ui-console/src/main/java/market/guess/ui/console/command/PlaceOrderMenuCommand.java index a03f757..0d58b1d 100644 --- a/ui-console/src/main/java/market/guess/ui/console/command/PlaceOrderMenuCommand.java +++ b/ui-console/src/main/java/market/guess/ui/console/command/PlaceOrderMenuCommand.java @@ -34,11 +34,16 @@ public final class PlaceOrderMenuCommand implements MenuCommand { var result = catalog.getAllEvents(); if (!result.isSuccess()) { - io.println("Failed to list events"); + io.println("Failed to list events: %s.".formatted(result.getMessage())); return; } var events = result.getData(); + if (events.isEmpty()) { + io.println("No events are currently loaded. Please load a valid XML file first."); + return; + } + var active = events.stream().filter(event -> event.status() == EventStatus.ACTIVE).toList(); if (active.isEmpty()) { @@ -46,7 +51,9 @@ public final class PlaceOrderMenuCommand implements MenuCommand { return; } - var pick = io.readSelect("Pick an event: ", active, event -> "%s".formatted(event.name())); + io.newLine(); + io.println("Active events:"); + var pick = io.readSelect("Pick an active event: ", active, event -> "[ID: %d] %s".formatted(event.displayId(), event.name())); var result2 = catalog.getEvent(pick.key()); var event = result2.getData(); @@ -59,11 +66,11 @@ public final class PlaceOrderMenuCommand implements MenuCommand { var market = io.readSelect( - "Choose a market: ", + "Choose an option to buy: ", event.state().markets(), - o -> "%s [value %s, %s shares helds]".formatted(o.name(), o.price(), o.volume())); + o -> "%s [current value: %s, %s shares purchased]".formatted(o.name(), o.price(), o.volume())); - var amount = io.readInt("How many shares? ", 1, Integer.MAX_VALUE); + var amount = io.readInt("How many shares would you like to buy? ", 1, Integer.MAX_VALUE); var buyResult = context.placeOrder( 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 3c5d6b9..78a5ca0 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 @@ -25,13 +25,17 @@ public final class SaveMarketStateMenuCommand implements MenuCommand { @Override public void execute() { - var path = io.readPath("Save to: "); + var path = io.readPath("Please enter full path to save file (without or with .json extension): "); - try { - catalog.saveState(path); - io.println("Saved current state to %s.".formatted(path)); - } catch (Exception e) { - io.println("Failed to save: %s".formatted(e.getMessage())); + var result = catalog.saveState(path); + if (result.isSuccess()) { + io.println("Successfully saved current market state to %s.".formatted(path)); + } else { + var msg = + result.getDetails() != null && !result.getDetails().isBlank() + ? result.getDetails() + : result.getMessage(); + io.println("Failed to save state: %s".formatted(msg)); } } } 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 3457e08..bec25a7 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 @@ -32,11 +32,16 @@ public final class SettleEventMenuCommand implements MenuCommand { var result = catalog.getAllEvents(); if (!result.isSuccess()) { - io.println("Failed to list events"); + io.println("Failed to list events: %s.".formatted(result.getMessage())); return; } var events = result.getData(); + if (events.isEmpty()) { + io.println("No events are currently loaded. Please load a valid XML file first."); + return; + } + var active = events.stream().filter(event -> event.status() == EventStatus.ACTIVE).toList(); if (active.isEmpty()) { @@ -44,7 +49,9 @@ public final class SettleEventMenuCommand implements MenuCommand { return; } - var pick = io.readSelect("Pick an event: ", active, event -> "%s".formatted(event.name())); + io.newLine(); + io.println("Active events to settle:"); + var pick = io.readSelect("Pick an event to settle: ", active, event -> "[ID: %d] %s".formatted(event.displayId(), event.name())); var result2 = catalog.getEvent(pick.key()); var event = result2.getData(); @@ -56,12 +63,17 @@ public final class SettleEventMenuCommand implements MenuCommand { io.printState(event.state()); var winner = - io.readSelect("Which market won? ", event.state().markets(), o -> "%s".formatted(o.name())); + 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()) { + var settledEvent = settleResult.getData(); + io.newLine(); + io.println("Event closed successfully! Winning option: %s".formatted(settledEvent.settledMarket())); + io.printState(settledEvent.state()); + io.printHistory(settledEvent.history()); io.newLine(); - io.println("Settled. Winning market: %s".formatted(settleResult.getData().settledMarket())); + io.println("Winning option: %s".formatted(settledEvent.settledMarket())); return; } io.newLine(); 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 721cf8b..3ff752c 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 @@ -20,20 +20,20 @@ public final class InputProcessor { private final Scanner scanner = new Scanner(System.in); public void print(Object object) { - IO.print(object); + System.out.print(object); } public void println(Object object) { - IO.println(object); + System.out.println(object); } public void newLine() { - IO.println(""); + System.out.println(""); } public void splashScreen() { newLine(); - IO.println(padLeft("<<- GUESS MARKET ->>", 36)); + System.out.println(padLeft("<<- GUESS MARKET ->>", 36)); } public void printState(MarketStateDTO state) { @@ -68,7 +68,8 @@ public final class InputProcessor { public String readLine(String prompt) { print(prompt); - return nextLine().trim(); + var line = nextLine(); + return line != null ? line.trim() : ""; } public int readInt(String prompt, int min, int max) { @@ -80,7 +81,6 @@ public final class InputProcessor { } try { var value = Integer.parseInt(raw); - if (value > max || value < min) { println( "%d is out of range, please enter a number between %d and %d." @@ -90,9 +90,8 @@ public final class InputProcessor { return value; } catch (NumberFormatException e) { println( - "%s in not a number, please enter a number between %d and %d." + "'%s' is not a valid number, please enter a number between %d and %d." .formatted(raw, min, max)); - continue; } } } @@ -100,12 +99,10 @@ public final class InputProcessor { public Path readPath(String prompt) { while (true) { var raw = readLine(prompt); - if (raw.isBlank()) { println("Please enter a file path."); continue; } - try { return Path.of(unquote(raw)); } catch (InvalidPathException e) { @@ -126,7 +123,6 @@ public final class InputProcessor { public boolean readYesNo(String prompt) { while (true) { var raw = readLine("%s [Y/N]: ".formatted(prompt)).toLowerCase(); - switch (raw) { case "y", "yes" -> { return true; @@ -134,14 +130,14 @@ public final class InputProcessor { case "n", "no" -> { return false; } - default -> IO.println("Please answer y/yes or n/no."); + default -> println("Please answer y/yes or n/no."); } } } public T readSelect(String prompt, List items, Function function) { for (var i = 0; i < items.size(); i++) { - IO.println(" [%d]->> %s".formatted(i + 1, function.apply(items.get(i)))); + println(" [%d]->> %s".formatted(i + 1, function.apply(items.get(i)))); } var pick = readInt(prompt, 1, items.size()); @@ -156,12 +152,16 @@ public final class InputProcessor { public void printReceipt(PurchaseReceiptDTO receipt) { newLine(); println("Purchase confirmed:"); - println(tradeRow("Option", "Qty", "Paid", "At")); + println(tradeRow("Option", "Qty", "Cost", "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())); } + newLine(); + println("Shares cost: %s".formatted(receipt.sharesCost())); + println("Commission paid: %s".formatted(receipt.commissionPaid())); + println("Total amount paid: %s".formatted(receipt.totalPaid())); printState(receipt.stateAfter()); } @@ -175,7 +175,7 @@ public final class InputProcessor { return; } - println(tradeRow("Option", "Qty", "Paid", "At")); + println(tradeRow("Option", "Qty", "Cost", "At")); println("-".repeat(OPTION_WIDTH + QTY_WIDTH + PAID_WIDTH + AT_WIDTH + 3)); for (var trade : history) { @@ -192,3 +192,4 @@ public final class InputProcessor { padRight(at, AT_WIDTH)); } } + -- cgit v1.2.3