@Override boolean shouldNotify(final ExecutionStartedEvent event, final SessionInfo sessionInfo) { final Optional<BigDecimal> lastKnownBalance = balanceTracker.getLastKnownBalance(sessionInfo); final BigDecimal newBalance = event.getPortfolioOverview().getCzkAvailable(); final BigDecimal expectedBalance = BigDecimal.valueOf(targetBalance); logger.debug("Last known balance: {}, target: {}, new: {}.", lastKnownBalance, expectedBalance, newBalance); final boolean balanceNowExceeded = newBalance.compareTo(expectedBalance) > 0; final boolean wasFineLastTime = !lastKnownBalance.isPresent() || lastKnownBalance.get().compareTo(expectedBalance) < 0; return (balanceNowExceeded && wasFineLastTime); } }
@Override boolean shouldNotify(final ExecutionStartedEvent event, final SessionInfo sessionInfo) { final Optional<BigDecimal> lastKnownBalance = balanceTracker.getLastKnownBalance(sessionInfo); final BigDecimal newBalance = event.getPortfolioOverview().getCzkAvailable(); final BigDecimal expectedBalance = BigDecimal.valueOf(targetBalance); LOGGER.debug("Last known balance: {}, target: {}, new: {}.", lastKnownBalance, expectedBalance, newBalance); final boolean balanceNowExceeded = newBalance.compareTo(expectedBalance) > 0; final boolean wasFineLastTime = !lastKnownBalance.isPresent() || lastKnownBalance.get().compareTo(expectedBalance) < 0; return (balanceNowExceeded && wasFineLastTime); } }
@Override boolean shouldNotify(final ExecutionStartedEvent event, final SessionInfo sessionInfo) { final Optional<BigDecimal> lastKnownBalance = balanceTracker.getLastKnownBalance(sessionInfo); final BigDecimal newBalance = event.getPortfolioOverview().getCzkAvailable(); final BigDecimal expectedBalance = BigDecimal.valueOf(minimumBalance); LOGGER.debug("Last known balance: {}, minimum: {}, new: {}.", lastKnownBalance, expectedBalance, newBalance); final boolean balanceNowUnder = newBalance.compareTo(expectedBalance) < 0; final boolean wasFineLastTime = !lastKnownBalance.isPresent() || lastKnownBalance.get().compareTo(expectedBalance) >= 0; return (balanceNowUnder && wasFineLastTime); } }
@Override boolean shouldNotify(final ExecutionStartedEvent event, final SessionInfo sessionInfo) { final Optional<BigDecimal> lastKnownBalance = balanceTracker.getLastKnownBalance(sessionInfo); final BigDecimal newBalance = event.getPortfolioOverview().getCzkAvailable(); final BigDecimal expectedBalance = BigDecimal.valueOf(minimumBalance); logger.debug("Last known balance: {}, minimum: {}, new: {}.", lastKnownBalance, expectedBalance, newBalance); final boolean balanceNowUnder = newBalance.compareTo(expectedBalance) < 0; final boolean wasFineLastTime = !lastKnownBalance.isPresent() || lastKnownBalance.get().compareTo(expectedBalance) >= 0; return (balanceNowUnder && wasFineLastTime); } }
protected static PortfolioOverview mockPortfolioOverview(final int balance) { final PortfolioOverview po = mock(PortfolioOverview.class); when(po.getCzkAvailable()).thenReturn(BigDecimal.valueOf(balance)); when(po.getCzkInvested()).thenReturn(BigDecimal.ZERO); when(po.getCzkInvested(any())).thenReturn(BigDecimal.ZERO); when(po.getCzkAtRisk()).thenReturn(BigDecimal.ZERO); when(po.getCzkAtRisk(any())).thenReturn(BigDecimal.ZERO); when(po.getShareAtRisk()).thenReturn(BigDecimal.ZERO); when(po.getShareOnInvestment(any())).thenReturn(BigDecimal.ZERO); when(po.getAtRiskShareOnInvestment(any())).thenReturn(BigDecimal.ZERO); when(po.getTimestamp()).thenReturn(ZonedDateTime.now()); return po; }
protected static PortfolioOverview mockPortfolioOverview(final int balance) { final PortfolioOverview po = mock(PortfolioOverview.class); when(po.getCzkAvailable()).thenReturn(BigDecimal.valueOf(balance)); when(po.getCzkInvested()).thenReturn(BigDecimal.ZERO); when(po.getCzkInvested(any())).thenReturn(BigDecimal.ZERO); when(po.getCzkAtRisk()).thenReturn(BigDecimal.ZERO); when(po.getCzkAtRisk(any())).thenReturn(BigDecimal.ZERO); when(po.getShareAtRisk()).thenReturn(BigDecimal.ZERO); when(po.getShareOnInvestment(any())).thenReturn(BigDecimal.ZERO); when(po.getAtRiskShareOnInvestment(any())).thenReturn(BigDecimal.ZERO); when(po.getTimestamp()).thenReturn(ZonedDateTime.now()); return po; }
static boolean isAcceptable(final ParsedStrategy strategy, final PortfolioOverview portfolio) { final long balance = portfolio.getCzkAvailable().longValue(); if (balance < strategy.getMinimumBalance()) { Decisions.report(logger -> logger.debug("Not recommending any loans due to balance under minimum.")); return false; } final long invested = portfolio.getCzkInvested().longValue(); final long investmentCeiling = strategy.getMaximumInvestmentSizeInCzk(); if (invested >= investmentCeiling) { Decisions.report(logger -> logger.debug("Not recommending any loans due to reaching the ceiling.")); return false; } return true; } }
static boolean isAcceptable(final ParsedStrategy strategy, final PortfolioOverview portfolio) { final long balance = portfolio.getCzkAvailable().longValue(); if (balance < strategy.getMinimumBalance()) { Decisions.report(logger -> logger.debug("Not recommending any loans due to balance under minimum.")); return false; } final long invested = portfolio.getCzkInvested().longValue(); final long investmentCeiling = strategy.getMaximumInvestmentSizeInCzk(); if (invested >= investmentCeiling) { Decisions.report(logger -> logger.debug("Not recommending any loans due to reaching the ceiling.")); return false; } return true; } }
@Override public Stream<RecommendedParticipation> recommend(final Collection<ParticipationDescriptor> available, final PortfolioOverview portfolio, final Restrictions restrictions) { if (!Util.isAcceptable(strategy, portfolio)) { return Stream.empty(); } // split available marketplace into buckets per rating final Map<Rating, List<ParticipationDescriptor>> splitByRating = Util.sortByRating(strategy.getApplicableParticipations(available), d -> d.item().getRating()); // recommend amount to invest per strategy return Util.rankRatingsByDemand(strategy, splitByRating.keySet(), portfolio) .flatMap(rating -> splitByRating.get(rating).stream().sorted(COMPARATOR)) .peek(d -> Decisions.report(logger -> logger.trace("Evaluating {}.", d.item()))) .filter(d -> sizeMatchesStrategy(d.item(), portfolio.getCzkAvailable())) .flatMap(d -> d.recommend().map(Stream::of).orElse(Stream.empty())); } }
@Override public Stream<RecommendedParticipation> recommend(final Collection<ParticipationDescriptor> available, final PortfolioOverview portfolio, final Restrictions restrictions) { if (!Util.isAcceptable(strategy, portfolio)) { return Stream.empty(); } // split available marketplace into buckets per rating final Map<Rating, List<ParticipationDescriptor>> splitByRating = Util.sortByRating(strategy.getApplicableParticipations(available), d -> d.item().getRating()); // recommend amount to invest per strategy return Util.rankRatingsByDemand(strategy, splitByRating.keySet(), portfolio) .flatMap(rating -> splitByRating.get(rating).stream().sorted(COMPARATOR)) .peek(d -> Decisions.report(logger -> logger.trace("Evaluating {}.", d.item()))) .filter(d -> sizeMatchesStrategy(d.item(), portfolio.getCzkAvailable())) .flatMap(d -> d.recommend().map(Stream::of).orElse(Stream.empty())); } }
@Test void unacceptablePortfolioDueToLowBalance() { final ParsedStrategy p = new ParsedStrategy(DefaultPortfolio.EMPTY); final PurchaseStrategy s = new NaturalLanguagePurchaseStrategy(p); final PortfolioOverview portfolio = mock(PortfolioOverview.class); when(portfolio.getCzkAvailable()).thenReturn(BigDecimal.ZERO); when(portfolio.getCzkInvested()).thenReturn(BigDecimal.ZERO); final Stream<RecommendedParticipation> result = s.recommend(Collections.singletonList(mockDescriptor()), portfolio, new Restrictions()); assertThat(result).isEmpty(); }
@Test void unacceptablePortfolioDueToLowBalance() { final ParsedStrategy p = new ParsedStrategy(DefaultPortfolio.EMPTY); final InvestmentStrategy s = new NaturalLanguageInvestmentStrategy(p); final PortfolioOverview portfolio = mock(PortfolioOverview.class); when(portfolio.getCzkAvailable()).thenReturn(BigDecimal.valueOf(p.getMinimumBalance() - 1)); final Stream<RecommendedLoan> result = s.recommend(Collections.singletonList(new LoanDescriptor(mockLoan(2))), portfolio, new Restrictions()); assertThat(result).isEmpty(); }
/** * Override to run custom code after {@link #handle(Event, SessionInfo)} has finished processing. Always call * {@link AbstractListener#finish(Event, SessionInfo)} in your override. * @param event * @param sessionInfo */ protected void finish(final T event, final SessionInfo sessionInfo) { if (event instanceof Financial) { // register balance final BigDecimal balance = ((Financial) event).getPortfolioOverview().getCzkAvailable(); balanceTracker.setLastKnownBalance(sessionInfo, balance); } if (event instanceof DelinquencyBased) { delinquencyTracker.setDelinquent(sessionInfo, ((DelinquencyBased) event).getInvestment()); } else if (event instanceof LoanLostEvent || event instanceof LoanRepaidEvent || event instanceof LoanNoLongerDelinquentEvent) { delinquencyTracker.unsetDelinquent(sessionInfo, ((InvestmentBased) event).getInvestment()); } }
/** * Override to run custom code after {@link #handle(Event, SessionInfo)} has finished processing. Always call * {@link AbstractListener#finish(Event, SessionInfo)} in your override. * @param event * @param sessionInfo */ protected void finish(final T event, final SessionInfo sessionInfo) { if (event instanceof Financial) { // register balance final BigDecimal balance = ((Financial) event).getPortfolioOverview().getCzkAvailable(); balanceTracker.setLastKnownBalance(sessionInfo, balance); } if (event instanceof DelinquencyBased) { delinquencyTracker.setDelinquent(sessionInfo, ((DelinquencyBased) event).getInvestment()); } else if (event instanceof LoanLostEvent || event instanceof LoanRepaidEvent || event instanceof LoanNoLongerDelinquentEvent) { delinquencyTracker.unsetDelinquent(sessionInfo, ((InvestmentBased) event).getInvestment()); } }
public static Collection<Investment> invest(final Investor investor, final PowerTenant tenant, final Collection<LoanDescriptor> loans, final InvestmentStrategy strategy) { final InvestingSession s = new InvestingSession(loans, investor, tenant); final PortfolioOverview portfolioOverview = tenant.getPortfolio().getOverview(); final long balance = portfolioOverview.getCzkAvailable().longValue(); s.tenant.fire(executionStartedLazy(() -> executionStarted(loans, portfolioOverview))); if (balance >= tenant.getRestrictions().getMinimumInvestmentAmount() && !s.getAvailable().isEmpty()) { s.invest(strategy); } final Collection<Investment> result = s.getResult(); // make sure we get fresh portfolio reference here s.tenant.fire(executionCompletedLazy(() -> executionCompleted(result, tenant.getPortfolio().getOverview()))); return Collections.unmodifiableCollection(result); }
public static Collection<Investment> invest(final Investor investor, final PowerTenant tenant, final Collection<LoanDescriptor> loans, final InvestmentStrategy strategy) { final InvestingSession s = new InvestingSession(loans, investor, tenant); final PortfolioOverview portfolioOverview = tenant.getPortfolio().getOverview(); final long balance = portfolioOverview.getCzkAvailable().longValue(); s.tenant.fire(executionStartedLazy(() -> executionStarted(loans, portfolioOverview))); if (balance >= tenant.getRestrictions().getMinimumInvestmentAmount() && !s.getAvailable().isEmpty()) { s.invest(strategy); } final Collection<Investment> result = s.getResult(); // make sure we get fresh portfolio reference here s.tenant.fire(executionCompletedLazy(() -> executionCompleted(result, tenant.getPortfolio().getOverview()))); return Collections.unmodifiableCollection(result); }
public static Map<String, Object> summarizePortfolioStructure(final PortfolioOverview portfolioOverview) { return Maps.ofEntries( entry("absoluteShare", perRating(portfolioOverview::getCzkInvested)), entry("relativeShare", perRating(portfolioOverview::getShareOnInvestment)), entry("absoluteRisk", perRating(portfolioOverview::getCzkAtRisk)), entry("relativeRisk", perRating(portfolioOverview::getAtRiskShareOnInvestment)), entry("total", portfolioOverview.getCzkInvested()), entry("totalRisk", portfolioOverview.getCzkAtRisk()), entry("totalShare", portfolioOverview.getShareAtRisk()), entry("balance", portfolioOverview.getCzkAvailable()), entry("timestamp", toDate(portfolioOverview.getTimestamp())) ); }
public static Map<String, Object> summarizePortfolioStructure(final PortfolioOverview portfolioOverview) { return Maps.ofEntries( entry("absoluteShare", perRating(portfolioOverview::getCzkInvested)), entry("relativeShare", perRating(portfolioOverview::getShareOnInvestment)), entry("absoluteRisk", perRating(portfolioOverview::getCzkAtRisk)), entry("relativeRisk", perRating(portfolioOverview::getAtRiskShareOnInvestment)), entry("total", portfolioOverview.getCzkInvested()), entry("totalRisk", portfolioOverview.getCzkAtRisk()), entry("totalShare", portfolioOverview.getShareAtRisk()), entry("balance", portfolioOverview.getCzkAvailable()), entry("timestamp", toDate(portfolioOverview.getTimestamp())) ); }
@Test void emptyPortfolio() { final BigDecimal balance = BigDecimal.TEN; final PortfolioOverview po = new PortfolioOverviewImpl(balance, Collections.emptyMap(), Collections.emptyMap()); SoftAssertions.assertSoftly(softly -> { softly.assertThat(po.getCzkAvailable()).isEqualTo(balance); softly.assertThat(po.getCzkInvested()).isEqualTo(BigDecimal.ZERO); softly.assertThat(po.getCzkAtRisk()).isEqualTo(BigDecimal.ZERO); softly.assertThat(po.getShareAtRisk()).isEqualTo(BigDecimal.ZERO); for (final Rating r : Rating.values()) { softly.assertThat(po.getCzkInvested(r)).as(r + " invested").isEqualTo(BigDecimal.ZERO); softly.assertThat(po.getCzkAtRisk(r)).as(r + " at risk").isEqualTo(BigDecimal.ZERO); softly.assertThat(po.getShareOnInvestment(r)) .as(r + " as a share") .isEqualTo(BigDecimal.ZERO); softly.assertThat(po.getAtRiskShareOnInvestment(r)) .as(r + " at risk as a share") .isEqualTo(BigDecimal.ZERO); } }); }
@Test void emptyPortfolioWithAdjustmentsAndRisks() { final BigDecimal adj = BigDecimal.TEN; final Map<Rating, BigDecimal> in = Collections.singletonMap(Rating.D, adj); final PortfolioOverview po = new PortfolioOverviewImpl(BigDecimal.ZERO, in, in); SoftAssertions.assertSoftly(softly -> { softly.assertThat(po.getCzkAvailable()).isEqualTo(BigDecimal.ZERO); softly.assertThat(po.getCzkInvested()).isEqualTo(adj); softly.assertThat(po.getCzkAtRisk()).isEqualTo(adj); final BigDecimal share = divide(po.getCzkAtRisk(), po.getCzkInvested()); softly.assertThat(po.getShareAtRisk()).isEqualTo(share); for (final Rating r : Rating.values()) { final BigDecimal expectedAbsolute = r == Rating.D ? adj : BigDecimal.ZERO; final BigDecimal expectedRelative = r == Rating.D ? BigDecimal.ONE : BigDecimal.ZERO; softly.assertThat(po.getCzkInvested(r)).as(r + " invested").isEqualTo(expectedAbsolute); softly.assertThat(po.getCzkAtRisk(r)).as(r + " at risk").isEqualTo(expectedAbsolute); softly.assertThat(po.getShareOnInvestment(r)) .as(r + " as a share") .isEqualTo(expectedRelative); softly.assertThat(po.getAtRiskShareOnInvestment(r)) .as(r + " at risk as a share") .isEqualTo(expectedRelative); } }); } }