From 0247f3f5068b96df52a4f9424578d4c66ca6975c Mon Sep 17 00:00:00 2001 From: Kostya Date: Sat, 15 Aug 2026 16:25:40 +0000 Subject: Implement LMSR and order-book trading logic, wire MarketContext facade and new menu commands Fill in the previously stubbed cost/pricing math for both trading mechanisms, and give OrderBookTradingMechanism an actual matching engine with resting bid/ask books. Fold EventRepository/UserRepository access into a single MarketContext used by LocalGuessMarketContext, and move the repository package under infrastructure. Add Buy/EventDetails/LoadState/ SaveState/SettleEvent console commands and wire them into App/Menu. --- .../src/main/java/market/guess/ui/console/App.java | 21 ++++-- .../main/java/market/guess/ui/console/Menu.java | 20 +++--- .../guess/ui/console/command/BuyMenuCommand.java | 74 ++++++++++++++++++++++ .../console/command/EventDetailsMenuCommand.java | 26 ++++++++ .../command/LoadMarketStateMenuCommand.java | 26 ++++++++ .../command/SaveMarketStateMenuCommand.java | 44 +++++++++++++ .../ui/console/command/SettleEventMenuCommand.java | 36 +++++++++++ .../market/guess/ui/console/io/InputProcessor.java | 14 ++-- 8 files changed, 242 insertions(+), 19 deletions(-) create mode 100644 ui-console/src/main/java/market/guess/ui/console/command/BuyMenuCommand.java create mode 100644 ui-console/src/main/java/market/guess/ui/console/command/EventDetailsMenuCommand.java create mode 100644 ui-console/src/main/java/market/guess/ui/console/command/LoadMarketStateMenuCommand.java create mode 100644 ui-console/src/main/java/market/guess/ui/console/command/SaveMarketStateMenuCommand.java create mode 100644 ui-console/src/main/java/market/guess/ui/console/command/SettleEventMenuCommand.java (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 3d42e9b..330498c 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 @@ -4,16 +4,22 @@ import market.guess.api.GuessMarketContext; import market.guess.service.LocalGuessMarketContext; import market.guess.service.TradingOperation; import market.guess.service.TradingOperationImpl; +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.repository.EventRepository; -import market.guess.service.repository.InMemoryEventRepository; -import market.guess.service.repository.InMemoryUserRepository; -import market.guess.service.repository.UserRepository; +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.ui.console.command.BuyMenuCommand; +import market.guess.ui.console.command.EventDetailsMenuCommand; import market.guess.ui.console.command.ExitMenuCommand; import market.guess.ui.console.command.ListEventsMenuCommand; import market.guess.ui.console.command.LoadFileMenuCommand; +import market.guess.ui.console.command.LoadMarketStateMenuCommand; +import market.guess.ui.console.command.SaveMarketStateMenuCommand; +import market.guess.ui.console.command.SettleEventMenuCommand; import market.guess.ui.console.io.InputProcessor; import org.picocontainer.DefaultPicoContainer; import org.picocontainer.behaviors.Caching; @@ -24,7 +30,7 @@ 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); @@ -33,6 +39,11 @@ public class App { pico.addComponent(LoadFileMenuCommand.class); pico.addComponent(ListEventsMenuCommand.class); pico.addComponent(ExitMenuCommand.class); + pico.addComponent(BuyMenuCommand.class); + pico.addComponent(SettleEventMenuCommand.class); + pico.addComponent(EventDetailsMenuCommand.class); + pico.addComponent(LoadMarketStateMenuCommand.class); + pico.addComponent(SaveMarketStateMenuCommand.class); pico.addComponent(InputProcessor.class); pico.addComponent(Menu.class); diff --git a/ui-console/src/main/java/market/guess/ui/console/Menu.java b/ui-console/src/main/java/market/guess/ui/console/Menu.java index 18b72b7..90ddd38 100644 --- a/ui-console/src/main/java/market/guess/ui/console/Menu.java +++ b/ui-console/src/main/java/market/guess/ui/console/Menu.java @@ -1,5 +1,6 @@ package market.guess.ui.console; +import java.util.Comparator; import java.util.List; import market.guess.ui.console.command.MenuCommand; import market.guess.ui.console.io.InputProcessor; @@ -10,7 +11,8 @@ public final class Menu implements Startable { private final List commands; public Menu(List commands, InputProcessor io) { - this.commands = commands; + this.commands = + commands.stream().sorted(Comparator.comparingInt(MenuCommand::getIndex)).toList(); this.io = io; } @@ -19,8 +21,11 @@ public final class Menu implements Startable { var max = commands.stream().mapToInt(MenuCommand::getIndex).max().orElse(0); io.splashScreen(); while (true) { - printMenu(); + io.newLine(); + for (var command : commands) { + io.println(" [%d]->> %s".formatted(command.getIndex(), command.getName())); + } var selection = io.readInt("Choose a command: ", 1, max); var commandOptional = commands.stream().filter(c -> c.getIndex() == selection).findFirst(); @@ -32,7 +37,9 @@ public final class Menu implements Startable { command.execute(); - if (command.isExit()) return; + if (command.isExit()) { + break; + } } } @@ -40,11 +47,4 @@ public final class Menu implements Startable { public void stop() { // Do nothing. } - - private void printMenu() { - io.newLine(); - for (var command : commands) { - io.println(" [%d]->> %s".formatted(command.getIndex(), command.getName())); - } - } } 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 new file mode 100644 index 0000000..b8e2523 --- /dev/null +++ b/ui-console/src/main/java/market/guess/ui/console/command/BuyMenuCommand.java @@ -0,0 +1,74 @@ +package market.guess.ui.console.command; + +import market.guess.api.EventStatus; +import market.guess.api.GuessMarketContext; +import market.guess.exception.GuessMarketException; +import market.guess.ui.console.io.InputProcessor; + +public final class BuyMenuCommand implements MenuCommand { + private final GuessMarketContext context; + private final InputProcessor io; + + public BuyMenuCommand(GuessMarketContext context, InputProcessor io) { + super(); + this.context = context; + this.io = io; + } + + @Override + public int getIndex() { + return 4; + } + + @Override + public String getName() { + return "Buy share(s) in an event"; + } + + @Override + public boolean allowed() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'allowed'"); + } + + @Override + public void execute() { + var active = + context.listEvents().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 -> "[%d] %s".formatted(event.displayId(), event.name())); + + var event = context.getEvent(pick.eventKey()); + + if (event == null) { + io.println("That event is no longer available."); + return; + } + + // TODO: print state + + var option = + io.readSelect( + "Choose an option: ", event.state().options(), o -> "%s [value %d, %d shares helds]"); + + var amount = io.readInt("How many shares? ", 1, Integer.MAX_VALUE); + + try { + var receipt = context.buyShares("", option.optionKey(), option.optionKey(), amount); + } catch (GuessMarketException e) { + io.newLine(); + io.println("Purchase declined: %s".formatted(e.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 new file mode 100644 index 0000000..e12caeb --- /dev/null +++ b/ui-console/src/main/java/market/guess/ui/console/command/EventDetailsMenuCommand.java @@ -0,0 +1,26 @@ +package market.guess.ui.console.command; + +public final class EventDetailsMenuCommand implements MenuCommand { + + @Override + public int getIndex() { + return 3; + } + + @Override + public String getName() { + return "Show event details"; + } + + @Override + public boolean allowed() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'allowed'"); + } + + @Override + public void execute() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'execute'"); + } +} 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 new file mode 100644 index 0000000..3111044 --- /dev/null +++ b/ui-console/src/main/java/market/guess/ui/console/command/LoadMarketStateMenuCommand.java @@ -0,0 +1,26 @@ +package market.guess.ui.console.command; + +public final class LoadMarketStateMenuCommand implements MenuCommand { + + @Override + public int getIndex() { + return 8; + } + + @Override + public String getName() { + return "Load market state"; + } + + @Override + public boolean allowed() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'allowed'"); + } + + @Override + public void execute() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'execute'"); + } +} 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 new file mode 100644 index 0000000..2f950f0 --- /dev/null +++ b/ui-console/src/main/java/market/guess/ui/console/command/SaveMarketStateMenuCommand.java @@ -0,0 +1,44 @@ +package market.guess.ui.console.command; + +import java.nio.file.Path; +import market.guess.api.GuessMarketContext; +import market.guess.ui.console.io.InputProcessor; + +public final class SaveMarketStateMenuCommand implements MenuCommand { + private final GuessMarketContext context; + private final InputProcessor io; + + public SaveMarketStateMenuCommand(GuessMarketContext context, InputProcessor io) { + super(); + this.context = context; + this.io = io; + } + + @Override + public int getIndex() { + return 7; + } + + @Override + public String getName() { + return "Save current state"; + } + + @Override + public boolean allowed() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'allowed'"); + } + + @Override + public void execute() { + var path = io.readPath("Save to: "); + + try { + context.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/command/SettleEventMenuCommand.java b/ui-console/src/main/java/market/guess/ui/console/command/SettleEventMenuCommand.java new file mode 100644 index 0000000..9b62be5 --- /dev/null +++ b/ui-console/src/main/java/market/guess/ui/console/command/SettleEventMenuCommand.java @@ -0,0 +1,36 @@ +package market.guess.ui.console.command; + +import market.guess.api.GuessMarketContext; +import market.guess.ui.console.io.InputProcessor; + +public final class SettleEventMenuCommand implements MenuCommand { + private final GuessMarketContext context; + private final InputProcessor io; + + public SettleEventMenuCommand(GuessMarketContext context, InputProcessor io) { + this.context = context; + this.io = io; + } + + @Override + public int getIndex() { + return 5; + } + + @Override + public String getName() { + return "Settle an event"; + } + + @Override + public boolean allowed() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'allowed'"); + } + + @Override + public void execute() { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'execute'"); + } +} 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 fc5422f..b6d5405 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,14 +20,20 @@ public final class InputProcessor { } public void splashScreen() { - IO.println("<<-- GUESS MARKET -->>"); + newLine(); + IO.println("<<- GUESS MARKET ->>"); + } + + public void printState() { + nextLine(); + println(" Option | Value | Shares "); + + newLine(); } public String readLine(String prompt) { print(prompt); - var line = nextLine(); - - return line.trim(); + return nextLine().trim(); } public int readInt(String prompt, int min, int max) { -- cgit v1.2.3