diff options
| author | Kostya <mail@sartin.in> | 2026-08-16 11:55:08 +0000 |
|---|---|---|
| committer | Kostya <mail@sartin.in> | 2026-08-16 11:55:08 +0000 |
| commit | 07a7a1cb3e4cfc1580bc05b0a6010a1e9f7e7508 (patch) | |
| tree | 3cf36ee5ba06de89602d923a32637820cc508d7a /service/src | |
| parent | 0247f3f5068b96df52a4f9424578d4c66ca6975c (diff) | |
| download | guess-market-07a7a1cb3e4cfc1580bc05b0a6010a1e9f7e7508.tar.gz guess-market-07a7a1cb3e4cfc1580bc05b0a6010a1e9f7e7508.tar.xz guess-market-07a7a1cb3e4cfc1580bc05b0a6010a1e9f7e7508.zip | |
Rename Option/Account to Market/Ledger, add Result-based error handling
Split GuessMarketContext into a separate CatalogContext for read-only
event listing, wrap fallible operations in Result<T> instead of throwing,
and wire load/save of events and users through a JSON Wrapper.
Diffstat (limited to 'service/src')
18 files changed, 295 insertions, 133 deletions
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; |