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:
- Will this user exceed their budget this week?
- When is the next high-risk spending period likely to occur?
- Is spending trending upward or downward over time?
- What impact will payday have on spending behaviour?
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:
- AR (AutoRegressive): Relationship with past values
- I (Integrated): Differencing to achieve stationarity
- MA (Moving Average): Relationship with past forecast errors
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:
- Trend: Long-term direction (increasing/decreasing spending)
- Seasonality: Weekly, monthly, yearly patterns
- Holidays: Special events (payday, holidays, sales events)
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."
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:
- Gambling Help: 1800 858 858 (24/7, free and confidential)
- Lifeline: 13 11 14 (24/7 crisis support)
- Beyond Blue: 1300 22 4636 (mental health support)
- Financial Counselling Australia: 1800 007 007