Back to Blog

Time Series Forecasting for Financial Behaviour: Predicting Spending Trends

Time series forecasting predicts future values based on historical patterns. In behavioural finance, this means anticipating spending trends, identifying potential problem periods, and enabling proactive interventions. Discover how Whistl uses ARIMA, Prophet, and deep learning to forecast financial behaviour.

Why Time Series Forecasting Matters for Financial Behaviour

Spending isn't random—it follows patterns driven by pay cycles, seasonal trends, habits, and life events. Time series forecasting captures these patterns to answer critical questions:

Unlike classification (impulse vs. not impulse), forecasting predicts continuous values—spending amounts, risk scores, or probability trajectories over time.

Classical Time Series Methods

Traditional statistical methods remain valuable for financial forecasting:

ARIMA (AutoRegressive Integrated Moving Average)

ARIMA models capture three components of time series data:

from statsmodels.tsa.arima.model import ARIMA
import pandas as pd

def forecast_spending_arima(transaction_history, forecast_steps=7):
    """
    Forecast future spending using ARIMA.
    
    Args:
        transaction_history: Daily spending amounts as time series
        forecast_steps: Number of days to forecast
    
    Returns:
        Forecast with confidence intervals
    """
    # Convert to pandas Series with datetime index
    spending_series = pd.Series(
        transaction_history['amount'],
        index=pd.to_datetime(transaction_history['date'])
    )
    
    # Aggregate to daily spending
    daily_spending = spending_series.resample('D').sum()
    
    # Fit ARIMA model
    # p=2 (AR order), d=1 (differencing), q=2 (MA order)
    model = ARIMA(daily_spending, order=(2, 1, 2))
    fitted_model = model.fit()
    
    # Forecast
    forecast = fitted_model.forecast(steps=forecast_steps)
    
    # Get confidence intervals
    pred_results = fitted_model.get_forecast(steps=forecast_steps)
    conf_int = pred_results.conf_int(alpha=0.05)
    
    return {
        'forecast': forecast.values,
        'lower_bound': conf_int.iloc[:, 0].values,
        'upper_bound': conf_int.iloc[:, 1].values,
        'dates': pd.date_range(start=daily_spending.index[-1], periods=forecast_steps+1)[1:]
    }

# Example output:
# Forecast: [120.5, 95.3, 180.2, 75.8, 110.4, 88.9, 145.6]
# (Higher on day 3 - possibly weekend or payday effect)

Seasonal ARIMA (SARIMA)

SARIMA extends ARIMA with seasonal components—crucial for spending data with weekly and monthly patterns:

from statsmodels.tsa.statespace.sarimax import SARIMAX

# SARIMA with weekly seasonality (7 days)
model = SARIMAX(
    daily_spending,
    order=(1, 1, 1),           # Non-seasonal parameters
    seasonal_order=(1, 1, 1, 7)  # Seasonal parameters (period=7 for weekly)
)

fitted_model = model.fit()
forecast = fitted_model.forecast(steps=30)  # 30-day forecast

Facebook Prophet for Business Time Series

Prophet, developed by Facebook, is designed for business time series with strong seasonal effects and holiday impacts—perfect for spending data.

Prophet Components

Prophet decomposes time series into:

Prophet Implementation

from prophet import Prophet
import pandas as pd

def forecast_with_prophet(transaction_data, forecast_days=30):
    """
    Forecast spending using Prophet.
    
    Args:
        transaction_data: DataFrame with 'date' and 'amount' columns
        forecast_days: Number of days to forecast
    
    Returns:
        Forecast DataFrame with trend and seasonality components
    """
    # Prepare data for Prophet (requires 'ds' and 'y' columns)
    df = pd.DataFrame({
        'ds': pd.to_datetime(transaction_data['date']),
        'y': transaction_data['amount']
    })
    
    # Aggregate to daily
    df = df.groupby('ds')['y'].sum().reset_index()
    
    # Initialize Prophet with custom settings
    model = Prophet(
        daily_seasonality=True,      # Intra-week patterns
        weekly_seasonality=True,     # Day-of-week patterns
        yearly_seasonality=False,    # Not enough data for yearly
        changepoint_prior_scale=0.1,  # Flexibility of trend changes
        holidays_prior_scale=10.0     # Impact of holidays
    )
    
    # Add Australian paydays as holidays
    paydays = generate_payday_holidays(years=[2025, 2026])
    model.add_country_holidays('AU')
    for payday in paydays:
        model.add_holiday(payday)
    
    # Fit model
    model.fit(df)
    
    # Create future dataframe
    future = model.make_future_dataframe(periods=forecast_days)
    
    # Generate forecast
    forecast = model.predict(future)
    
    # Extract components
    return {
        'forecast': forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']],
        'trend': forecast[['ds', 'trend']],
        'weekly': forecast[['ds', 'weekly']],
        'yearly': forecast[['ds', 'yearly']],
        'holidays': forecast[['ds', 'holidays']]
    }

def generate_payday_holidays(years):
    """Generate Australian payday dates (typically bi-weekly)."""
    paydays = []
    for year in years:
        # Simplified: actual implementation would use employer-specific dates
        for month in range(1, 13):
            paydays.append({
                'ds': f'{year}-{month:02d}-15',  # 15th of month
                'holiday': 'payday'
            })
            paydays.append({
                'ds': f'{year}-{month:02d}-30',  # End of month
                'holiday': 'payday'
            })
    return paydays

Prophet Visualization

# Plot forecast
fig = model.plot(forecast)
plt.title('Spending Forecast with Prophet')
plt.xlabel('Date')
plt.ylabel('Daily Spending ($)')

# Plot components
fig_components = model.plot_components(forecast)
# Shows trend, weekly seasonality, yearly seasonality, and holidays

Deep Learning for Time Series Forecasting

Neural networks offer powerful alternatives to statistical methods, especially for complex, non-linear patterns:

LSTM for Multi-Step Forecasting

import tensorflow as tf
from tensorflow import keras

class SpendingForecaster:
    def __init__(self, lookback=30, forecast_horizon=7):
        """
        LSTM-based spending forecaster.
        
        Args:
            lookback: Number of past days to use as input
            forecast_horizon: Number of days to predict
        """
        self.lookback = lookback
        self.forecast_horizon = forecast_horizon
        self.model = self._build_model()
    
    def _build_model(self):
        """Build LSTM forecasting model."""
        model = keras.Sequential([
            # LSTM layers for temporal patterns
            keras.layers.LSTM(
                128, 
                return_sequences=True,
                input_shape=(self.lookback, 1)
            ),
            keras.layers.Dropout(0.2),
            
            keras.layers.LSTM(64, return_sequences=True),
            keras.layers.Dropout(0.2),
            
            keras.layers.LSTM(32),
            keras.layers.Dropout(0.2),
            
            # Dense layers for output
            keras.layers.Dense(64, activation='relu'),
            keras.layers.Dense(self.forecast_horizon)  # Multi-step output
        ])
        
        model.compile(
            optimizer=keras.optimizers.Adam(learning_rate=0.001),
            loss='mse',
            metrics=['mae']
        )
        
        return model
    
    def prepare_sequences(self, data):
        """
        Create sequences for LSTM training.
        """
        X, y = [], []
        for i in range(len(data) - self.lookback - self.forecast_horizon + 1):
            X.append(data[i:i + self.lookback])
            y.append(data[i + self.lookback:i + self.lookback + self.forecast_horizon])
        
        return np.array(X), np.array(y)
    
    def fit(self, spending_data, epochs=100, batch_size=32):
        """Train the forecaster."""
        X, y = self.prepare_sequences(spending_data)
        
        # Reshape for LSTM
        X = X.reshape((X.shape[0], X.shape[1], 1))
        
        self.model.fit(
            X, y,
            epochs=epochs,
            batch_size=batch_size,
            validation_split=0.2,
            callbacks=[
                keras.callbacks.EarlyStopping(
                    monitor='val_loss',
                    patience=15,
                    restore_best_weights=True
                )
            ]
        )
    
    def predict(self, recent_spending):
        """
        Predict future spending.
        
        Args:
            recent_spending: Last `lookback` days of spending
        
        Returns:
            Forecast for next `forecast_horizon` days
        """
        X = np.array(recent_spending).reshape(1, self.lookback, 1)
        prediction = self.model.predict(X)
        return prediction[0]

Temporal Fusion Transformer (TFT)

TFT is a state-of-the-art architecture specifically designed for interpretable multi-horizon forecasting:

# Using PyTorch Forecasting library
from pytorch_forecasting import TemporalFusionTransformer, TimeSeriesDataSet

# Create time series dataset
training = TimeSeriesDataSet(
    data[lambda x: x.date <= cutoff_date],
    time_idx="time_idx",
    target="spending",
    group_ids=["user_id"],
    min_encoder_length=30,
    max_encoder_length=60,
    min_prediction_length=1,
    max_prediction_length=7,
    static_categoricals=["user_age_group", "account_type"],
    static_reals=["avg_spending", "income_level"],
    time_varying_known_categoricals=["day_of_week", "is_payday", "is_holiday"],
    time_varying_known_reals=["days_since_payday", "budget_remaining"],
    time_varying_unknown_reals=["stress_level", "sleep_quality"],
)

# Create model
tft = TemporalFusionTransformer.from_dataset(
    training,
    learning_rate=0.03,
    hidden_size=16,
    attention_head_size=1,
    dropout=0.1,
    hidden_continuous_size=8,
    output_size=7,  # 7-day forecast
    loss=QuantileLoss(),
)

# Train
trainer = pl.Trainer(max_epochs=50)
trainer.fit(tft, train_dataloader, val_dataloader)

# Predict
predictions = tft.predict(val_dataloader)

Ensemble Forecasting

Combining multiple forecasting methods often produces better results than any single approach:

class EnsembleForecaster:
    def __init__(self):
        self.arima_model = None
        self.prophet_model = None
        self.lstm_model = None
        self.weights = [0.3, 0.3, 0.4]  # ARIMA, Prophet, LSTM
    
    def fit(self, spending_data):
        """Train all forecasters."""
        # Fit ARIMA
        self.arima_model = ARIMA(spending_data, order=(2, 1, 2)).fit()
        
        # Fit Prophet
        prophet_df = pd.DataFrame({'ds': dates, 'y': spending_data})
        self.prophet_model = Prophet()
        self.prophet_model.fit(prophet_df)
        
        # Fit LSTM
        self.lstm_model = SpendingForecaster()
        self.lstm_model.fit(spending_data)
    
    def predict(self, forecast_steps=7):
        """Combine predictions from all models."""
        # ARIMA forecast
        arima_forecast = self.arima_model.forecast(steps=forecast_steps)
        
        # Prophet forecast
        future = self.prophet_model.make_future_dataframe(periods=forecast_steps)
        prophet_forecast = self.prophet_model.predict(future)['yhat'].tail(forecast_steps)
        
        # LSTM forecast
        lstm_forecast = self.lstm_model.predict(recent_spending)
        
        # Weighted ensemble
        ensemble_forecast = (
            self.weights[0] * arima_forecast +
            self.weights[1] * prophet_forecast.values +
            self.weights[2] * lstm_forecast
        )
        
        return ensemble_forecast

Forecasting Performance Metrics

Evaluating forecast accuracy requires appropriate metrics:

Metric Formula Best For
MAE (Mean Absolute Error) mean(|y - ŷ|) Interpretable error magnitude
RMSE (Root Mean Square Error) √mean((y - ŷ)²) Penalizing large errors
MAPE (Mean Absolute % Error) mean(|(y - ŷ)/y|) × 100 Relative error comparison
SMAPE (Symmetric MAPE) mean(|y - ŷ|/(|y| + |ŷ|)) × 200 Handling zero values
Coverage % of actual values in CI Confidence interval quality

Real-World Forecasting Applications

Whistl uses time series forecasting for multiple purposes:

Budget Trajectory Prediction

Forecasting whether users will stay within budget based on current spending rate and historical patterns.

High-Risk Period Identification

Predicting upcoming periods of elevated spending risk (e.g., weekends, post-payday, holiday seasons).

Trend Detection

Identifying whether spending is trending upward or downward over time, enabling early intervention.

"The forecast feature opened my eyes. Whistl showed me that at my current rate, I'd exceed my budget by $400 this month—with two weeks still to go. That prediction motivated me to change course before it was too late."
— Lisa H., Whistl user since 2025

Getting Started with Whistl

See your spending future before it happens. Whistl's time series forecasting helps you anticipate challenges and stay on track with your financial goals.

Predict Your Spending Future

Join thousands of Australians using Whistl's forecasting tools to anticipate spending trends and stay on budget.

Crisis Support Resources

If you're experiencing severe financial distress or gambling-related harm, professional support is available:

Related Articles