diff options
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 @@ | |||
| 1 | package market.guess.api; | ||
| 2 | |||
| 3 | import java.nio.file.Path; | ||
| 4 | import java.util.List; | ||
| 5 | |||
| 6 | public 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 @@ | |||
| 1 | package market.guess.api; | 1 | package market.guess.api; |
| 2 | 2 | ||
| 3 | public enum EventStatus { | 3 | public 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; | |||
| 3 | import java.util.List; | 3 | import java.util.List; |
| 4 | 4 | ||
| 5 | public record EventSummaryDTO( | 5 | public 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 @@ | |||
| 1 | package market.guess.api; | 1 | package market.guess.api; |
| 2 | 2 | ||
| 3 | import java.nio.file.Path; | 3 | import java.nio.file.Path; |
| 4 | import java.util.List; | ||
| 5 | import market.guess.exception.GuessMarketException; | ||
| 6 | 4 | ||
| 7 | public interface GuessMarketContext { | 5 | public 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 @@ | |||
| 1 | package market.guess.api; | ||
| 2 | |||
| 3 | public 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; | |||
| 5 | public record MarketStateDTO( | 5 | public 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 @@ | |||
| 1 | package market.guess.api; | ||
| 2 | |||
| 3 | public 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 @@ | |||
| 1 | package market.guess.api; | ||
| 2 | |||
| 3 | import java.time.Instant; | ||
| 4 | |||
| 5 | public 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 @@ | |||
| 1 | package market.guess.api; | 1 | package market.guess.api; |
| 2 | 2 | ||
| 3 | public record TradeRowDTO( | 3 | public 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 @@ | |||
| 1 | package market.guess.service; | ||
| 2 | |||
| 3 | import java.nio.file.Path; | ||
| 4 | import java.util.List; | ||
| 5 | import market.guess.api.CatalogContext; | ||
| 6 | import market.guess.api.EventDetailDTO; | ||
| 7 | import market.guess.api.EventSummaryDTO; | ||
| 8 | import market.guess.api.LoadResultDTO; | ||
| 9 | import market.guess.api.Result; | ||
| 10 | import market.guess.service.domain.Event; | ||
| 11 | import market.guess.service.infrastructure.MarketContext; | ||
| 12 | import market.guess.service.infrastructure.provider.Loader; | ||
| 13 | |||
| 14 | public 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; | |||
| 4 | import java.util.List; | 4 | import java.util.List; |
| 5 | import market.guess.api.AccountDTO; | 5 | import market.guess.api.AccountDTO; |
| 6 | import market.guess.api.EventDetailDTO; | 6 | import market.guess.api.EventDetailDTO; |
| 7 | import market.guess.api.EventSummaryDTO; | ||
| 8 | import market.guess.api.GuessMarketContext; | 7 | import market.guess.api.GuessMarketContext; |
| 9 | import market.guess.api.LoadResultDTO; | 8 | import market.guess.api.LoadResultDTO; |
| 9 | import market.guess.api.ProblemDTO; | ||
| 10 | import market.guess.api.PurchaseReceiptDTO; | 10 | import market.guess.api.PurchaseReceiptDTO; |
| 11 | import market.guess.exception.GuessMarketException; | 11 | import market.guess.api.Result; |
| 12 | import market.guess.service.domain.Event; | ||
| 13 | import market.guess.service.infrastructure.MarketContext; | 12 | import market.guess.service.infrastructure.MarketContext; |
| 14 | import market.guess.service.infrastructure.provider.Loader; | ||
| 15 | 13 | ||
| 16 | public final class LocalGuessMarketContext implements GuessMarketContext { | 14 | public 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 @@ | |||
| 1 | package market.guess.service; | 1 | package market.guess.service; |
| 2 | 2 | ||
| 3 | import market.guess.service.domain.Event; | 3 | import market.guess.service.domain.Event; |
| 4 | import market.guess.service.domain.Option; | 4 | import market.guess.service.domain.Market; |
| 5 | import market.guess.service.domain.Settlement; | 5 | import market.guess.service.domain.Settlement; |
| 6 | import market.guess.service.domain.Trade; | 6 | import market.guess.service.domain.Trade; |
| 7 | import market.guess.service.domain.User; | 7 | import market.guess.service.domain.User; |
| 8 | 8 | ||
| 9 | public interface TradingOperation { | 9 | public 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 @@ | |||
| 1 | package market.guess.service; | 1 | package market.guess.service; |
| 2 | 2 | ||
| 3 | import market.guess.service.domain.Event; | 3 | import market.guess.service.domain.Event; |
| 4 | import market.guess.service.domain.Option; | 4 | import market.guess.service.domain.Market; |
| 5 | import market.guess.service.domain.Settlement; | 5 | import market.guess.service.domain.Settlement; |
| 6 | import market.guess.service.domain.Trade; | 6 | import market.guess.service.domain.Trade; |
| 7 | import market.guess.service.domain.User; | 7 | import market.guess.service.domain.User; |
| @@ -9,7 +9,7 @@ import market.guess.service.domain.User; | |||
| 9 | public final class TradingOperationImpl implements TradingOperation { | 9 | public 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 @@ | |||
| 1 | package market.guess.service.domain; | ||
| 2 | |||
| 3 | import java.math.BigDecimal; | ||
| 4 | import java.time.Clock; | ||
| 5 | import java.util.ArrayList; | ||
| 6 | import java.util.List; | ||
| 7 | import market.guess.api.LedgerType; | ||
| 8 | |||
| 9 | public 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; | |||
| 7 | import market.guess.api.CommissionTiming; | 7 | import market.guess.api.CommissionTiming; |
| 8 | import market.guess.api.EventStatus; | 8 | import market.guess.api.EventStatus; |
| 9 | import market.guess.api.EventSummaryDTO; | 9 | import market.guess.api.EventSummaryDTO; |
| 10 | import market.guess.api.MarketStateDTO; | ||
| 11 | import market.guess.api.TradeRowDTO; | ||
| 12 | import market.guess.service.ledger.Ledger; | ||
| 10 | import market.guess.service.mechanism.TradingMechanism; | 13 | import market.guess.service.mechanism.TradingMechanism; |
| 11 | 14 | ||
| 12 | public final class Event { | 15 | public 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 @@ | |||
| 1 | package market.guess.service.domain; | ||
| 2 | |||
| 3 | import market.guess.api.MarketDTO; | ||
| 4 | |||
| 5 | public 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 @@ | |||
| 1 | package market.guess.service.domain; | ||
| 2 | |||
| 3 | public 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 @@ | |||
| 1 | package market.guess.service.domain; | 1 | package market.guess.service.domain; |
| 2 | 2 | ||
| 3 | import java.math.BigDecimal; | 3 | import java.math.BigDecimal; |
| 4 | import market.guess.service.ledger.Ledger; | ||
| 4 | 5 | ||
| 5 | public final class User { | 6 | public 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 | ||
| 3 | import com.google.gson.Gson; | 3 | import com.google.gson.Gson; |
| 4 | import com.google.gson.GsonBuilder; | 4 | import com.google.gson.GsonBuilder; |
| 5 | import com.google.gson.JsonSyntaxException; | ||
| 5 | import java.io.IOException; | 6 | import java.io.IOException; |
| 6 | import java.nio.charset.StandardCharsets; | 7 | import java.nio.charset.StandardCharsets; |
| 7 | import java.nio.file.Files; | 8 | import java.nio.file.Files; |
| 9 | import java.nio.file.Path; | ||
| 8 | import java.nio.file.Paths; | 10 | import java.nio.file.Paths; |
| 9 | import java.time.Instant; | 11 | import java.time.Instant; |
| 12 | import java.util.List; | ||
| 13 | import market.guess.service.domain.Event; | ||
| 14 | import market.guess.service.domain.User; | ||
| 10 | import market.guess.service.infrastructure.adapter.InstantTypeAdapter; | 15 | import market.guess.service.infrastructure.adapter.InstantTypeAdapter; |
| 11 | import market.guess.service.infrastructure.repository.EventRepository; | 16 | import market.guess.service.infrastructure.repository.EventRepository; |
| 12 | import market.guess.service.infrastructure.repository.UserRepository; | 17 | import market.guess.service.infrastructure.repository.UserRepository; |
| 13 | 18 | ||
| 14 | public final class MarketContext { | 19 | public 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; | |||
| 5 | import market.guess.api.CommissionTiming; | 5 | import market.guess.api.CommissionTiming; |
| 6 | import market.guess.api.EventStatus; | 6 | import market.guess.api.EventStatus; |
| 7 | import market.guess.service.domain.Event; | 7 | import market.guess.service.domain.Event; |
| 8 | import market.guess.service.domain.Option; | 8 | import market.guess.service.domain.Market; |
| 9 | import market.guess.service.infrastructure.mapper.Mapper; | 9 | import market.guess.service.infrastructure.mapper.Mapper; |
| 10 | import market.guess.service.mechanism.LmsrTradingMechanism; | 10 | import market.guess.service.mechanism.LmsrTradingMechanism; |
| 11 | import market.guess.service.model.v1.GMEvent; | 11 | import 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; | |||
| 5 | import market.guess.api.CommissionTiming; | 5 | import market.guess.api.CommissionTiming; |
| 6 | import market.guess.api.EventStatus; | 6 | import market.guess.api.EventStatus; |
| 7 | import market.guess.service.domain.Event; | 7 | import market.guess.service.domain.Event; |
| 8 | import market.guess.service.domain.Option; | 8 | import market.guess.service.domain.Market; |
| 9 | import market.guess.service.infrastructure.mapper.Mapper; | 9 | import market.guess.service.infrastructure.mapper.Mapper; |
| 10 | import market.guess.service.mechanism.LmsrTradingMechanism; | 10 | import market.guess.service.mechanism.LmsrTradingMechanism; |
| 11 | import market.guess.service.mechanism.OrderBookTradingMechanism; | 11 | import 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 @@ | |||
| 1 | package market.guess.service.ledger; | ||
| 2 | |||
| 3 | import java.math.BigDecimal; | ||
| 4 | import java.util.ArrayList; | ||
| 5 | import java.util.Collections; | ||
| 6 | import java.util.List; | ||
| 7 | import market.guess.service.domain.BigDecimalOptions; | ||
| 8 | |||
| 9 | public 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 @@ | |||
| 1 | package market.guess.service.ledger; | ||
| 2 | |||
| 3 | import java.math.BigDecimal; | ||
| 4 | import java.time.Clock; | ||
| 5 | import market.guess.api.LedgerType; | ||
| 6 | import market.guess.service.infrastructure.repository.UserRepository; | ||
| 7 | |||
| 8 | public 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 @@ | |||
| 1 | package market.guess.service.domain; | 1 | package market.guess.service.ledger; |
| 2 | 2 | ||
| 3 | import java.math.BigDecimal; | 3 | import java.math.BigDecimal; |
| 4 | import java.time.Instant; | 4 | import 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 @@ | |||
| 1 | package market.guess.ui.console.command; | 1 | package market.guess.ui.console.command; |
| 2 | 2 | ||
| 3 | import market.guess.api.CatalogContext; | ||
| 3 | import market.guess.api.EventStatus; | 4 | import market.guess.api.EventStatus; |
| 4 | import market.guess.api.GuessMarketContext; | 5 | import market.guess.api.GuessMarketContext; |
| 5 | import market.guess.exception.GuessMarketException; | ||
| 6 | import market.guess.ui.console.io.InputProcessor; | 6 | import market.guess.ui.console.io.InputProcessor; |
| 7 | 7 | ||
| 8 | public final class BuyMenuCommand implements MenuCommand { | 8 | public 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 @@ | |||
| 1 | package market.guess.ui.console.command; | 1 | package market.guess.ui.console.command; |
| 2 | 2 | ||
| 3 | import market.guess.api.CatalogContext; | ||
| 3 | import market.guess.api.CommissionTiming; | 4 | import market.guess.api.CommissionTiming; |
| 4 | import market.guess.api.GuessMarketContext; | ||
| 5 | import market.guess.ui.console.io.InputProcessor; | 5 | import market.guess.ui.console.io.InputProcessor; |
| 6 | 6 | ||
| 7 | public final class ListEventsMenuCommand implements MenuCommand { | 7 | public 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 @@ | |||
| 1 | package market.guess.ui.console.command; | 1 | package market.guess.ui.console.command; |
| 2 | 2 | ||
| 3 | import java.nio.file.Path; | 3 | import java.nio.file.Path; |
| 4 | import market.guess.api.GuessMarketContext; | 4 | import market.guess.api.CatalogContext; |
| 5 | import market.guess.ui.console.io.InputProcessor; | 5 | import market.guess.ui.console.io.InputProcessor; |
| 6 | 6 | ||
| 7 | public final class LoadFileMenuCommand implements MenuCommand { | 7 | public 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 @@ | |||
| 1 | package market.guess.ui.console.command; | 1 | package market.guess.ui.console.command; |
| 2 | 2 | ||
| 3 | import java.nio.file.Path; | ||
| 4 | import market.guess.api.GuessMarketContext; | ||
| 5 | import market.guess.ui.console.io.InputProcessor; | ||
| 6 | |||
| 3 | public final class LoadMarketStateMenuCommand implements MenuCommand { | 7 | public 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; | |||
| 3 | import java.util.List; | 3 | import java.util.List; |
| 4 | import java.util.Scanner; | 4 | import java.util.Scanner; |
| 5 | import java.util.function.Function; | 5 | import java.util.function.Function; |
| 6 | import market.guess.api.MarketStateDTO; | ||
| 6 | 7 | ||
| 7 | public final class InputProcessor { | 8 | public 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 | } |