I managed to figure it out eventually. I was on the right track originally; add a PlotChangeListener to be notified of a change and then get the crosshair value. Because my domain axis is a DateAxis the value returned as the crosshair value is actually the millisecond value of the date that the crosshair falls on. From that, I can construct a Day instance that can be used to retrieve the TimeSeriesDataItem from each TimeSeries in my TimeSeriesCollection.
plot.addChangeListener(new PlotChangeListener() {
    public void plotChanged(PlotChangeEvent event) {
        double crosshairXValue = this.plot.getDomainCrosshairValue();
        if (crosshairXValue == 0) {
            return;
        }
        Date date = new Date((long) crosshairXValue);
        Day day = new Day(date);
        for (Iterator<TimeSeries> itr = timeseriesCollection.getSeries().iterator(); itr.hasNext(); ) {
            TimeSeries timeSeries = itr.next();
            TimeSeriesDataItem dataItem = timeSeries.getDataItem(day);
            Number balance = dataItem.getValue();
            System.out.println("Balance for day " + day + " is " + balance);
            //TODO publish balances to listeners
        }
    }
});
Then you also have to add a MouseClickListener to the ChartPanel so that you can delegate clicks to the chart. Otherwise your plot change listener will never be invoked.
chartPanel.addChartMouseListener(new ChartMouseListener() {
        public void chartMouseMoved(ChartMouseEvent event) {
            // Do nothing
        }
        public void chartMouseClicked(ChartMouseEvent event) {
            int x = event.getTrigger().getX();
            int y = event.getTrigger().getY();
            chart.handleClick(x, y, chartPanel.getChartRenderingInfo());
        }
    });