Lesson 5.8.1 · 60 min

Time series foundation models: pretrain once, forecast without training

A time series foundation model is trained only once on tens of billions of observations from very different domains and is then used, without retraining, on a series it has never seen. This lesson explains what gets pretrained, how a series is turned into tokens or patches and what “zero-shot” means, and makes the first forecast with Chronos-Bolt on the level 5 realized volatility, with two naive baselines alongside.

Fig. — Chronos-Bolt-small one-day zero-shot forecast of the synthetic index's realized volatility in the first half of 2020: 10-90% band, median and realized (highlighted)
Level 5 lessons
  1. 5.1 · ML fundamentals applied to finance

    1. 5.1.1 Why financial machine learning is different
    2. 5.1.2 Supervised, unsupervised and reinforcement learning; scikit-learn pipelines
    3. 5.1.3 Economic metrics for trading models: beyond accuracy
  2. 5.2 · Feature engineering

    1. 5.2.1 Price, volume and microstructure features
    2. 5.2.2 Point-in-time fundamentals, macro and alternative data
    3. 5.2.3 Fractional differentiation for features
    4. 5.2.4 Leakage-free normalization and cross-sectional rank
    5. 5.2.5 Feature importance: MDI, MDA, SFI and SHAP
  3. 5.3 · Labelling and sampling

    1. 5.3.1 Fixed-horizon labeling and its problems
    2. 5.3.2 Triple-barrier method: labels that mimic a trade
    3. 5.3.3 Meta-labeling: one model for the side and another for the size
    4. 5.3.4 Label uniqueness, overlap weights and the sequential bootstrap
    5. 5.3.5 Event-based sampling: the symmetric CUSUM filter
  4. 5.4 · Validation for financial series

    1. 5.4.1 Why random k-fold fails on financial data
    2. 5.4.2 Purged k-fold with embargo and CPCV for ML models
    3. 5.4.3 Walk-forward with retraining: windows and frequency
    4. 5.4.4 Backtesting ML signals with costs and Deflated Sharpe
  5. 5.5 · Classical models

    1. 5.5.1 Regularized regressions: Ridge, Lasso, Elastic Net and logistic
    2. 5.5.2 Decision trees, Random Forest and bagging
    3. 5.5.3 Gradient boosting: XGBoost, LightGBM and CatBoost
    4. 5.5.4 Optuna and the risk of overfitting the validation
    5. 5.5.5 Unsupervised learning: PCA, clustering and autoencoders
    6. 5.5.6 Learning to rank for stock selection
  6. 5.6 · Deep learning with Keras 3

    1. 5.6.1 Keras 3: one model, several compute engines
    2. 5.6.2 An MLP on a stock panel: regularize and compare
    3. 5.6.3 1D CNN, LSTM and GRU: windows, scaling and leaks
    4. 5.6.4 Callbacks, a Sharpe loss and the model in production
  7. 5.7 · Deep learning with PyTorch

    1. 5.7.1 PyTorch: tensors, autograd and the training loop
    2. 5.7.2 Time series architectures: TCN, N-BEATS, PatchTST and iTransformer
    3. 5.7.3 Probabilistic models: quantile loss, DeepAR and intervals
    4. 5.7.4 Graph neural networks for asset relationships
    5. 5.7.5 End-to-end portfolio optimization: differentiable layers and Sharpe loss
  8. 5.8 · Time series foundation models

    1. 5.8.1 Time series foundation models: pretrain once, forecast without training
    2. 5.8.2 Foundation model families: architecture, data and license
    3. 5.8.3 In practice: context, quantiles, covariates and fine-tuning
    4. 5.8.4 Foundation models in finance: what the evidence says
    5. 5.8.5 Lab: foundation models vs GARCH, HAR and LightGBM
  9. 5.9 · Reinforcement learning

    1. 5.9.1 MDP, Q-learning, PPO and Gymnasium environments
    2. 5.9.2 RL for execution, market making and dynamic hedging
    3. 5.9.3 Why reinforcement learning overfits in directional trading
  10. 5.10 · Interpretability, robustness and model risk

    1. 5.10.1 SHAP, partial dependence and the stability of feature importances
    2. 5.10.2 Data drift and concept drift: detecting them and what to do
    3. 5.10.3 Ensembles, signal combination and synthetic data

From one model per series to one model for all

Up to here, every model in the course has been estimated with the data of its problem: a GARCH with the returns of one asset (lesson 2.1.4), a HAR or a network with the volatility of six assets (lesson 5.7.2). A time series foundation model reverses the order. Someone trains it once, on a huge corpus of series from very different domains (energy, traffic, sales, weather, web visits, finance, synthetic series), publishes the weights, and anyone can then use them on a series the model has never seen, without retraining. That is called zero-shot forecasting: the new series only enters as context, in the forward pass, and the weights do not change.

Massive corpusPretrainingPublished weightsContextZero-shotFine-tuning
Mermaid source
flowchart TB
  A[Massive corpus] --> B[Pretraining]
  B --> C[Published weights]
  C --> D[Context]
  D --> E[Zero-shot]
  E --> F[Fine-tuning]

The idea comes from language models: a transformer that has seen enough texts completes a new one without anyone training it for that text. In time series, the bet is that there are regularities common to almost all of them (trends, seasonalities, persistence, level shifts, structured noise) and that a large model can learn them and recognize them in the context it receives. The corpus figures say it all:

ModelPretraining corpus (according to its authors)
Chronos (Amazon, 2024)28 public datasets, about 890,000 series and about 84 billion observations, plus 10 million series augmented with TSMixup and 1 million synthetic Gaussian process series (KernelSynth)
Chronos-Bolt (Amazon, 2024)“nearly 100 billion time series observations”
TimesFM (Google, 2024)on the order of 100 billion points: Google Trends, Wikipedia page views and synthetic series

Lesson 5.8.2 covers the families with their architectures, sizes and licenses. This lesson sticks to what they have in common: how they turn a series into something a transformer can read and what you get when you use them without training.

The series: the index's realized variance

The test bench is the one from lessons 5.7.2 and 5.7.3: the indice asset of vol5_diario.parquet, 4,529 sessions from 2008 to 2025, with its realized variance for each day (rv_5min, in squared return). Its logarithm is forecast, which is almost Gaussian and is the scale on which the generator works.

IN [01]python
import numpy as np
import pandas as pd

vol = pd.read_parquet("vol5_diario.parquet")
ind = vol[vol["activo"] == "indice"].set_index("fecha")
log_rv = np.log(ind["rv_5min"].to_numpy())
vol_anual = np.sqrt(252 * ind["rv_5min"].to_numpy())
fechas = pd.DatetimeIndex(ind.index)
print(f"{len(log_rv)} sessions, {fechas[0].date()} to "
      f"{fechas[-1].date()}")
print(f"log RV: mean {log_rv.mean():.2f}, std. {log_rv.std():.2f}")
print(f"annual vol.: median {np.median(vol_anual):.1%}, "
      f"p99 {np.quantile(vol_anual, 0.99):.1%}")
OUT [01]The series: the index's realized variance
4529 sessions, 2008-01-02 to 2025-12-31
log RV: mean -9.75, std. 0.64
annual vol.: median 12.1%, p99 25.6%

The log RV sits around −9.75 with a standard deviation of 0.64: a series far from zero and with little relative variation. That detail matters right away.

From numbers to tokens: the Chronos recipe

A language transformer works with a finite vocabulary. Chronos (Ansari et al., 2024) does the minimum to make a series look like one: it scales the context by dividing it by the mean of its absolute values and quantizes the result into uniform bins. With a context x1,…,xCx_1, \dots, x_C:

s=1C∑i=1C∣xi∣,x~i=xis,tokeni=bin(x~i)s = \frac{1}{C}\sum_{i=1}^{C} |x_i|, \qquad \tilde x_i = \frac{x_i}{s}, \qquad \text{token}_i = \text{bin}(\tilde x_i)

According to the paper and the models' config.json, the bins are 4,093 equally spaced centers between −15 and +15 (a vocabulary of 4,096 with the special padding and end tokens), the context is 512 observations and the horizon, 64. The model (a T5 with 20 to 710 million parameters) is trained with cross-entropy to predict the next token, like a language model, and the probabilistic forecast comes from sampling token trajectories and undoing the scaling. Each bin measures 30/4,092≈0.007330/4{,}092 \approx 0.0073 in scaled units.

The chronos-forecasting library includes the tokenizer, which needs no weights: the configuration of amazon/chronos-t5-small is enough. The cell reproduces it by hand and applies it to two versions of the same information, the annual volatility and the logarithm of the variance:

IN [02]python
import torch
from chronos import ChronosConfig

# The configuration of amazon/chronos-t5-small (the repo's config.json)
cfg = ChronosConfig(
    tokenizer_class="MeanScaleUniformBins",
    tokenizer_kwargs={"low_limit": -15.0, "high_limit": 15.0},
    n_tokens=4096, n_special_tokens=2, pad_token_id=0,
    eos_token_id=1, use_eos_token=True, model_type="seq2seq",
    context_length=512, prediction_length=64, num_samples=20,
    temperature=1.0, top_k=50, top_p=1.0)
tok = cfg.create_tokenizer()
centros = np.linspace(-15, 15, 4096 - 2 - 1)

def tokens_a_mano(x):
    """Scale by the mean of |x| and round to the nearest center.
    Ids 0 and 1 are PAD and EOS; the bins start at 3."""
    escala = np.abs(x).mean()
    ids = np.abs(x[:, None] / escala - centros).argmin(1) + 3
    return ids, centros[ids - 3] * escala

for nombre, x in (("annual vol", vol_anual[-512:]),
                  ("log RV", log_rv[-512:])):
    ids, x_rec = tokens_a_mano(x)
    ids_chr, _, escala = tok.context_input_transform(
        torch.tensor(x[None, :], dtype=torch.float32))
    igual = (ids == ids_chr[0, :512].numpy()).mean()
    print(f"{nombre:<10} scale {escala.item():8.4f}  "
          f"distinct tokens {len(np.unique(ids)):4d}  "
          f"max. error {np.abs(x - x_rec).max():.4f}  "
          f"same as chronos {igual:.0%}")
OUT [02]Chronos tokens: mean scaling and 4,093 bins
annual vol scale   0.1405  distinct tokens  168  max. error 0.0005  same as chronos 100%
log RV     scale   9.5590  distinct tokens   46  max. error 0.0350  same as chronos 100%

The library's tokenizer and the cell's agree. The comparison between the two series shows the weakness of mean scaling. The annual volatility (positive, with a mean of 0.14) uses 168 distinct bins and the rounding costs at most 0.0005, five hundredths of a volatility point. The logarithm of the variance, on the other hand, is always around −9.6: divided by its mean absolute value it is almost always close to −1, it only uses 46 bins and the rounding reaches 0.035 in log, 3.6% of the variance. Mean scaling does not subtract the mean, so a series far from zero loses resolution. Exercise 2 measures it with the standardized series.

Patches: what Chronos-Bolt and TimesFM do

The second generation drops the one-token-per-value approach. Chronos-Bolt standardizes the context (subtracts the mean and divides by the standard deviation, what the code calls InstanceNorm), cuts it into patches of 16 observations and projects each patch to a vector with a small network; at the end it adds a regression token. The T5 encoder reads those vectors and the decoder produces at once the 0.1…0.9 quantiles of the next 64 steps (direct multi-step forecasting), without sampling. TimesFM does something similar with patches of 32 and a decoder-only transformer (lesson 5.8.2). It is the same idea as PatchTST in lesson 5.7.2, at pretraining scale.

ContextMean scalingOne token per valueStandardizePatches of 16Transformer
Mermaid source
flowchart LR
  S[Context] --> M[Mean scaling]
  M --> Q[One token per value]
  S --> N[Standardize]
  N --> P[Patches of 16]
  Q --> T[Transformer]
  P --> T

The consequence is about cost. Attention compares each token with all the others, so its cost grows with the square of the number of tokens:

Fig. — How many tokens the transformer processes for a 512-observation context: one per point in Chronos (plus the end token), a 16-point patch plus the regression token in Chronos-Bolt and a 32-point patch in TimesFM-2.5

The Chronos-Bolt-small weights are in the ml environment's cache, pinned by commit (environment README). The cell loads them without network access and checks the hand-made patches against the model's:

IN [03]python
from chronos import BaseChronosPipeline

torch.manual_seed(0)
torch.use_deterministic_algorithms(True)
bolt = BaseChronosPipeline.from_pretrained(
    "amazon/chronos-bolt-small", device_map="cpu",
    dtype=torch.float32)
cc = bolt.model.chronos_config
print(f"max. context {cc.context_length}, "
      f"patch {cc.input_patch_size}, "
      f"horizon {cc.prediction_length}, quantiles {cc.quantiles}")

x = torch.tensor(log_rv[-512:], dtype=torch.float32)[None, :]
z = (x - x.mean()) / x.std(correction=0)       # standardize
parches = z.unfold(-1, 16, 16)                    # (1, 32, 16)
z_bolt, _ = bolt.model.instance_norm(x)
print("patches:", tuple(parches.shape),
      "| equal to the model's:",
      torch.allclose(parches, bolt.model.patch(z_bolt), atol=1e-5))
print(f"standardized values: from {z.min():.2f} to {z.max():.2f}")
OUT [03]Chronos-Bolt patches
max. context 2048, patch 16, horizon 64, quantiles [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]
patches: (1, 32, 16) | equal to the model's: True
standardized values: from -2.45 to 2.57

Maximum context of 2,048 observations, patches of 16, a horizon of 64 steps and nine quantiles. Standardized, the log RV spans −2.45 to 2.57: the resolution problem of mean scaling disappears, because here there is no rounding (each patch enters with its real values) and the mean is subtracted first.

A first forecast

With the model loaded, forecasting is one call: it is given the context (the last 512 sessions) and the horizon. The chosen origin is Friday, March 6, 2020, the day before one of the shocks planted in the data (the README documents +1.8 in log-variance on March 9). The output is converted to annual volatility to make it easier to read:

IN [04]python
i0 = fechas.get_loc(pd.Timestamp("2020-03-06")) + 1
q, _ = bolt.predict_quantiles(
    inputs=torch.tensor(log_rv[i0 - 512:i0], dtype=torch.float32),
    prediction_length=5, quantile_levels=[0.1, 0.5, 0.9])
a_vol = lambda l: np.sqrt(252 * np.exp(l))       # log RV to annual vol.
tabla = pd.DataFrame(
    a_vol(q[0].numpy().astype(float)) * 100,
    index=fechas[i0:i0 + 5].date, columns=["q10", "q50", "q90"])
tabla["realized"] = vol_anual[i0:i0 + 5] * 100
tabla.round(1)
OUT [04]A forecast: five sessions from March 6, 2020
             q10   q50   q90  realized
2020-03-09  12.0  15.5  20.2      52.8
2020-03-10  12.0  16.0  21.4      28.6
2020-03-11  12.2  16.5  22.1      31.0
2020-03-12  12.4  16.9  22.6      24.9
2020-03-13  12.5  17.1  22.9      23.3

The model forecasts a median volatility of 15.5% for Monday, with a 10 to 90% band from 12.0 to 20.2%, and a slight rise over the following days. Monday's realized volatility is 52.8%: the shock was not in the past of the series and nobody could have forecast it from it. From Tuesday on the realized value (28.6%, 31.0%…) stays above the band, because this forecast is five days ahead from Friday and is not updated. The figure at the top shows what happens when the forecast is redone each day with the new data point: the band jumps after the shock and chases the realized value.

One-day zero-shot with a rolling origin

A single forecast evaluates nothing. The honest evaluation is that of lesson 5.4.3 without the retraining: for each session from 2018 to 2025, the next day is forecast with the previous 512 sessions and compared with what happened. The model is never fitted, so there is no possible leak through training; the only rule is that the context ends on the day before. The baselines are the naive forecast (yesterday's log RV) and the mean of the last 22 days.

IN [05]python
prueba = np.flatnonzero(fechas.year >= 2018)
C = 512

def zero_shot(serie, origenes, contexto=C, lote=256):
    """Quantiles 0.1…0.9 for tomorrow at each origin (no retraining)."""
    salida = []
    for k in range(0, len(origenes), lote):
        ventanas = np.stack([serie[i - contexto:i]
                             for i in origenes[k:k + lote]])
        q, _ = bolt.predict_quantiles(
            inputs=torch.tensor(ventanas, dtype=torch.float32),
            prediction_length=1)
        salida.append(q[:, 0, :].numpy().astype(float))
    return np.concatenate(salida)

q_bolt = zero_shot(log_rv, prueba)
real = log_rv[prueba]
previsiones = {
    "naive (yesterday)": log_rv[prueba - 1],
    "22-day mean": np.array([log_rv[i - 22:i].mean()
                             for i in prueba]),
    "Chronos-Bolt (median)": q_bolt[:, 4],
}

def metricas(f):
    e2 = (real - f) ** 2
    return {"MSE": e2.mean(), "R²": np.corrcoef(real, f)[0, 1] ** 2}

tabla = pd.DataFrame({k: metricas(f) for k, f in previsiones.items()})
tabla.T.round(3)
OUT [05]One-day zero-shot, 2018-2025, against two baselines
                         MSE     R²
naive (yesterday)      0.233  0.503
22-day mean            0.237  0.418
Chronos-Bolt (median)  0.181  0.551

Chronos-Bolt, without ever having seen the series, gives a mean squared error (MSE) of 0.181 against 0.233 for the naive forecast and 0.237 for the 22-day mean, and an R² of 0.551 (the R² is that of the regression of the log RV on the forecast, the same measure as in the README). To put it in context: the README publishes, for the same period, an R² of 0.57 for a HAR re-estimated every January and of 0.61 for the oracle, the exact expectation of the variance with the information of the day before. The foundation model sits between the naive baseline and the HAR, closer to the HAR. Whether that is enough is decided by the lab of lesson 5.8.5, with GARCH and LightGBM in the same test.

The forecast being compared is the median (the 0.5 quantile), not the mean. In log it makes no difference because the distribution is almost symmetric; in variance it would (the exponential of the median of the log understates the mean of the variance), and lesson 5.8.3 deals with it.

Is the improvement larger than the noise?

The error differences are read with the day-by-day paired difference, as in lesson 5.4.3: the hard days are hard for every model, and the difference removes that common noise.

IN [06]python
def pareada(a, b):
    """Mean difference of squared errors (a − b) and its t."""
    d = (real - previsiones[a]) ** 2 - (real - previsiones[b]) ** 2
    return d.mean(), d.mean() / (d.std(ddof=1) / np.sqrt(len(d)))

for ref in ("naive (yesterday)", "22-day mean"):
    dif, t = pareada("Chronos-Bolt (median)", ref)
    print(f"Bolt − {ref:<17} {dif:+.4f}  t = {t:+.1f}")
dentro = (real >= q_bolt[:, 0]) & (real <= q_bolt[:, 8])
print(f"coverage of the 10-90% interval: {dentro.mean():.1%} "
      f"({len(real)} days)")
OUT [06]Is the improvement larger than the noise?
Bolt − naive (yesterday) -0.0516  t = -9.8
Bolt − 22-day mean       -0.0557  t = -10.4
coverage of the 10-90% interval: 76.4% (2011 days)

Both improvements are far above the noise: t = −9.8 against the naive forecast and −10.4 against the 22-day mean. The test days overlap in the context but not in the error being measured (each day is forecast once), so the standard error of the difference is reasonable; if the errors had autocorrelation it would have to be corrected (Newey-West), but with a t of −10 a very strong autocorrelation would be needed to change the conclusion. The 10 to 90% interval covers 76.4% of the days, not 80%: the quantiles are somewhat narrow. Lesson 5.8.3 measures the calibration quantile by quantile and corrects it.

The result holds year by year:

IN [07]python
anio = fechas.year[prueba]
por_anio = pd.DataFrame({k: (real - f) ** 2 for k, f in
                         previsiones.items()}).groupby(anio).mean()
por_anio.round(3)
OUT [07]Error by year
       naive (yesterday)  22-day mean  Chronos-Bolt (median)
fecha
2018               0.202        0.163                  0.147
2019               0.229        0.194                  0.166
2020               0.288        0.285                  0.224
2021               0.214        0.301                  0.181
2022               0.247        0.261                  0.197
2023               0.201        0.216                  0.159
2024               0.233        0.224                  0.182
2025               0.249        0.251                  0.194
Fig. — Mean squared error of the one-day forecast of the synthetic index's log realized variance, by year: naive, 22-day mean and zero-shot Chronos-Bolt

Chronos-Bolt has the lowest error in all eight years. The worst is 2020 for all of them (the year of the March shocks). The 22-day mean suffers more in 2021: for weeks it drags along the days of extreme volatility of the previous year, which the foundation model discounts sooner.

What zero-shot does not do

A good zero-shot volatility forecast says nothing about other things, and it is worth being clear about that before going on:

The model doesThe model does not
Extract persistence, level and dispersion from the recent historyKnow the asset, the event calendar or the news
Give quantiles without training anythingGuarantee that those quantiles are calibrated (here, 76.4% against 80%)
Work on series with structure (volatility has plenty)Predict what has no structure: the returns of vol5 are unpredictable by construction
Save the work of estimating a model per seriesBeat the generating process: the oracle is the floor

Lesson 5.8.2 compares the families (Chronos, Chronos-Bolt, Chronos-2, TimesFM-2.5, Moirai-2.0, TiRex, TimeGPT and Kronos) and puts TimesFM-2.5 next to Chronos-Bolt; 5.8.3 goes into the practical decisions (context length, quantiles, covariates and fine-tuning).

Exercises

  1. Repeat the zero-shot evaluation with the cripto asset (use one origin out of every four to make it fast). Does Chronos-Bolt beat both baselines? Compare its R² with that of the README's HAR for that asset (0.40).
Solution
IN [08]python
vc = vol[vol["activo"] == "cripto"].set_index("fecha")
log_rv_c = np.log(vc["rv_5min"].to_numpy())
orig = prueba[::4]
q_c = zero_shot(log_rv_c, orig)
real_c = log_rv_c[orig]
for nombre, f in (("naive", log_rv_c[orig - 1]),
                  ("mean 22", np.array([log_rv_c[i - 22:i].mean()
                                        for i in orig])),
                  ("Bolt", q_c[:, 4])):
    print(f"{nombre:<9} MSE {np.mean((real_c - f) ** 2):.3f}  "
          f"R² {np.corrcoef(real_c, f)[0, 1] ** 2:.3f}")
OUT [08]Crypto
naive     MSE 0.652  R² 0.252
mean 22   MSE 0.427  R² 0.328
Bolt      MSE 0.395  R² 0.393

In crypto the naive forecast is much worse (MSE 0.652): the daily RV of an asset with frequent jumps is very noisy and yesterday's value is a poor forecast. Chronos-Bolt drops to 0.395, better than the 22-day mean (0.427), and its R² of 0.393 is almost that of the README's HAR (0.40), with the oracle at 0.46. With 503 origins the R² has an error of a few hundredths: the comparison with the HAR reads as “similar”, not as a ranking.

  1. Standardize the log RV context (subtract the mean and divide by the standard deviation) before tokenizing it with the Chronos recipe. How many bins does it use now? What does it tell you about the mean scaling of the original Chronos?
Solution
IN [09]python
x = log_rv[-512:]
z = (x - x.mean()) / x.std()
for nombre, serie in (("raw log RV", x), ("z-scored log RV", z)):
    ids, rec = tokens_a_mano(serie)
    print(f"{nombre:<18} distinct tokens {len(np.unique(ids)):4d}")
OUT [09]Tokens after standardizing
raw log RV         distinct tokens   46
z-scored log RV    distinct tokens  353

Standardized, the same information uses 353 bins instead of 46: almost eight times more resolution. The mean scaling of the original Chronos is designed for positive series close to their typical level (sales, demand, visits), where dividing by the mean is natural. For series far from zero or with a sign, the second generation (Chronos-Bolt, Chronos-2) switched to standardizing, and on top of that it does not quantize. If you use a model that quantizes, the prior transformation of the series is part of the model.

  1. A paper evaluates in 2025 a foundation model on daily returns of S&P 500 stocks from 2015 to 2023, and the model was pretrained on a corpus published in 2024 that includes public financial series. What problem does the evaluation have? Why do the course data not have it?
Solution

The risk is pretraining contamination: if the test series (or series highly correlated with them, such as other stocks of the same index on the same dates) were in the corpus, the model may have memorized part of what it later “forecasts”, and the evaluation is no longer out of sample. It is the same leak as in lesson 5.2.2, but hidden in someone else's weights. Serious authors control for it: the Chronos-2 model card says it excludes the test portions of the datasets that overlap with the GIFT-Eval benchmark, and the Moirai-2.0 one talks about “non-leaking” subsets. For your own evaluation, the safe option is to test with data after the corpus cutoff date or with data that cannot be in it, like the synthetic data of this course, generated after the models were published.

Each answer is folded under its question

Self-assessment

  1. What does it mean that a Chronos-Bolt forecast on vol5 is zero-shot?

    • AThat the model has no parameters
    • BThat the forecast has no error on the first day
    • CThat the model has not been trained or tuned on that series: the weights are those of the pretraining and the series only enters as context
    • DThat the model is retrained from scratch for each series
    Show answer

    Answer C. That the model has not been trained or tuned on that series: the weights are those of the pretraining and the series only enters as context

    The model learns once, with its corpus, and then receives the recent history of the series as context. Whatever it “learns” from the new series it does in the forward pass, without touching any weight.

  2. Chronos tokenizes the logarithm of the index's realized variance (values around −9.8 with a standard deviation of 0.6) with mean scaling and 4,093 uniform bins in [−15, 15]. What happens?

    • AThe scaled series is almost always around −1, so it only uses about 46 bins and the rounding reaches 0.035 in log (3.6% of the variance)
    • BNothing: mean scaling is invariant to any transformation
    • CIt goes out of the [−15, 15] range and the model fails
    • DIt uses all the bins equally
    Show answer

    Answer A. The scaled series is almost always around −1, so it only uses about 46 bins and the rounding reaches 0.035 in log (3.6% of the variance)

    Mean scaling divides by the mean of |x| but does not subtract the mean. In a series far from zero and with little relative variation, all the scaled values fall within a narrow band. Standardized (subtracting the mean), the same series uses 353 bins.

  3. Why is Chronos-Bolt much faster than the original Chronos of the same size?

    • ABecause it has fewer layers
    • BBecause it does not use attention
    • CBecause it only predicts the mean
    • DBecause it groups 16 observations into each token (512 data points are 33 tokens instead of 513) and predicts all the quantiles of the horizon at once, instead of sampling token by token
    Show answer

    Answer D. Because it groups 16 observations into each token (512 data points are 33 tokens instead of 513) and predicts all the quantiles of the horizon at once, instead of sampling token by token

    Fewer tokens make attention cheaper, since its cost grows with the square of their number, and the direct quantile output avoids generating many autoregressive trajectories. Amazon puts the gain at up to 250 times in speed.

  4. Zero-shot Chronos-Bolt gives an MSE of 0.181 in log RV against 0.233 for the naive forecast, with t = −9.8 in the paired difference. What can be concluded?

    • AThat Chronos-Bolt is the best possible volatility model
    • BThat it clearly beats the naive forecast on these data, but stays below the README's HAR (R² 0.551 against 0.57) and far from the oracle (0.61): it still has to be compared with models estimated on the series (lesson 5.8.5)
    • CThat it will also predict returns well
    • DNothing: 2,011 days are too few
    Show answer

    Answer B. That it clearly beats the naive forecast on these data, but stays below the README's HAR (R² 0.551 against 0.57) and far from the oracle (0.61): it still has to be compared with models estimated on the series (lesson 5.8.5)

    The naive baseline is easy to beat. The serious bar is a HAR, a GARCH or a LightGBM estimated on the series itself, and the floor is the oracle. And volatility has structure; the returns of vol5, by construction, do not.

  5. On March 9, 2020 the realized volatility of the synthetic index jumps to 52.8% and the 10-90% band forecast the day before goes from 12.0 to 20.2%. What does it indicate?

    • AThat the model is broken
    • BThat the model needs more training
    • CThat the shock is an innovation with no warning in the data: no model that only sees the past of the series could forecast it, and the band reacts afterwards
    • DThat the context was too short
    Show answer

    Answer C. That the shock is an innovation with no warning in the data: no model that only sees the past of the series could forecast it, and the band reacts afterwards

    The level README documents it as a +1.8 shock in log-variance on that day. A time series model, foundation or not, can only anticipate what the past contains; jumps are handled with risk management, not with forecasting.

Key points

  • A foundation model is pretrained once on a huge, varied corpus (Chronos: about 84 billion observations from 28 datasets, plus augmented and synthetic data) and used zero-shot: the new series enters as context, without touching the weights.
  • Chronos turns each data point into a token: it divides it by the mean of |x| and rounds it to one of 4,093 bins in [−15, 15]. In a series far from zero, like the log of the variance, that leaves few useful bins.
  • Chronos-Bolt and TimesFM standardize the series and cut it into patches (16 and 32 data points): 512 observations are 33 or 16 tokens, and attention costs much less.
  • On the index's log RV, 2018-2025, zero-shot Chronos-Bolt-small gives an MSE of 0.181 against 0.233 for the naive forecast (t = −9.8) and an R² of 0.551, below the README's HAR (0.57) and the oracle (0.61).
  • Its quantiles are useful but not exact: the 10-90% interval covers 76.4% of the days. Shocks, like the one on March 9, 2020, are not forecast.

Resources