Skip to content

best/worst preformers of the week #175

Merged
merged 1 commit into from
May 27, 2026
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public enum MarketActions {
SORT_PRICE,
/** Cycle the change-filter between winners-only and losers-only. */
SORT_CHANGE,
/** Cycle the week-change filter between best-of-week and worst-of-week. */
SORT_WEEK_CHANGE,
/** Show only stocks the player currently owns. */
SORT_OWNED;
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ protected void initInteractions() {
? MarketSort.LOSERS : MarketSort.WINNERS;
getViewElement().setSort(next);
});
getViewElement().setOnAction(MarketActions.SORT_WEEK_CHANGE,
() -> {
MarketSort next =
(getViewElement().getCurrentSort() == MarketSort.BEST_WEEK)
? MarketSort.WORST_WEEK : MarketSort.BEST_WEEK;
getViewElement().setSort(next);
});
getViewElement().setOnAction(MarketActions.SORT_OWNED,
() -> getViewElement().setSort(MarketSort.OWNED));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
*
* <p>{@link #WINNERS} and {@link #LOSERS} are mutually exclusive views
* of the "change" filter button: pressing the button cycles between
* the two. {@link #OWNED} acts as a "show only owned" filter rather
* than a pure sort.</p>
* the two. {@link #BEST_WEEK} and {@link #WORST_WEEK} behave the same
* way on the "week" filter button, but only consider the change from
* the previous week to the current one. {@link #OWNED} acts as a
* "show only owned" filter rather than a pure sort.</p>
* */
public enum MarketSort {
/** Alphabetical sort by ticker symbol. */
Expand All @@ -17,6 +19,10 @@ public enum MarketSort {
WINNERS,
/** Show only stocks with negative change, sorted ascending. */
LOSERS,
/** Show only stocks that went up this week, sorted best to worst. */
BEST_WEEK,
/** Show only stocks that went down this week, sorted worst to best. */
WORST_WEEK,
/** Show only stocks the player currently owns, sorted by quantity. */
OWNED;
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public class MarketView extends ViewElement<VBox, MarketActions> {
private Button sortTickerButton;
private Button sortPriceButton;
private Button sortChangeButton;
private Button sortWeekChangeButton;
private Button sortOwnedButton;

/** Maps every sort mode to its filter-bar button. Used for active state. */
Expand Down Expand Up @@ -113,6 +114,7 @@ private HBox buildFilterBar() {
sortTickerButton = new Button("A-Z");
sortPriceButton = new Button("price");
sortChangeButton = new Button("change");
sortWeekChangeButton = new Button("week");
sortOwnedButton = new Button("owned");

sortButtonMap.put(MarketSort.TICKER, sortTickerButton);
Expand All @@ -121,16 +123,20 @@ private HBox buildFilterBar() {
// between them on each press.
sortButtonMap.put(MarketSort.WINNERS, sortChangeButton);
sortButtonMap.put(MarketSort.LOSERS, sortChangeButton);
// BEST_WEEK and WORST_WEEK share the week button in the same way.
sortButtonMap.put(MarketSort.BEST_WEEK, sortWeekChangeButton);
sortButtonMap.put(MarketSort.WORST_WEEK, sortWeekChangeButton);
sortButtonMap.put(MarketSort.OWNED, sortOwnedButton);

registerButton(MarketActions.SORT_TICKER, sortTickerButton);
registerButton(MarketActions.SORT_PRICE, sortPriceButton);
registerButton(MarketActions.SORT_CHANGE, sortChangeButton);
registerButton(MarketActions.SORT_WEEK_CHANGE, sortWeekChangeButton);
registerButton(MarketActions.SORT_OWNED, sortOwnedButton);

HBox filterBar = new HBox(8,
searchField, sortTickerButton, sortPriceButton,
sortChangeButton, sortOwnedButton);
sortChangeButton, sortWeekChangeButton, sortOwnedButton);
filterBar.setAlignment(Pos.CENTER_LEFT);
filterBar.setPadding(Insets.EMPTY);

Expand Down Expand Up @@ -163,7 +169,8 @@ protected void initStyling() {
getRootPane().getStyleClass().add("market-container");

searchField.getStyleClass().add("market-search");
Stream.of(sortTickerButton, sortPriceButton, sortChangeButton, sortOwnedButton)
Stream.of(sortTickerButton, sortPriceButton, sortChangeButton,
sortWeekChangeButton, sortOwnedButton)
.forEach(b -> b.getStyleClass().add("market-filter-btn"));

stocksScroll.getStyleClass().add("market-scroll");
Expand Down Expand Up @@ -223,7 +230,8 @@ public MarketSort getCurrentSort() {
*
* <p>The change button doubles as a winners/losers cycle - its label
* updates to reflect the active mode so the user can tell which one
* they're currently looking at.</p>
* they're currently looking at. The week button does the same for
* best-of-week and worst-of-week.</p>
*
* @param sort the new sort mode.
* */
Expand All @@ -237,6 +245,7 @@ public void setSort(final MarketSort sort) {
}
}
refreshChangeButtonLabel();
refreshWeekChangeButtonLabel();
renderStocks();
}

Expand All @@ -256,11 +265,28 @@ private void refreshChangeButtonLabel() {
});
}

/**
* Updates the label on the week-change button the same way as
* {@link #refreshChangeButtonLabel()}, but for the best/worst-of-week
* cycle.
* */
private void refreshWeekChangeButtonLabel() {
if (sortWeekChangeButton == null) {
return;
}
sortWeekChangeButton.setText(switch (currentSort) {
case BEST_WEEK -> "best of week";
case WORST_WEEK -> "worst of week";
default -> "week";
});
}

/**
* Rebuilds the stock card grid based on the current search term and
* sort mode.
*
* <p>{@link MarketSort#WINNERS}, {@link MarketSort#LOSERS} and
* <p>{@link MarketSort#WINNERS}, {@link MarketSort#LOSERS},
* {@link MarketSort#BEST_WEEK}, {@link MarketSort#WORST_WEEK} and
* {@link MarketSort#OWNED} act as filters: in those modes the grid
* only shows the matching subset of stocks. The other modes show
* every stock that matches the search term.</p>
Expand Down Expand Up @@ -301,6 +327,8 @@ private boolean matchesSortFilter(final Stock stock) {
return switch (currentSort) {
case WINNERS -> changePercent(stock) > 0;
case LOSERS -> changePercent(stock) < 0;
case BEST_WEEK -> weekChangePercent(stock) > 0;
case WORST_WEEK -> weekChangePercent(stock) < 0;
case OWNED -> ownedLookup.applyAsInt(stock.getSymbol()) > 0;
default -> true;
};
Expand All @@ -315,6 +343,8 @@ private String emptyMessageFor(final MarketSort sort) {
return switch (sort) {
case WINNERS -> "no winners right now";
case LOSERS -> "no losers right now";
case BEST_WEEK -> "no stocks went up this week";
case WORST_WEEK -> "no stocks went down this week";
case OWNED -> "you don't own any stocks yet";
default -> "no stocks match your search";
};
Expand All @@ -331,6 +361,10 @@ private Comparator<Stock> comparatorFor(final MarketSort sort) {
(Stock s) -> changePercent(s)).reversed();
case LOSERS -> Comparator.comparingDouble(
(Stock s) -> changePercent(s));
case BEST_WEEK -> Comparator.comparingDouble(
(Stock s) -> weekChangePercent(s)).reversed();
case WORST_WEEK -> Comparator.comparingDouble(
(Stock s) -> weekChangePercent(s));
case OWNED -> Comparator.comparingInt(
(Stock s) -> ownedLookup.applyAsInt(s.getSymbol())).reversed();
};
Expand All @@ -353,6 +387,24 @@ private double changePercent(final Stock stock) {
return ((end - start) / start) * 100.0;
}

/**
* Calculates the change percentage between the previous week's price
* and the current week's price. Each entry in the price history
* represents one week, so this is just the last two prices.
* */
private double weekChangePercent(final Stock stock) {
List<BigDecimal> prices = stock.getHistoricalPrices();
if (prices.size() < 2) {
return 0.0;
}
double previous = prices.get(prices.size() - 2).doubleValue();
double current = prices.getLast().doubleValue();
if (previous == 0.0) {
return 0.0;
}
return ((current - previous) / previous) * 100.0;
}

/**
* Builds a single stock card. The card is clickable and forwards the
* selection to the registered {@code onStockSelected} consumer.
Expand Down