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