Forecasting in real-world financial market data

In this tutorial, we will load data from the Jane Street Kaggle competition and apply advanced neural forecasting models. We’ll evaluate the performance of these models and understand their suitability for this task.

Open In Colab

WARNING

This tutorial requires a large amount of RAM. It is currently not yet possible to stream load the data. Experiments are conducted on a machine with 128GB of RAM.

Setup

First, we import the necessary libraries and configure logging

[1]:
import logging
import torch
from neuralforecast import NeuralForecast
from neuralforecast.losses.numpy import mae, mse
from neuralforecast.models import NBEATS, NHITS, BiTCN, NBEATSx
from fintorch.datasets.marketdata import MarketDataset

# Set up logging
logging.basicConfig(level=logging.INFO)
torch.set_float32_matmul_precision("medium")

/home/marcel/Documents/research/FinTorch/.conda/lib/python3.11/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm
2024-11-30 12:04:18,606 INFO util.py:154 -- Missing packages: ['ipywidgets']. Run `pip install -U ipywidgets`, then restart the notebook server for rich notebook output.
2024-11-30 12:04:18,697 INFO util.py:154 -- Missing packages: ['ipywidgets']. Run `pip install -U ipywidgets`, then restart the notebook server for rich notebook output.

Defining parameters

We define hyperparameters such as the input size (past time steps for prediction), forecast horizon, and batch sizes:

[2]:
# Hyperparameters
input_size = 2  # Number of past time steps used for prediction
days = 1  # Number of days to forecast
steps_per_day = 5  # Number of steps per day
horizon = days * steps_per_day  # Forecast horizon
max_steps = 100  # Max training steps

# Validation and test set sizes
val_size = horizon
test_size = horizon

# Batch sizes
batch_size = 16
windows_batch_size = 16
valid_batch_size = 16
batch_size = 1  # Single series per batch

Dataloading

We begin by loading the dataset using the MarketDataset class from the fintorch library:

[3]:
# Load dataset
df = MarketDataset("~/.fintorch_data/marketdata-janestreet/")
df = df.data.collect()

# Number of unique series
n_series = len(df["unique_id"].unique())
print(f"Number of unique series: {n_series}")

INFO:root:Load train market data
INFO:root:Downloading dataset from Kaggle
Downloading jane-street-real-time-market-data-forecasting.zip to ~/.fintorch_data/marketdata-janestreet/raw
100%|██████████| 11.5G/11.5G [07:15<00:00, 28.3MB/s]

INFO:root:Processing: apply transformations to train market data
INFO:root:Processing: apply transformations to train market data
Number of unique series: 39

This code initializes the dataset from the specified path and collects it into a DataFrame for further processing.

Models

We utilize the NeuralForecast library to implement four models: NHITS, BiTCN, NBEATS, and NBEATSx. These models are well-suited for time series forecasting due to their unique architectures:

  • NHITS: Builds upon NBEATS by specializing its outputs in different frequencies of the time series through hierarchical interpolation and multi-rate input processing, enhancing long-horizon forecasting accuracy.

  • BiTCN: Employs bidirectional temporal convolutional networks to capture both past and future dependencies in time series data, making it effective for sequential data modeling.

  • NBEATS: A deep neural architecture with backward and forward residual links, capable of modeling trend and seasonality components in time series data.

  • NBEATSx: Extends NBEATS by incorporating exogenous variables, allowing the model to leverage additional information for improved forecasting accuracy.

We configure each model with parameters such as input_size, horizon, max_steps, and batch sizes, and specify any exogenous features to be included.

[4]:
# Initialize models
models = [
    NHITS(
        input_size=input_size,
        h=horizon,
        futr_exog_list=["feature_00", "feature_01", "feature_02"],
        scaler_type="robust",
        max_steps=max_steps,
        windows_batch_size=windows_batch_size,
        batch_size=batch_size,
        valid_batch_size=valid_batch_size,
    ),
    BiTCN(
        input_size=input_size,
        h=horizon,
        futr_exog_list=["feature_00", "feature_01", "feature_02"],
        scaler_type="robust",
        max_steps=max_steps,
        windows_batch_size=windows_batch_size,
        batch_size=batch_size,
        valid_batch_size=valid_batch_size,
    ),
    NBEATS(
        input_size=input_size,
        h=horizon,
        max_steps=max_steps,
        windows_batch_size=windows_batch_size,
        batch_size=batch_size,
        valid_batch_size=valid_batch_size,
    ),
    NBEATSx(
        input_size=input_size,
        futr_exog_list=["feature_00", "feature_01", "feature_02"],
        h=horizon,
        max_steps=max_steps,
        windows_batch_size=windows_batch_size,
        batch_size=batch_size,
        valid_batch_size=valid_batch_size,
    ),
]

# List of model names
model_names = ["NHITS", "BiTCN", "NBEATS", "NBEATSx"]

Seed set to 1
Seed set to 1
Seed set to 1
Seed set to 1

Cross-validation

To assess model performance, we perform cross-validation using the cross_validation method of the NeuralForecast object:

[5]:
# Create NeuralForecast object
nf = NeuralForecast(models=models, freq="10s")

# Perform cross-validation
Y_hat_df = nf.cross_validation(
    df=df,
    val_size=val_size,
    test_size=test_size,
    n_windows=None,  # Expanding window
).to_pandas()

GPU available: True (cuda), used: True
TPU available: False, using: 0 TPU cores
HPU available: False, using: 0 HPUs
Missing logger folder: /home/marcel/Documents/research/FinTorch/docs/tutorials/marketdata/lightning_logs
LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]

  | Name         | Type          | Params | Mode
-------------------------------------------------------
0 | loss         | MAE           | 0      | train
1 | padder_train | ConstantPad1d | 0      | train
2 | scaler       | TemporalNorm  | 0      | train
3 | blocks       | ModuleList    | 2.4 M  | train
-------------------------------------------------------
2.4 M     Trainable params
0         Non-trainable params
2.4 M     Total params
9.591     Total estimated model params size (MB)
Epoch 2:  56%|█████▋    | 22/39 [00:37<00:28,  0.59it/s, v_num=0, train_loss_step=14.50, train_loss_epoch=14.40, valid_loss=0.270]
`Trainer.fit` stopped: `max_steps=100` reached.
Epoch 2:  56%|█████▋    | 22/39 [00:37<00:28,  0.59it/s, v_num=0, train_loss_step=14.50, train_loss_epoch=14.40, valid_loss=0.270]
GPU available: True (cuda), used: True
TPU available: False, using: 0 TPU cores
HPU available: False, using: 0 HPUs
LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]
Predicting DataLoader 0: 100%|██████████| 3/3 [00:12<00:00,  0.24it/s]
GPU available: True (cuda), used: True
TPU available: False, using: 0 TPU cores
HPU available: False, using: 0 HPUs
LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]

   | Name          | Type          | Params | Mode
---------------------------------------------------------
0  | loss          | MAE           | 0      | train
1  | padder_train  | ConstantPad1d | 0      | train
2  | scaler        | TemporalNorm  | 0      | train
3  | lin_hist      | Linear        | 80     | train
4  | drop_hist     | Dropout       | 0      | train
5  | net_bwd       | Sequential    | 1.1 K  | train
6  | lin_futr      | Linear        | 64     | train
7  | drop_futr     | Dropout       | 0      | train
8  | net_fwd       | Sequential    | 3.2 K  | train
9  | drop_temporal | Dropout       | 0      | train
10 | temporal_lin1 | Linear        | 48     | train
11 | temporal_lin2 | Linear        | 85     | train
12 | output_lin    | Linear        | 49     | train
---------------------------------------------------------
4.6 K     Trainable params
0         Non-trainable params
4.6 K     Total params
0.018     Total estimated model params size (MB)
Epoch 2:  56%|█████▋    | 22/39 [00:37<00:28,  0.59it/s, v_num=2, train_loss_step=13.90, train_loss_epoch=14.70, valid_loss=0.275]
`Trainer.fit` stopped: `max_steps=100` reached.
Epoch 2:  56%|█████▋    | 22/39 [00:37<00:28,  0.59it/s, v_num=2, train_loss_step=13.90, train_loss_epoch=14.70, valid_loss=0.275]
GPU available: True (cuda), used: True
TPU available: False, using: 0 TPU cores
HPU available: False, using: 0 HPUs
LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]
Predicting DataLoader 0: 100%|██████████| 3/3 [00:12<00:00,  0.24it/s]
GPU available: True (cuda), used: True
TPU available: False, using: 0 TPU cores
HPU available: False, using: 0 HPUs
LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]


  | Name         | Type          | Params | Mode
-------------------------------------------------------
0 | loss         | MAE           | 0      | train
1 | padder_train | ConstantPad1d | 0      | train
2 | scaler       | TemporalNorm  | 0      | train
3 | blocks       | ModuleList    | 2.4 M  | train
-------------------------------------------------------
2.4 M     Trainable params
77        Non-trainable params
2.4 M     Total params
9.534     Total estimated model params size (MB)
Epoch 2:  56%|█████▋    | 22/39 [00:37<00:28,  0.59it/s, v_num=4, train_loss_step=0.441, train_loss_epoch=0.454, valid_loss=0.234]
`Trainer.fit` stopped: `max_steps=100` reached.
Epoch 2:  56%|█████▋    | 22/39 [00:37<00:28,  0.59it/s, v_num=4, train_loss_step=0.441, train_loss_epoch=0.454, valid_loss=0.234]
GPU available: True (cuda), used: True
TPU available: False, using: 0 TPU cores
HPU available: False, using: 0 HPUs
LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]
Predicting DataLoader 0: 100%|██████████| 3/3 [00:12<00:00,  0.24it/s]
GPU available: True (cuda), used: True
TPU available: False, using: 0 TPU cores
HPU available: False, using: 0 HPUs
LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]


  | Name         | Type          | Params | Mode
-------------------------------------------------------
0 | loss         | MAE           | 0      | train
1 | padder_train | ConstantPad1d | 0      | train
2 | scaler       | TemporalNorm  | 0      | train
3 | blocks       | ModuleList    | 2.4 M  | train
-------------------------------------------------------
2.4 M     Trainable params
77        Non-trainable params
2.4 M     Total params
9.663     Total estimated model params size (MB)
Epoch 2:  56%|█████▋    | 22/39 [00:37<00:28,  0.59it/s, v_num=6, train_loss_step=0.428, train_loss_epoch=0.467, valid_loss=0.234]
`Trainer.fit` stopped: `max_steps=100` reached.
Epoch 2:  56%|█████▋    | 22/39 [00:37<00:28,  0.59it/s, v_num=6, train_loss_step=0.428, train_loss_epoch=0.467, valid_loss=0.234]
GPU available: True (cuda), used: True
TPU available: False, using: 0 TPU cores
HPU available: False, using: 0 HPUs
LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]
Predicting DataLoader 0: 100%|██████████| 3/3 [00:12<00:00,  0.24it/s]

This approach provides insights into how each model generalizes to unseen data. We then compute evaluation metrics such as Mean Squared Error (MSE) and Mean Absolute Error (MAE) to quantify the models’ predictive accuracy.

Evaluation

For each model, we calculate the Mean Squared Error (MSE) and Mean Absolute Error (MAE) as performance metrics:

[6]:
from neuralforecast.losses.numpy import mae, mse

# Iterate over models to compute metrics
for model_name in model_names:
    # Extract true values and predictions
    y_true = Y_hat_df.y.values
    y_hat = Y_hat_df[model_name].values

    # Reshape arrays
    y_true = y_true.reshape(n_series, -1, horizon)
    y_hat = y_hat.reshape(n_series, -1, horizon)

    # Compute metrics
    mse_score = mse(y_true, y_hat)
    mae_score = mae(y_true, y_hat)

    # Print results
    print(f"\nModel: {model_name}")
    print(f"MSE: {mse_score}")
    print(f"MAE: {mae_score}")


Model: NHITS
MSE: 0.1669229418039322
MAE: 0.28757739067077637

Model: BiTCN
MSE: 0.17169682681560516
MAE: 0.3052093982696533

Model: NBEATS
MSE: 0.12209504097700119
MAE: 0.257291316986084

Model: NBEATSx
MSE: 0.13161614537239075
MAE: 0.27656877040863037

Conclusion

By leveraging these advanced neural forecasting models, we can effectively tackle the time series forecasting challenges presented in the Jane Street Kaggle competition. Each model offers distinct advantages, enabling us to capture various patterns and dependencies within the financial data, ultimately enhancing our predictive capabilities.