aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/src/main/java/market/guess/api/CatalogContext.java12
-rw-r--r--api/src/main/java/market/guess/api/EventStatus.java4
-rw-r--r--api/src/main/java/market/guess/api/EventSummaryDTO.java2
-rw-r--r--api/src/main/java/market/guess/api/GuessMarketContext.java23
-rw-r--r--api/src/main/java/market/guess/api/MarketDTO.java3
-rw-r--r--api/src/main/java/market/guess/api/MarketStateDTO.java2
-rw-r--r--api/src/main/java/market/guess/api/OptionStateDTO.java3
-rw-r--r--api/src/main/java/market/guess/api/Result.java49
-rw-r--r--api/src/main/java/market/guess/api/TradeRowDTO.java2
-rw-r--r--service/src/main/java/market/guess/service/LocalCatalogContext.java45
-rw-r--r--service/src/main/java/market/guess/service/LocalGuessMarketContext.java61
-rw-r--r--service/src/main/java/market/guess/service/TradingOperation.java4
-rw-r--r--service/src/main/java/market/guess/service/TradingOperationImpl.java4
-rw-r--r--service/src/main/java/market/guess/service/domain/Account.java49
-rw-r--r--service/src/main/java/market/guess/service/domain/Event.java73
-rw-r--r--service/src/main/java/market/guess/service/domain/Market.java32
-rw-r--r--service/src/main/java/market/guess/service/domain/Option.java3
-rw-r--r--service/src/main/java/market/guess/service/domain/User.java7
-rw-r--r--service/src/main/java/market/guess/service/infrastructure/MarketContext.java34
-rw-r--r--service/src/main/java/market/guess/service/infrastructure/mapper/v1/EventMapperV1.java8
-rw-r--r--service/src/main/java/market/guess/service/infrastructure/mapper/v2/EventMapperV2.java10
-rw-r--r--service/src/main/java/market/guess/service/infrastructure/repository/InMemoryEventRepository.java5
-rw-r--r--service/src/main/java/market/guess/service/infrastructure/repository/InMemoryUserRepository.java5
-rw-r--r--service/src/main/java/market/guess/service/infrastructure/repository/Repository.java2
-rw-r--r--service/src/main/java/market/guess/service/ledger/Ledger.java35
-rw-r--r--service/src/main/java/market/guess/service/ledger/LedgerContext.java49
-rw-r--r--service/src/main/java/market/guess/service/ledger/LedgerEntry.java (renamed from service/src/main/java/market/guess/service/domain/LedgerEntry.java)2
-rw-r--r--ui-console/src/main/java/market/guess/ui/console/Menu.java2
-rw-r--r--ui-console/src/main/java/market/guess/ui/console/command/BuyMenuCommand.java54
-rw-r--r--ui-console/src/main/java/market/guess/ui/console/command/EventDetailsMenuCommand.java6
-rw-r--r--ui-console/src/main/java/market/guess/ui/console/command/ExitMenuCommand.java5
-rw-r--r--ui-console/src/main/java/market/guess/ui/console/command/ListEventsMenuCommand.java20
-rw-r--r--ui-console/src/main/java/market/guess/ui/console/command/LoadFileMenuCommand.java31
-rw-r--r--ui-console/src/main/java/market/guess/ui/console/command/LoadMarketStateMenuCommand.java26
-rw-r--r--ui-console/src/main/java/market/guess/ui/console/command/MenuCommand.java2
-rw-r--r--ui-console/src/main/java/market/guess/ui/console/command/SaveMarketStateMenuCommand.java6
-rw-r--r--ui-console/src/main/java/market/guess/ui/console/command/SettleEventMenuCommand.java6
-rw-r--r--ui-console/src/main/java/market/guess/ui/console/io/InputProcessor.java41
38 files changed, 477 insertions, 250 deletions
diff --git a/api/src/main/java/market/guess/api/CatalogContext.java b/api/src/main/java/market/guess/api/CatalogContext.java
new file mode 100644
index 0000000..7e80042
--- /dev/null
+++ b/api/src/main/java/market/guess/api/CatalogContext.java
@@ -0,0 +1,12 @@
1package market.guess.api;
2
3import java.nio.file.Path;
4import java.util.List;
5
6public interface CatalogContext {
7 Result<LoadResultDTO> loadEvents(Path path);
8
9 Result<List<EventSummaryDTO>> getAllEvents();
10
11 Result<EventDetailDTO> getEvent(String eventKey);
12}
diff --git a/api/src/main/java/market/guess/api/EventStatus.java b/api/src/main/java/market/guess/api/EventStatus.java
index acd7542..6580157 100644
--- a/api/src/main/java/market/guess/api/EventStatus.java
+++ b/api/src/main/java/market/guess/api/EventStatus.java
@@ -1,7 +1,7 @@
1package market.guess.api; 1package market.guess.api;
2 2
3public enum EventStatus { 3public enum EventStatus {
4 NOT_STARTED, 4 DRAFT,
5 ACTIVE, 5 ACTIVE,
6 CLOSED 6 SETTLED
7} 7}
diff --git a/api/src/main/java/market/guess/api/EventSummaryDTO.java b/api/src/main/java/market/guess/api/EventSummaryDTO.java
index 361656b..d185103 100644
--- a/api/src/main/java/market/guess/api/EventSummaryDTO.java
+++ b/api/src/main/java/market/guess/api/EventSummaryDTO.java
@@ -3,7 +3,7 @@ package market.guess.api;
3import java.util.List; 3import java.util.List;
4 4
5public record EventSummaryDTO( 5public record EventSummaryDTO(
6 String eventKey, 6 String key,
7 int displayId, 7 int displayId,
8 String name, 8 String name,
9 String description, 9 String description,
diff --git a/api/src/main/java/market/guess/api/GuessMarketContext.java b/api/src/main/java/market/guess/api/GuessMarketContext.java
index d959de6..4eee37e 100644
--- a/api/src/main/java/market/guess/api/GuessMarketContext.java
+++ b/api/src/main/java/market/guess/api/GuessMarketContext.java
@@ -1,27 +1,16 @@
1package market.guess.api; 1package market.guess.api;
2 2
3import java.nio.file.Path; 3import java.nio.file.Path;
4import java.util.List;
5import market.guess.exception.GuessMarketException;
6 4
7public interface GuessMarketContext { 5public interface GuessMarketContext {
8 LoadResultDTO loadEvents(Path path); 6 Result<PurchaseReceiptDTO> buyShares(
7 String userName, String eventKey, String optionKey, int quantity);
9 8
10 List<EventSummaryDTO> listEvents(); 9 Result<EventDetailDTO> closeEvent(String userName, String eventKey, String winningOptionKey);
11 10
12 boolean isLoaded(); 11 Result<AccountDTO> account(String userName);
13 12
14 EventDetailDTO getEvent(String eventKey); 13 Result<Void> saveState(Path path);
15 14
16 PurchaseReceiptDTO buyShares(String userName, String eventKey, String optionKey, int quantity) 15 Result<LoadResultDTO> restoreState(Path path);
17 throws GuessMarketException;
18
19 EventDetailDTO closeEvent(String userName, String eventKey, String winningOptionKey)
20 throws GuessMarketException;
21
22 AccountDTO account(String userName);
23
24 void saveState(Path path) throws GuessMarketException;
25
26 LoadResultDTO restoreState(Path path);
27} 16}
diff --git a/api/src/main/java/market/guess/api/MarketDTO.java b/api/src/main/java/market/guess/api/MarketDTO.java
new file mode 100644
index 0000000..b7c48a2
--- /dev/null
+++ b/api/src/main/java/market/guess/api/MarketDTO.java
@@ -0,0 +1,3 @@
1package market.guess.api;
2
3public record MarketDTO(String key, String name, String price, String volume) {}
diff --git a/api/src/main/java/market/guess/api/MarketStateDTO.java b/api/src/main/java/market/guess/api/MarketStateDTO.java
index f49b2aa..1f7e79b 100644
--- a/api/src/main/java/market/guess/api/MarketStateDTO.java
+++ b/api/src/main/java/market/guess/api/MarketStateDTO.java
@@ -5,6 +5,6 @@ import java.util.List;
5public record MarketStateDTO( 5public record MarketStateDTO(
6 String eventKey, 6 String eventKey,
7 MechanismType mechanism, 7 MechanismType mechanism,
8 List<OptionStateDTO> options, 8 List<MarketDTO> markets,
9 String accountBalance, 9 String accountBalance,
10 String commissionCollected) {} 10 String commissionCollected) {}
diff --git a/api/src/main/java/market/guess/api/OptionStateDTO.java b/api/src/main/java/market/guess/api/OptionStateDTO.java
deleted file mode 100644
index c002fdd..0000000
--- a/api/src/main/java/market/guess/api/OptionStateDTO.java
+++ /dev/null
@@ -1,3 +0,0 @@
1package market.guess.api;
2
3public record OptionStateDTO(String optionKey, String name, String price, long sharesOutstanding) {}
diff --git a/api/src/main/java/market/guess/api/Result.java b/api/src/main/java/market/guess/api/Result.java
new file mode 100644
index 0000000..15ac5db
--- /dev/null
+++ b/api/src/main/java/market/guess/api/Result.java
@@ -0,0 +1,49 @@
1package market.guess.api;
2
3import java.time.Instant;
4
5public final class Result<T> {
6 private final boolean success;
7 private final String message;
8 private final T data;
9 private final Instant timeStamp;
10
11 private Result(boolean success, String message, T data) {
12 this.success = success;
13 this.message = message;
14 this.data = data;
15 this.timeStamp = Instant.now();
16 }
17
18 public static <T> Result<T> ok() {
19 return new Result<T>(true, "Operation successful", null);
20 }
21
22 public static <T> Result<T> ok(T data) {
23 return new Result<>(true, "Operation successful", data);
24 }
25
26 public static <T> Result<T> error(String message, T data) {
27 return new Result<>(false, message, data);
28 }
29
30 public static <T> Result<T> error(String message) {
31 return new Result<>(false, message, null);
32 }
33
34 public boolean isSuccess() {
35 return success;
36 }
37
38 public String getMessage() {
39 return message;
40 }
41
42 public T getData() {
43 return data;
44 }
45
46 public Instant getTimestamp() {
47 return timeStamp;
48 }
49}
diff --git a/api/src/main/java/market/guess/api/TradeRowDTO.java b/api/src/main/java/market/guess/api/TradeRowDTO.java
index 7331bb1..68d159a 100644
--- a/api/src/main/java/market/guess/api/TradeRowDTO.java
+++ b/api/src/main/java/market/guess/api/TradeRowDTO.java
@@ -1,4 +1,4 @@
1package market.guess.api; 1package market.guess.api;
2 2
3public record TradeRowDTO( 3public record TradeRowDTO(
4 String at, String userName, String optionName, long quantity, String pricePaid) {} 4 String at, String userName, String optionName, String quantity, String pricePaid) {}
diff --git a/service/src/main/java/market/guess/service/LocalCatalogContext.java b/service/src/main/java/market/guess/service/LocalCatalogContext.java
new file mode 100644
index 0000000..15a5890
--- /dev/null
+++ b/service/src/main/java/market/guess/service/LocalCatalogContext.java
@@ -0,0 +1,45 @@
1package market.guess.service;
2
3import java.nio.file.Path;
4import java.util.List;
5import market.guess.api.CatalogContext;
6import market.guess.api.EventDetailDTO;
7import market.guess.api.EventSummaryDTO;
8import market.guess.api.LoadResultDTO;
9import market.guess.api.Result;
10import market.guess.service.domain.Event;
11import market.guess.service.infrastructure.MarketContext;
12import market.guess.service.infrastructure.provider.Loader;
13
14public final class LocalCatalogContext implements CatalogContext {
15
16 private final MarketContext context;
17 private final Loader provider;
18
19 public LocalCatalogContext(Loader provider, MarketContext context) {
20 super();
21 this.provider = provider;
22 this.context = context;
23 }
24
25 @Override
26 public Result<LoadResultDTO> loadEvents(Path path) {
27 return Result.ok(provider.load(path));
28 }
29
30 @Override
31 public Result<List<EventSummaryDTO>> getAllEvents() {
32 return Result.ok(context.getEvents().getAll().stream().map(Event::toEventSummary).toList());
33 }
34
35 @Override
36 public Result<EventDetailDTO> getEvent(String eventKey) {
37 var optional = context.getEvents().get(eventKey);
38
39 if (optional.isEmpty()) return Result.error("Unable to find event.");
40 var event = optional.get();
41
42 return Result.ok(
43 new EventDetailDTO(event.toEventSummary(), event.toMarketState(), event.getHistory(), ""));
44 }
45}
diff --git a/service/src/main/java/market/guess/service/LocalGuessMarketContext.java b/service/src/main/java/market/guess/service/LocalGuessMarketContext.java
index b405329..8f97432 100644
--- a/service/src/main/java/market/guess/service/LocalGuessMarketContext.java
+++ b/service/src/main/java/market/guess/service/LocalGuessMarketContext.java
@@ -4,83 +4,66 @@ import java.nio.file.Path;
4import java.util.List; 4import java.util.List;
5import market.guess.api.AccountDTO; 5import market.guess.api.AccountDTO;
6import market.guess.api.EventDetailDTO; 6import market.guess.api.EventDetailDTO;
7import market.guess.api.EventSummaryDTO;
8import market.guess.api.GuessMarketContext; 7import market.guess.api.GuessMarketContext;
9import market.guess.api.LoadResultDTO; 8import market.guess.api.LoadResultDTO;
9import market.guess.api.ProblemDTO;
10import market.guess.api.PurchaseReceiptDTO; 10import market.guess.api.PurchaseReceiptDTO;
11import market.guess.exception.GuessMarketException; 11import market.guess.api.Result;
12import market.guess.service.domain.Event;
13import market.guess.service.infrastructure.MarketContext; 12import market.guess.service.infrastructure.MarketContext;
14import market.guess.service.infrastructure.provider.Loader;
15 13
16public final class LocalGuessMarketContext implements GuessMarketContext { 14public final class LocalGuessMarketContext implements GuessMarketContext {
17 15
18 private final TradingOperation tradingOperation; 16 private final TradingOperation tradingOperation;
19 private final MarketContext context; 17 private final MarketContext context;
20 private final Loader provider;
21 18
22 public LocalGuessMarketContext( 19 public LocalGuessMarketContext(MarketContext context, TradingOperation tradingOperation) {
23 Loader provider, MarketContext context, TradingOperation tradingOperation) {
24 super(); 20 super();
25 this.provider = provider;
26 this.context = context; 21 this.context = context;
27 this.tradingOperation = tradingOperation; 22 this.tradingOperation = tradingOperation;
28 } 23 }
29 24
30 @Override 25 @Override
31 public LoadResultDTO loadEvents(Path path) { 26 public Result<PurchaseReceiptDTO> buyShares(
32 return provider.load(path); 27 String userName, String eventKey, String optionKey, int quantity) {
33 }
34
35 @Override
36 public List<EventSummaryDTO> listEvents() {
37 return context.getEvents().getAll().stream().map(Event::toEventSummary).toList();
38 }
39
40 @Override
41 public boolean isLoaded() {
42 throw new UnsupportedOperationException("Unimplemented method 'isLoaded'");
43 }
44
45 @Override
46 public EventDetailDTO getEvent(String eventKey) {
47 throw new UnsupportedOperationException("Unimplemented method 'getEvent'");
48 }
49
50 @Override
51 public PurchaseReceiptDTO buyShares(
52 String userName, String eventKey, String optionKey, int quantity)
53 throws GuessMarketException {
54 var optional = context.getEvents().get(eventKey); 28 var optional = context.getEvents().get(eventKey);
55 29
56 var userOptional = context.getUsers().get(userName); 30 var userOptional = context.getUsers().get(userName);
57 if (optional.isEmpty() || userOptional.isEmpty()) throw new GuessMarketException(); 31 if (optional.isEmpty() || userOptional.isEmpty()) Result.error("Unable to find user/event.");
58 32
59 var event = optional.get(); 33 var event = optional.get();
60 var user = userOptional.get(); 34 var user = userOptional.get();
61 var option = event.getOption(optionKey); 35 var option = event.getOption(optionKey);
62 var trade = tradingOperation.buy(event, user, option, quantity); 36 var trade = tradingOperation.buy(event, user, option, quantity);
63 return new PurchaseReceiptDTO(); 37 return Result.ok(new PurchaseReceiptDTO());
64 } 38 }
65 39
66 @Override 40 @Override
67 public EventDetailDTO closeEvent(String userName, String eventKey, String winningOptionKey) 41 public Result<EventDetailDTO> closeEvent(
68 throws GuessMarketException { 42 String userName, String eventKey, String winningOptionKey) {
69 throw new UnsupportedOperationException("Unimplemented method 'account'"); 43 throw new UnsupportedOperationException("Unimplemented method 'account'");
70 } 44 }
71 45
72 @Override 46 @Override
73 public AccountDTO account(String userName) { 47 public Result<AccountDTO> account(String userName) {
74 throw new UnsupportedOperationException("Unimplemented method 'account'"); 48 throw new UnsupportedOperationException("Unimplemented method 'account'");
75 } 49 }
76 50
77 @Override 51 @Override
78 public void saveState(Path path) throws GuessMarketException { 52 public Result<Void> saveState(Path path) {
79 context.save(); 53 context.save();
54 return Result.ok();
80 } 55 }
81 56
82 @Override 57 @Override
83 public LoadResultDTO restoreState(Path path) { 58 public Result<LoadResultDTO> restoreState(Path path) {
84 throw new UnsupportedOperationException("Unimplemented method 'restoreState'"); 59
60 try {
61 context.load();
62 } catch (Exception e) {
63 return Result.error(
64 "Failed to load.",
65 LoadResultDTO.failure(path.toString(), List.of(new ProblemDTO("", e.getMessage(), ""))));
66 }
67 return Result.ok(LoadResultDTO.success(path.toString(), context.getEvents().getAll().size()));
85 } 68 }
86} 69}
diff --git a/service/src/main/java/market/guess/service/TradingOperation.java b/service/src/main/java/market/guess/service/TradingOperation.java
index c8153c2..63572c0 100644
--- a/service/src/main/java/market/guess/service/TradingOperation.java
+++ b/service/src/main/java/market/guess/service/TradingOperation.java
@@ -1,13 +1,13 @@
1package market.guess.service; 1package market.guess.service;
2 2
3import market.guess.service.domain.Event; 3import market.guess.service.domain.Event;
4import market.guess.service.domain.Option; 4import market.guess.service.domain.Market;
5import market.guess.service.domain.Settlement; 5import market.guess.service.domain.Settlement;
6import market.guess.service.domain.Trade; 6import market.guess.service.domain.Trade;
7import market.guess.service.domain.User; 7import market.guess.service.domain.User;
8 8
9public interface TradingOperation { 9public interface TradingOperation {
10 Trade buy(Event event, User user, Option Option, int quantity); 10 Trade buy(Event event, User user, Market Option, int quantity);
11 11
12 Settlement settle(Event event, String winningOption); 12 Settlement settle(Event event, String winningOption);
13} 13}
diff --git a/service/src/main/java/market/guess/service/TradingOperationImpl.java b/service/src/main/java/market/guess/service/TradingOperationImpl.java
index f6c9aa1..641a3a9 100644
--- a/service/src/main/java/market/guess/service/TradingOperationImpl.java
+++ b/service/src/main/java/market/guess/service/TradingOperationImpl.java
@@ -1,7 +1,7 @@
1package market.guess.service; 1package market.guess.service;
2 2
3import market.guess.service.domain.Event; 3import market.guess.service.domain.Event;
4import market.guess.service.domain.Option; 4import market.guess.service.domain.Market;
5import market.guess.service.domain.Settlement; 5import market.guess.service.domain.Settlement;
6import market.guess.service.domain.Trade; 6import market.guess.service.domain.Trade;
7import market.guess.service.domain.User; 7import market.guess.service.domain.User;
@@ -9,7 +9,7 @@ import market.guess.service.domain.User;
9public final class TradingOperationImpl implements TradingOperation { 9public final class TradingOperationImpl implements TradingOperation {
10 10
11 @Override 11 @Override
12 public Trade buy(Event event, User user, Option Option, int quantity) { 12 public Trade buy(Event event, User user, Market Option, int quantity) {
13 // TODO Auto-generated method stub 13 // TODO Auto-generated method stub
14 throw new UnsupportedOperationException("Unimplemented method 'buy'"); 14 throw new UnsupportedOperationException("Unimplemented method 'buy'");
15 } 15 }
diff --git a/service/src/main/java/market/guess/service/domain/Account.java b/service/src/main/java/market/guess/service/domain/Account.java
deleted file mode 100644
index 48d5054..0000000
--- a/service/src/main/java/market/guess/service/domain/Account.java
+++ /dev/null
@@ -1,49 +0,0 @@
1package market.guess.service.domain;
2
3import java.math.BigDecimal;
4import java.time.Clock;
5import java.util.ArrayList;
6import java.util.List;
7import market.guess.api.LedgerType;
8
9public final class Account {
10 private final String owner;
11 private final List<LedgerEntry> entries = new ArrayList<>();
12 private BigDecimal balance;
13 private int runningId;
14
15 private final Clock clock;
16
17 public Account(String owner, BigDecimal initialBalance) {
18 this(owner, initialBalance, Clock.systemUTC());
19 }
20
21 public Account(String owner, BigDecimal initialBalance, Clock clock) {
22 super();
23 this.owner = owner;
24 this.balance = BigDecimalOptions.toMoney(initialBalance);
25 this.clock = clock;
26 }
27
28 public String getOwner() {
29 return owner;
30 }
31
32 public BigDecimal getBalance() {
33 return balance;
34 }
35
36 public boolean credit(LedgerType type, BigDecimal amount, String note) {
37 record(type, amount, note);
38 return true;
39 }
40
41 public boolean debit(LedgerType type, BigDecimal amount, String note) {
42 record(type, amount.negate(), note);
43 return true;
44 }
45
46 private void record(LedgerType type, BigDecimal amount, String note) {
47 entries.add(new LedgerEntry(runningId++, clock.instant(), type, amount, balance, note));
48 }
49}
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 ce35e82..5c98908 100644
--- a/service/src/main/java/market/guess/service/domain/Event.java
+++ b/service/src/main/java/market/guess/service/domain/Event.java
@@ -7,6 +7,9 @@ import java.util.List;
7import market.guess.api.CommissionTiming; 7import market.guess.api.CommissionTiming;
8import market.guess.api.EventStatus; 8import market.guess.api.EventStatus;
9import market.guess.api.EventSummaryDTO; 9import market.guess.api.EventSummaryDTO;
10import market.guess.api.MarketStateDTO;
11import market.guess.api.TradeRowDTO;
12import market.guess.service.ledger.Ledger;
10import market.guess.service.mechanism.TradingMechanism; 13import market.guess.service.mechanism.TradingMechanism;
11 14
12public final class Event { 15public final class Event {
@@ -17,13 +20,13 @@ public final class Event {
17 private final int commissionPercent; 20 private final int commissionPercent;
18 private final CommissionTiming commissionTiming; 21 private final CommissionTiming commissionTiming;
19 private final TradingMechanism mechanism; 22 private final TradingMechanism mechanism;
20 private final List<Option> options; 23 private final List<Market> markets;
21 private final Account account; 24 private final Ledger ledger;
22 private final String marketMaker; 25 private final String marketMaker;
23 26
24 private int runningId; 27 private int runningId;
25 private final List<Trade> trades = new ArrayList<>(); 28 private final List<Trade> trades = new ArrayList<>();
26 private EventStatus status; 29 private EventStatus state;
27 private String winningOptionKey; 30 private String winningOptionKey;
28 private BigDecimal settlementCommission = BigDecimalOptions.ZERO_MONEY; 31 private BigDecimal settlementCommission = BigDecimalOptions.ZERO_MONEY;
29 32
@@ -36,7 +39,7 @@ public final class Event {
36 CommissionTiming commissionTiming, 39 CommissionTiming commissionTiming,
37 TradingMechanism mechanism, 40 TradingMechanism mechanism,
38 EventStatus status, 41 EventStatus status,
39 List<Option> options, 42 List<Market> markets,
40 String marketMaker) { 43 String marketMaker) {
41 this.eventKey = eventKey; 44 this.eventKey = eventKey;
42 this.displayId = displayId; 45 this.displayId = displayId;
@@ -45,10 +48,10 @@ public final class Event {
45 this.commissionPercent = commissionPercent; 48 this.commissionPercent = commissionPercent;
46 this.commissionTiming = commissionTiming; 49 this.commissionTiming = commissionTiming;
47 this.mechanism = mechanism; 50 this.mechanism = mechanism;
48 this.options = List.copyOf(options); 51 this.markets = markets;
49 this.marketMaker = marketMaker; 52 this.marketMaker = marketMaker;
50 this.account = new Account(eventKey, BigDecimalOptions.ZERO_MONEY); 53 this.ledger = new Ledger(eventKey, BigDecimalOptions.ZERO_MONEY);
51 this.status = status; 54 this.state = status;
52 } 55 }
53 56
54 public String getEventKey() { 57 public String getEventKey() {
@@ -79,16 +82,16 @@ public final class Event {
79 return mechanism; 82 return mechanism;
80 } 83 }
81 84
82 public List<Option> getOptions() { 85 public List<Market> getMarkets() {
83 return options; 86 return markets;
84 } 87 }
85 88
86 public List<String> getOptionsNames() { 89 public List<String> getOptionsNames() {
87 return options.stream().map(Option::name).toList(); 90 return markets.stream().map(Market::getKey).toList();
88 } 91 }
89 92
90 public Account getAccount() { 93 public Ledger getLedger() {
91 return account; 94 return ledger;
92 } 95 }
93 96
94 public String getMarketMaker() { 97 public String getMarketMaker() {
@@ -99,12 +102,12 @@ public final class Event {
99 return trades; 102 return trades;
100 } 103 }
101 104
102 public EventStatus getStatus() { 105 public EventStatus getState() {
103 return status; 106 return state;
104 } 107 }
105 108
106 public void setStatus(EventStatus status) { 109 public void setState(EventStatus status) {
107 this.status = status; 110 this.state = status;
108 } 111 }
109 112
110 public String getWinningOptionKey() { 113 public String getWinningOptionKey() {
@@ -118,12 +121,13 @@ public final class Event {
118 public Trade recordTrade( 121 public Trade recordTrade(
119 Instant time, 122 Instant time,
120 String user, 123 String user,
121 Option option, 124 Market option,
122 int quantity, 125 int quantity,
123 BigDecimal cost, 126 BigDecimal cost,
124 BigDecimal commission) { 127 BigDecimal commission) {
125 var trade = 128 var trade =
126 new Trade(runningId++, time, user, option.key(), option.name(), quantity, cost, commission); 129 new Trade(
130 runningId++, time, user, option.getKey(), option.getName(), quantity, cost, commission);
127 trades.add(trade); 131 trades.add(trade);
128 132
129 return trade; 133 return trade;
@@ -143,6 +147,20 @@ public final class Event {
143 .add(settlementCommission); 147 .add(settlementCommission);
144 } 148 }
145 149
150 public List<TradeRowDTO> getHistory() {
151 var history = new ArrayList<TradeRowDTO>();
152 for (var trade : trades.reversed()) {
153 history.add(
154 new TradeRowDTO(
155 trade.at().toString(),
156 trade.userName(),
157 trade.optionName(),
158 String.valueOf(trade.quantity()),
159 trade.sharesCost().toPlainString()));
160 }
161 return history;
162 }
163
146 public EventSummaryDTO toEventSummary() { 164 public EventSummaryDTO toEventSummary() {
147 return new EventSummaryDTO( 165 return new EventSummaryDTO(
148 eventKey, 166 eventKey,
@@ -152,14 +170,23 @@ public final class Event {
152 commissionPercent, 170 commissionPercent,
153 commissionTiming, 171 commissionTiming,
154 mechanism.getType(), 172 mechanism.getType(),
155 status, 173 state,
156 getOptionsNames(), 174 getOptionsNames(),
157 account.getBalance().toPlainString()); 175 ledger.getBalance().toPlainString());
176 }
177
178 public MarketStateDTO toMarketState() {
179 return new MarketStateDTO(
180 eventKey,
181 mechanism.getType(),
182 markets.stream().map(Market::toMarket).toList(),
183 String.valueOf(ledger.getBalance()),
184 "");
158 } 185 }
159 186
160 public Option getOption(String optionKey) { 187 public Market getOption(String optionKey) {
161 return options.stream() 188 return markets.stream()
162 .filter(option -> option.key().equalsIgnoreCase(optionKey)) 189 .filter(option -> option.getKey().equalsIgnoreCase(optionKey))
163 .findFirst() 190 .findFirst()
164 .orElseThrow(() -> new IllegalArgumentException("No such option.")); 191 .orElseThrow(() -> new IllegalArgumentException("No such option."));
165 } 192 }
diff --git a/service/src/main/java/market/guess/service/domain/Market.java b/service/src/main/java/market/guess/service/domain/Market.java
new file mode 100644
index 0000000..ce95bae
--- /dev/null
+++ b/service/src/main/java/market/guess/service/domain/Market.java
@@ -0,0 +1,32 @@
1package market.guess.service.domain;
2
3import market.guess.api.MarketDTO;
4
5public final class Market {
6 private final String key;
7 private final String name;
8
9 private int volume;
10
11 public Market(String key, String name) {
12 super();
13 this.key = key;
14 this.name = name;
15 }
16
17 public String getName() {
18 return name;
19 }
20
21 public String getKey() {
22 return key;
23 }
24
25 public int getVolume() {
26 return volume;
27 }
28
29 public MarketDTO toMarket() {
30 return new MarketDTO(key, name, "", String.valueOf(volume));
31 }
32}
diff --git a/service/src/main/java/market/guess/service/domain/Option.java b/service/src/main/java/market/guess/service/domain/Option.java
deleted file mode 100644
index 7f2ff86..0000000
--- a/service/src/main/java/market/guess/service/domain/Option.java
+++ /dev/null
@@ -1,3 +0,0 @@
1package market.guess.service.domain;
2
3public final record Option(String key, String name) {}
diff --git a/service/src/main/java/market/guess/service/domain/User.java b/service/src/main/java/market/guess/service/domain/User.java
index b2084b4..3536c4e 100644
--- a/service/src/main/java/market/guess/service/domain/User.java
+++ b/service/src/main/java/market/guess/service/domain/User.java
@@ -1,10 +1,11 @@
1package market.guess.service.domain; 1package market.guess.service.domain;
2 2
3import java.math.BigDecimal; 3import java.math.BigDecimal;
4import market.guess.service.ledger.Ledger;
4 5
5public final class User { 6public final class User {
6 private final String name; 7 private final String name;
7 private final Account account; 8 private final Ledger account;
8 9
9 public User(String name, int initialBalance) { 10 public User(String name, int initialBalance) {
10 this(name, BigDecimalOptions.toMoney(initialBalance)); 11 this(name, BigDecimalOptions.toMoney(initialBalance));
@@ -12,14 +13,14 @@ public final class User {
12 13
13 public User(String name, BigDecimal initialBalance) { 14 public User(String name, BigDecimal initialBalance) {
14 this.name = name; 15 this.name = name;
15 this.account = new Account(name, initialBalance); 16 this.account = new Ledger(name, initialBalance);
16 } 17 }
17 18
18 public String getName() { 19 public String getName() {
19 return name; 20 return name;
20 } 21 }
21 22
22 public Account getAccount() { 23 public Ledger getLedger() {
23 return account; 24 return account;
24 } 25 }
25} 26}
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 e769384..d9fe3f8 100644
--- a/service/src/main/java/market/guess/service/infrastructure/MarketContext.java
+++ b/service/src/main/java/market/guess/service/infrastructure/MarketContext.java
@@ -2,16 +2,23 @@ package market.guess.service.infrastructure;
2 2
3import com.google.gson.Gson; 3import com.google.gson.Gson;
4import com.google.gson.GsonBuilder; 4import com.google.gson.GsonBuilder;
5import com.google.gson.JsonSyntaxException;
5import java.io.IOException; 6import java.io.IOException;
6import java.nio.charset.StandardCharsets; 7import java.nio.charset.StandardCharsets;
7import java.nio.file.Files; 8import java.nio.file.Files;
9import java.nio.file.Path;
8import java.nio.file.Paths; 10import java.nio.file.Paths;
9import java.time.Instant; 11import java.time.Instant;
12import java.util.List;
13import market.guess.service.domain.Event;
14import market.guess.service.domain.User;
10import market.guess.service.infrastructure.adapter.InstantTypeAdapter; 15import market.guess.service.infrastructure.adapter.InstantTypeAdapter;
11import market.guess.service.infrastructure.repository.EventRepository; 16import market.guess.service.infrastructure.repository.EventRepository;
12import market.guess.service.infrastructure.repository.UserRepository; 17import market.guess.service.infrastructure.repository.UserRepository;
13 18
14public final class MarketContext { 19public final class MarketContext {
20 record Wrapper(List<Event> events, List<User> users) {}
21
15 private final EventRepository events; 22 private final EventRepository events;
16 private final UserRepository users; 23 private final UserRepository users;
17 24
@@ -39,11 +46,34 @@ public final class MarketContext {
39 46
40 try { 47 try {
41 if (path.getParent() != null) Files.createDirectories(path.getParent()); 48 if (path.getParent() != null) Files.createDirectories(path.getParent());
42 Files.writeString(path, GSON.toJson(events), StandardCharsets.UTF_8); 49 Files.writeString(
50 path,
51 GSON.toJson(new Wrapper(events.getAll(), users.getAll())),
52 StandardCharsets.UTF_8);
43 } catch (IOException e) { 53 } catch (IOException e) {
44 54
45 } 55 }
46 } 56 }
47 57
48 public void load() {} 58 public void load() {
59 try {
60 var wrapper =
61 GSON.fromJson(
62 Files.readString(Path.of("test-file.json"), StandardCharsets.UTF_8), Wrapper.class);
63 events.clear();
64 users.clear();
65
66 for (var event : wrapper.events()) {
67 events.add(event);
68 }
69
70 for (var user : wrapper.users()) {
71 users.add(user);
72 }
73
74 } catch (JsonSyntaxException | IOException e) {
75 // TODO Auto-generated catch block
76 e.printStackTrace();
77 }
78 }
49} 79}
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 8754555..7984c41 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
@@ -5,7 +5,7 @@ import java.util.List;
5import market.guess.api.CommissionTiming; 5import market.guess.api.CommissionTiming;
6import market.guess.api.EventStatus; 6import market.guess.api.EventStatus;
7import market.guess.service.domain.Event; 7import market.guess.service.domain.Event;
8import market.guess.service.domain.Option; 8import market.guess.service.domain.Market;
9import market.guess.service.infrastructure.mapper.Mapper; 9import market.guess.service.infrastructure.mapper.Mapper;
10import market.guess.service.mechanism.LmsrTradingMechanism; 10import market.guess.service.mechanism.LmsrTradingMechanism;
11import market.guess.service.model.v1.GMEvent; 11import market.guess.service.model.v1.GMEvent;
@@ -45,10 +45,10 @@ public final class EventMapperV1 implements Mapper<GMEvent, Event> {
45 }; 45 };
46 } 46 }
47 47
48 private static List<Option> getOptions(GMEvent x) { 48 private static List<Market> getOptions(GMEvent x) {
49 var ret = new ArrayList<Option>(x.getGMOptions().getGMOption().size()); 49 var ret = new ArrayList<Market>(x.getGMOptions().getGMOption().size());
50 for (var i = 0; i < x.getGMOptions().getGMOption().size(); i++) { 50 for (var i = 0; i < x.getGMOptions().getGMOption().size(); i++) {
51 ret.add(new Option(getEventKey(x) + ":" + i, x.getGMOptions().getGMOption().get(i))); 51 ret.add(new Market(getEventKey(x) + ":" + i, x.getGMOptions().getGMOption().get(i)));
52 } 52 }
53 return ret; 53 return ret;
54 } 54 }
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 a4fc4e7..0dd8e43 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
@@ -5,7 +5,7 @@ import java.util.List;
5import market.guess.api.CommissionTiming; 5import market.guess.api.CommissionTiming;
6import market.guess.api.EventStatus; 6import market.guess.api.EventStatus;
7import market.guess.service.domain.Event; 7import market.guess.service.domain.Event;
8import market.guess.service.domain.Option; 8import market.guess.service.domain.Market;
9import market.guess.service.infrastructure.mapper.Mapper; 9import market.guess.service.infrastructure.mapper.Mapper;
10import market.guess.service.mechanism.LmsrTradingMechanism; 10import market.guess.service.mechanism.LmsrTradingMechanism;
11import market.guess.service.mechanism.OrderBookTradingMechanism; 11import market.guess.service.mechanism.OrderBookTradingMechanism;
@@ -28,7 +28,7 @@ public final class EventMapperV2 implements Mapper<GMEvent, Event> {
28 source.getCommission().getValue(), 28 source.getCommission().getValue(),
29 getTiming(source.getCommission().getType()), 29 getTiming(source.getCommission().getType()),
30 getMechanism(source, options.size()), 30 getMechanism(source, options.size()),
31 EventStatus.NOT_STARTED, 31 EventStatus.DRAFT,
32 options, 32 options,
33 ""); 33 "");
34 } 34 }
@@ -59,10 +59,10 @@ public final class EventMapperV2 implements Mapper<GMEvent, Event> {
59 throw new IllegalArgumentException(); 59 throw new IllegalArgumentException();
60 } 60 }
61 61
62 private static List<Option> getOptions(GMEvent x) { 62 private static List<Market> getOptions(GMEvent x) {
63 var ret = new ArrayList<Option>(x.getGMOptions().getGMOption().size()); 63 var ret = new ArrayList<Market>(x.getGMOptions().getGMOption().size());
64 for (var i = 0; i < x.getGMOptions().getGMOption().size(); i++) { 64 for (var i = 0; i < x.getGMOptions().getGMOption().size(); i++) {
65 ret.add(new Option(getEventKey(x) + ":" + i, x.getGMOptions().getGMOption().get(i))); 65 ret.add(new Market(getEventKey(x) + ":" + i, x.getGMOptions().getGMOption().get(i)));
66 } 66 }
67 return ret; 67 return ret;
68 } 68 }
diff --git a/service/src/main/java/market/guess/service/infrastructure/repository/InMemoryEventRepository.java b/service/src/main/java/market/guess/service/infrastructure/repository/InMemoryEventRepository.java
index 0ed5b20..8b8eeb2 100644
--- a/service/src/main/java/market/guess/service/infrastructure/repository/InMemoryEventRepository.java
+++ b/service/src/main/java/market/guess/service/infrastructure/repository/InMemoryEventRepository.java
@@ -24,4 +24,9 @@ public final class InMemoryEventRepository implements EventRepository {
24 public List<Event> getAll() { 24 public List<Event> getAll() {
25 return Collections.unmodifiableList(events); 25 return Collections.unmodifiableList(events);
26 } 26 }
27
28 @Override
29 public void clear() {
30 events.clear();
31 }
27} 32}
diff --git a/service/src/main/java/market/guess/service/infrastructure/repository/InMemoryUserRepository.java b/service/src/main/java/market/guess/service/infrastructure/repository/InMemoryUserRepository.java
index b4014e5..b611a77 100644
--- a/service/src/main/java/market/guess/service/infrastructure/repository/InMemoryUserRepository.java
+++ b/service/src/main/java/market/guess/service/infrastructure/repository/InMemoryUserRepository.java
@@ -24,4 +24,9 @@ public final class InMemoryUserRepository implements UserRepository {
24 public List<User> getAll() { 24 public List<User> getAll() {
25 return Collections.unmodifiableList(users); 25 return Collections.unmodifiableList(users);
26 } 26 }
27
28 @Override
29 public void clear() {
30 users.clear();
31 }
27} 32}
diff --git a/service/src/main/java/market/guess/service/infrastructure/repository/Repository.java b/service/src/main/java/market/guess/service/infrastructure/repository/Repository.java
index 13f3901..41cb578 100644
--- a/service/src/main/java/market/guess/service/infrastructure/repository/Repository.java
+++ b/service/src/main/java/market/guess/service/infrastructure/repository/Repository.java
@@ -9,4 +9,6 @@ public interface Repository<T> {
9 Optional<T> get(String id); 9 Optional<T> get(String id);
10 10
11 List<T> getAll(); 11 List<T> getAll();
12
13 void clear();
12} 14}
diff --git a/service/src/main/java/market/guess/service/ledger/Ledger.java b/service/src/main/java/market/guess/service/ledger/Ledger.java
new file mode 100644
index 0000000..744fde7
--- /dev/null
+++ b/service/src/main/java/market/guess/service/ledger/Ledger.java
@@ -0,0 +1,35 @@
1package market.guess.service.ledger;
2
3import java.math.BigDecimal;
4import java.util.ArrayList;
5import java.util.Collections;
6import java.util.List;
7import market.guess.service.domain.BigDecimalOptions;
8
9public final class Ledger {
10 private final String owner;
11 private BigDecimal balance;
12 private final List<LedgerEntry> entries = new ArrayList<>();
13
14 public Ledger(String owner, BigDecimal initialBalance) {
15 super();
16 this.owner = owner;
17 this.balance = BigDecimalOptions.toMoney(initialBalance);
18 }
19
20 public String getOwner() {
21 return owner;
22 }
23
24 public BigDecimal getBalance() {
25 return balance;
26 }
27
28 public List<LedgerEntry> getEntries() {
29 return Collections.unmodifiableList(entries);
30 }
31
32 public void record(LedgerEntry entry) {
33 entries.add(entry);
34 }
35}
diff --git a/service/src/main/java/market/guess/service/ledger/LedgerContext.java b/service/src/main/java/market/guess/service/ledger/LedgerContext.java
new file mode 100644
index 0000000..caecf55
--- /dev/null
+++ b/service/src/main/java/market/guess/service/ledger/LedgerContext.java
@@ -0,0 +1,49 @@
1package market.guess.service.ledger;
2
3import java.math.BigDecimal;
4import java.time.Clock;
5import market.guess.api.LedgerType;
6import market.guess.service.infrastructure.repository.UserRepository;
7
8public final class LedgerContext {
9 private final Clock clock;
10 private final UserRepository users;
11
12 private int runningId;
13
14 public LedgerContext(UserRepository users) {
15 this(users, Clock.systemUTC());
16 }
17
18 public LedgerContext(UserRepository users, Clock clock) {
19 super();
20 this.clock = clock;
21 this.users = users;
22 }
23
24 public boolean credit(String name, LedgerType type, BigDecimal amount, String note) {
25 record(name, type, amount, note);
26 return true;
27 }
28
29 public boolean debit(String name, LedgerType type, BigDecimal amount, String note) {
30 record(name, type, amount.negate(), note);
31 return true;
32 }
33
34 private void record(String name, LedgerType type, BigDecimal amount, String note) {
35 users
36 .get(name)
37 .ifPresent(
38 u ->
39 u.getLedger()
40 .record(
41 new LedgerEntry(
42 runningId++,
43 clock.instant(),
44 type,
45 amount,
46 u.getLedger().getBalance().add(amount),
47 note)));
48 }
49}
diff --git a/service/src/main/java/market/guess/service/domain/LedgerEntry.java b/service/src/main/java/market/guess/service/ledger/LedgerEntry.java
index 7e6ff83..8008f20 100644
--- a/service/src/main/java/market/guess/service/domain/LedgerEntry.java
+++ b/service/src/main/java/market/guess/service/ledger/LedgerEntry.java
@@ -1,4 +1,4 @@
1package market.guess.service.domain; 1package market.guess.service.ledger;
2 2
3import java.math.BigDecimal; 3import java.math.BigDecimal;
4import java.time.Instant; 4import java.time.Instant;
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 90ddd38..9041590 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
@@ -20,8 +20,8 @@ public final class Menu implements Startable {
20 public void start() { 20 public void start() {
21 var max = commands.stream().mapToInt(MenuCommand::getIndex).max().orElse(0); 21 var max = commands.stream().mapToInt(MenuCommand::getIndex).max().orElse(0);
22 io.splashScreen(); 22 io.splashScreen();
23 while (true) {
24 23
24 while (true) {
25 io.newLine(); 25 io.newLine();
26 for (var command : commands) { 26 for (var command : commands) {
27 io.println(" [%d]->> %s".formatted(command.getIndex(), command.getName())); 27 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
index b8e2523..df40aa8 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,17 +1,19 @@
1package market.guess.ui.console.command; 1package market.guess.ui.console.command;
2 2
3import market.guess.api.CatalogContext;
3import market.guess.api.EventStatus; 4import market.guess.api.EventStatus;
4import market.guess.api.GuessMarketContext; 5import market.guess.api.GuessMarketContext;
5import market.guess.exception.GuessMarketException;
6import market.guess.ui.console.io.InputProcessor; 6import market.guess.ui.console.io.InputProcessor;
7 7
8public final class BuyMenuCommand implements MenuCommand { 8public final class BuyMenuCommand implements MenuCommand {
9 private final GuessMarketContext context; 9 private final GuessMarketContext context;
10 private final CatalogContext catalog;
10 private final InputProcessor io; 11 private final InputProcessor io;
11 12
12 public BuyMenuCommand(GuessMarketContext context, InputProcessor io) { 13 public BuyMenuCommand(GuessMarketContext context, CatalogContext catalog, InputProcessor io) {
13 super(); 14 super();
14 this.context = context; 15 this.context = context;
16 this.catalog = catalog;
15 this.io = io; 17 this.io = io;
16 } 18 }
17 19
@@ -26,49 +28,45 @@ public final class BuyMenuCommand implements MenuCommand {
26 } 28 }
27 29
28 @Override 30 @Override
29 public boolean allowed() {
30 // TODO Auto-generated method stub
31 throw new UnsupportedOperationException("Unimplemented method 'allowed'");
32 }
33
34 @Override
35 public void execute() { 31 public void execute() {
36 var active = 32 var result = catalog.getAllEvents();
37 context.listEvents().stream() 33
38 .filter(event -> event.status() == EventStatus.ACTIVE) 34 if (!result.isSuccess()) {
39 .toList(); 35 io.println("Failed to list events");
36 return;
37 }
38
39 var events = result.getData();
40 var active = events.stream().filter(event -> event.status() == EventStatus.ACTIVE).toList();
40 41
41 if (active.isEmpty()) { 42 if (active.isEmpty()) {
42 io.println("There are currently no active events to trade on."); 43 io.println("There are currently no active events to trade on.");
43 return; 44 return;
44 } 45 }
45 46
46 var pick = 47 var pick = io.readSelect("Pick an event: ", active, event -> "%s".formatted(event.name()));
47 io.readSelect(
48 "Pick an event: ",
49 active,
50 event -> "[%d] %s".formatted(event.displayId(), event.name()));
51
52 var event = context.getEvent(pick.eventKey());
53 48
49 var result2 = catalog.getEvent(pick.key());
50 var event = result2.getData();
54 if (event == null) { 51 if (event == null) {
55 io.println("That event is no longer available."); 52 io.println("That event is no longer available.");
56 return; 53 return;
57 } 54 }
58 55
59 // TODO: print state 56 io.printState(event.state());
60 57
61 var option = 58 var market =
62 io.readSelect( 59 io.readSelect(
63 "Choose an option: ", event.state().options(), o -> "%s [value %d, %d shares helds]"); 60 "Choose a market: ",
61 event.state().markets(),
62 o -> "%s [value %d, %d shares helds]".formatted(o.name(), o.price(), o.volume()));
64 63
65 var amount = io.readInt("How many shares? ", 1, Integer.MAX_VALUE); 64 var amount = io.readInt("How many shares? ", 1, Integer.MAX_VALUE);
66 65
67 try { 66 var receipt = context.buyShares("", event.summary().key(), market.key(), amount);
68 var receipt = context.buyShares("", option.optionKey(), option.optionKey(), amount); 67 if (receipt.isSuccess()) return;
69 } catch (GuessMarketException e) { 68
70 io.newLine(); 69 io.newLine();
71 io.println("Purchase declined: %s".formatted(e.getMessage())); 70 io.println("Purchase declined: %s".formatted(receipt.getMessage()));
72 }
73 } 71 }
74} 72}
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 e12caeb..ab60a1f 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
@@ -13,12 +13,6 @@ public final class EventDetailsMenuCommand implements MenuCommand {
13 } 13 }
14 14
15 @Override 15 @Override
16 public boolean allowed() {
17 // TODO Auto-generated method stub
18 throw new UnsupportedOperationException("Unimplemented method 'allowed'");
19 }
20
21 @Override
22 public void execute() { 16 public void execute() {
23 // TODO Auto-generated method stub 17 // TODO Auto-generated method stub
24 throw new UnsupportedOperationException("Unimplemented method 'execute'"); 18 throw new UnsupportedOperationException("Unimplemented method 'execute'");
diff --git a/ui-console/src/main/java/market/guess/ui/console/command/ExitMenuCommand.java b/ui-console/src/main/java/market/guess/ui/console/command/ExitMenuCommand.java
index 23ed9c9..4fcc448 100644
--- a/ui-console/src/main/java/market/guess/ui/console/command/ExitMenuCommand.java
+++ b/ui-console/src/main/java/market/guess/ui/console/command/ExitMenuCommand.java
@@ -21,11 +21,6 @@ public final class ExitMenuCommand implements MenuCommand {
21 } 21 }
22 22
23 @Override 23 @Override
24 public boolean allowed() {
25 return true;
26 }
27
28 @Override
29 public boolean isExit() { 24 public boolean isExit() {
30 return true; 25 return true;
31 } 26 }
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 bad621d..a97ab9f 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
@@ -1,16 +1,16 @@
1package market.guess.ui.console.command; 1package market.guess.ui.console.command;
2 2
3import market.guess.api.CatalogContext;
3import market.guess.api.CommissionTiming; 4import market.guess.api.CommissionTiming;
4import market.guess.api.GuessMarketContext;
5import market.guess.ui.console.io.InputProcessor; 5import market.guess.ui.console.io.InputProcessor;
6 6
7public final class ListEventsMenuCommand implements MenuCommand { 7public final class ListEventsMenuCommand implements MenuCommand {
8 private final GuessMarketContext context; 8 private final CatalogContext catalog;
9 private final InputProcessor io; 9 private final InputProcessor io;
10 10
11 public ListEventsMenuCommand(GuessMarketContext context, InputProcessor io) { 11 public ListEventsMenuCommand(CatalogContext catalog, InputProcessor io) {
12 super(); 12 super();
13 this.context = context; 13 this.catalog = catalog;
14 this.io = io; 14 this.io = io;
15 } 15 }
16 16
@@ -25,13 +25,13 @@ public final class ListEventsMenuCommand implements MenuCommand {
25 } 25 }
26 26
27 @Override 27 @Override
28 public boolean allowed() {
29 return !context.listEvents().isEmpty();
30 }
31
32 @Override
33 public void execute() { 28 public void execute() {
34 var events = context.listEvents(); 29 var result = catalog.getAllEvents();
30
31 if (!result.isSuccess()) {
32 return;
33 }
34 var events = result.getData();
35 35
36 for (var event : events) { 36 for (var event : events) {
37 io.newLine(); 37 io.newLine();
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 7005632..065ac6e 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
@@ -1,15 +1,15 @@
1package market.guess.ui.console.command; 1package market.guess.ui.console.command;
2 2
3import java.nio.file.Path; 3import java.nio.file.Path;
4import market.guess.api.GuessMarketContext; 4import market.guess.api.CatalogContext;
5import market.guess.ui.console.io.InputProcessor; 5import market.guess.ui.console.io.InputProcessor;
6 6
7public final class LoadFileMenuCommand implements MenuCommand { 7public final class LoadFileMenuCommand implements MenuCommand {
8 private final GuessMarketContext context; 8 private final CatalogContext catalog;
9 private final InputProcessor io; 9 private final InputProcessor io;
10 10
11 public LoadFileMenuCommand(GuessMarketContext context, InputProcessor io) { 11 public LoadFileMenuCommand(CatalogContext catalog, InputProcessor io) {
12 this.context = context; 12 this.catalog = catalog;
13 this.io = io; 13 this.io = io;
14 } 14 }
15 15
@@ -24,23 +24,24 @@ public final class LoadFileMenuCommand implements MenuCommand {
24 } 24 }
25 25
26 @Override 26 @Override
27 public boolean allowed() {
28 return true;
29 }
30
31 @Override
32 public void execute() { 27 public void execute() {
33 var path = io.readPath("Pleae enter the full file path to the XML file: "); 28 // var path = io.readPath("Pleae enter the full file path to the XML file: ");
34 var result = context.loadEvents(Path.of(path)); 29 // var result = context.loadEvents(Path.of(path));
30 var result =
31 catalog.loadEvents(
32 Path.of("/home/hex-dev/projects/guess-market/test-data/ex-1/multiple.xml"));
35 33
36 if (result.success()) { 34 if (result.isSuccess()) {
37 io.println("Loaded %d event(s) from %s.".formatted(result.eventsLoaded(), result.source())); 35 io.println(
36 "Loaded %d event(s) from %s."
37 .formatted(result.getData().eventsLoaded(), result.getData().source()));
38 return; 38 return;
39 } 39 }
40 40
41 io.println( 41 io.println(
42 "The file was not loaded. %d problem(s) were found: ".formatted(result.issues().size())); 42 "The file was not loaded. %d problem(s) were found: "
43 for (var issue : result.issues()) { 43 .formatted(result.getData().issues().size()));
44 for (var issue : result.getData().issues()) {
44 io.println(" - %s: %s".formatted(issue.location(), issue.humanizedMessage())); 45 io.println(" - %s: %s".formatted(issue.location(), issue.humanizedMessage()));
45 } 46 }
46 } 47 }
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 3111044..defb402 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,6 +1,18 @@
1package market.guess.ui.console.command; 1package market.guess.ui.console.command;
2 2
3import java.nio.file.Path;
4import market.guess.api.GuessMarketContext;
5import market.guess.ui.console.io.InputProcessor;
6
3public final class LoadMarketStateMenuCommand implements MenuCommand { 7public final class LoadMarketStateMenuCommand implements MenuCommand {
8 private final GuessMarketContext context;
9 private final InputProcessor io;
10
11 public LoadMarketStateMenuCommand(GuessMarketContext context, InputProcessor io) {
12 super();
13 this.context = context;
14 this.io = io;
15 }
4 16
5 @Override 17 @Override
6 public int getIndex() { 18 public int getIndex() {
@@ -13,14 +25,12 @@ public final class LoadMarketStateMenuCommand implements MenuCommand {
13 } 25 }
14 26
15 @Override 27 @Override
16 public boolean allowed() {
17 // TODO Auto-generated method stub
18 throw new UnsupportedOperationException("Unimplemented method 'allowed'");
19 }
20
21 @Override
22 public void execute() { 28 public void execute() {
23 // TODO Auto-generated method stub 29 var path = io.readPath("Which state file to load? ");
24 throw new UnsupportedOperationException("Unimplemented method 'execute'"); 30 var result = context.restoreState(Path.of(path));
31
32 if (result.isSuccess()) {
33 io.println("Restored %d event(s) from %s.".formatted(result.getData().eventsLoaded(), path));
34 }
25 } 35 }
26} 36}
diff --git a/ui-console/src/main/java/market/guess/ui/console/command/MenuCommand.java b/ui-console/src/main/java/market/guess/ui/console/command/MenuCommand.java
index 50acdac..8d35112 100644
--- a/ui-console/src/main/java/market/guess/ui/console/command/MenuCommand.java
+++ b/ui-console/src/main/java/market/guess/ui/console/command/MenuCommand.java
@@ -5,8 +5,6 @@ public interface MenuCommand {
5 5
6 String getName(); 6 String getName();
7 7
8 boolean allowed();
9
10 void execute(); 8 void execute();
11 9
12 default boolean isExit() { 10 default boolean isExit() {
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 2f950f0..27dfdde 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,12 +25,6 @@ public final class SaveMarketStateMenuCommand implements MenuCommand {
25 } 25 }
26 26
27 @Override 27 @Override
28 public boolean allowed() {
29 // TODO Auto-generated method stub
30 throw new UnsupportedOperationException("Unimplemented method 'allowed'");
31 }
32
33 @Override
34 public void execute() { 28 public void execute() {
35 var path = io.readPath("Save to: "); 29 var path = io.readPath("Save to: ");
36 30
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 9b62be5..b7344e3 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
@@ -23,12 +23,6 @@ public final class SettleEventMenuCommand implements MenuCommand {
23 } 23 }
24 24
25 @Override 25 @Override
26 public boolean allowed() {
27 // TODO Auto-generated method stub
28 throw new UnsupportedOperationException("Unimplemented method 'allowed'");
29 }
30
31 @Override
32 public void execute() { 26 public void execute() {
33 // TODO Auto-generated method stub 27 // TODO Auto-generated method stub
34 throw new UnsupportedOperationException("Unimplemented method 'execute'"); 28 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 b6d5405..912ca25 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
@@ -3,6 +3,7 @@ package market.guess.ui.console.io;
3import java.util.List; 3import java.util.List;
4import java.util.Scanner; 4import java.util.Scanner;
5import java.util.function.Function; 5import java.util.function.Function;
6import market.guess.api.MarketStateDTO;
6 7
7public final class InputProcessor { 8public final class InputProcessor {
8 private final Scanner scanner = new Scanner(System.in); 9 private final Scanner scanner = new Scanner(System.in);
@@ -21,14 +22,45 @@ public final class InputProcessor {
21 22
22 public void splashScreen() { 23 public void splashScreen() {
23 newLine(); 24 newLine();
24 IO.println("<<- GUESS MARKET ->>"); 25 IO.println(padLeft("<<- GUESS MARKET ->>", 36));
25 } 26 }
26 27
27 public void printState() { 28 public void printState(MarketStateDTO state) {
28 nextLine(); 29 newLine();
29 println(" Option | Value | Shares "); 30 println(
31 "%s|%s%s|%s"
32 .formatted(
33 padAround("Option", 18), space(10), padLeft("Value", 8), padRight("Shares", 8)));
30 34
35 for (var market : state.markets()) {
36 println(
37 "%s|%s%s|%s"
38 .formatted(
39 padAround(market.name(), 18),
40 space(10),
41 padLeft(market.price(), 8),
42 padRight(market.volume(), 8)));
43 }
31 newLine(); 44 newLine();
45
46 print("Event ledger balance: ".formatted(state.accountBalance()));
47 print("Commission collected: ".formatted(state.commissionCollected()));
48 }
49
50 private static String space(int width) {
51 return " ".repeat(width);
52 }
53
54 private static String padAround(String input, int width) {
55 return input.length() >= width ? input : padRight(padLeft(input, width / 2), width / 2);
56 }
57
58 private static String padRight(String input, int width) {
59 return input.length() >= width ? input : input + " ".repeat(width - input.length());
60 }
61
62 private static String padLeft(String input, int width) {
63 return input.length() >= width ? input : " ".repeat(width - input.length()) + input;
32 } 64 }
33 65
34 public String readLine(String prompt) { 66 public String readLine(String prompt) {
@@ -92,7 +124,6 @@ public final class InputProcessor {
92 } 124 }
93 125
94 public <T> T readSelect(String prompt, List<T> items, Function<T, String> function) { 126 public <T> T readSelect(String prompt, List<T> items, Function<T, String> function) {
95
96 for (var i = 0; i < items.size(); i++) { 127 for (var i = 0; i < items.size(); i++) {
97 IO.println(" [%d]->> %s".formatted(i + 1, function.apply(items.get(i)))); 128 IO.println(" [%d]->> %s".formatted(i + 1, function.apply(items.get(i))));
98 } 129 }