Lesson 5.8.2 · 70 min

Foundation model families: architecture, data and license

In two years a dozen time series foundation models have come out of Amazon, Google, Salesforce, Nixtla, NX-AI and academic groups. They are more alike than their names suggest: almost all are transformers over patches that return quantiles. This lesson sorts them by architecture, pretraining data, size and license, with the figures from their papers and model cards, and has Chronos-Bolt and TimesFM-2.5 forecast the same thing; for the rest, the real code is left unexecuted.

Fig. — Size of the time-series foundation models in the lesson, according to their papers and model cards (the two used in the course, counted from the cached weights)
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

A map of the families

Lesson 5.8.1 explained what they have in common: pretrain once on a huge corpus and forecast zero-shot from a context that is standardized and cut into patches. What distinguishes each family is the transformer architecture, what data it saw, what it returns, how big it is and under what license it is released. The table summarizes what their papers and cards say (the Hugging Face cards and the READMEs of their repositories, consulted in October 2026):

ModelWho and whenArchitectureOutputSizeWeights license
ChronosAmazon, Mar. 2024encoder-decoder T5, one token per data pointsampled trajectories20-710 MApache-2.0
Chronos-BoltAmazon, Nov. 2024encoder-decoder T5, patches of 169 quantiles, 64 steps at once9-205 MApache-2.0
Chronos-2Amazon, Oct. 2025encoder-only T5, time and group attention21 quantiles, covariates120 MApache-2.0
TimesFM-2.5Google, Sep. 2025decoder-only, patches of 32mean and 9 quantiles200 MApache-2.0
Moirai-2.0Salesforce, Nov. 2025decoder-only, a single patch sizequantiles, several tokens per step11.4 M (small)CC-BY-NC-4.0
TiRexNX-AI, May 2025xLSTM (sLSTM), not a transformerquantiles35 MNXAI community
TimeGPT-1Nixtla, Oct. 2023encoder-decoder transformerpoint and conformal intervalsnot publishedclosed (paid API)
Kronosacademic, Aug. 2025decoder-only over tokenized OHLCV candlessampled future candles4.1-102.3 M openMIT

The header figure puts the sizes on a log scale. They range from 11 to 710 million parameters, tiny next to a language model, and the smallest in the figure, Moirai-2.0 small, is the one its authors champion: in their paper they train variants with 8 and 30 times more parameters and none of them beats it on the GIFT-Eval benchmark (“we do not observe scaling benefits”). With the pretraining data available, more parameters do not buy more accuracy.

The timeline tells another story, the speed of the field: almost every family releases a second version in under two years, and the latest TimesFM, version 3.0 from August 2026, changes the license of the weights.

Fig. — Timeline of the time-series foundation models in the lesson, by family (date of the first arXiv paper or of the repository announcement)
ChronosChronos-BoltChronos-2TimesFMTimesFM-2.5TimesFM 3.0MoiraiMoirai-2.0
Mermaid source
flowchart TB
  C[Chronos] --> B[Chronos-Bolt]
  B --> C2[Chronos-2]
  T1[TimesFM] --> T25[TimesFM-2.5]
  T25 --> T3[TimesFM 3.0]
  M1[Moirai] --> M2[Moirai-2.0]

Chronos: from token to patch

The original Chronos (Ansari et al., 2024) is the one lesson 5.8.1 took apart: mean scaling, 4,093 bins, a T5 that predicts the next token with cross-entropy, and sampled trajectories. It was pretrained on 28 public datasets (about 84 billion observations), 10 million series augmented with TSMixup (mixtures of series) and 1 million synthetic series from Gaussian processes (KernelSynth). Its weights are not in the course cache; the code, with the same library, would be this:

IN [01]python
from chronos import ChronosPipeline

chronos = ChronosPipeline.from_pretrained(
    "amazon/chronos-t5-small", device_map="cpu", dtype=torch.float32)
# 20 sampled trajectories: (series, samples, horizon)
muestras = chronos.predict(torch.tensor(series["indice"][-512:]),
                           prediction_length=5, num_samples=20)
q10, q50, q90 = np.quantile(muestras[0].numpy(), [0.1, 0.5, 0.9],
                            axis=0)

Chronos-Bolt (November 2024) swaps the token for the patch and sampling for direct quantiles. According to its card, it was trained on “nearly 100 billion time series observations” and is up to 250 times faster and 20 times more memory efficient than the original Chronos of the same size; the repository README adds 5 % less error. It is the model of lesson 5.8.1. To compare, first the data: the same as in the previous lesson, on two assets, with one origin every ten sessions so that TimesFM, which is slower, fits in the lesson's time.

IN [02]python
import numpy as np
import pandas as pd
import torch

torch.manual_seed(0)
torch.use_deterministic_algorithms(True)
vol = pd.read_parquet("vol5_diario.parquet")
series = {}
for a in ("indice", "cripto"):
    d = vol[vol["activo"] == a].set_index("fecha")
    series[a] = np.log(d["rv_5min"].to_numpy())
fechas = pd.DatetimeIndex(d.index)
C = 512
origenes = np.flatnonzero(fechas.year >= 2018)[::10]
print(f"{len(origenes)} origins per asset, from "
      f"{fechas[origenes[0]].date()} to {fechas[origenes[-1]].date()}")
OUT [02]Two series and 202 test origins per asset
202 origins per asset, from 2018-01-02 to 2025-12-31
IN [03]python
from chronos import BaseChronosPipeline

bolt = BaseChronosPipeline.from_pretrained(
    "amazon/chronos-bolt-small", device_map="cpu",
    dtype=torch.float32)
n_bolt = sum(p.numel() for p in bolt.model.parameters())
print(f"Chronos-Bolt-small: {n_bolt / 1e6:.1f} M parameters, "
      f"T5 with {bolt.model.config.num_layers} + "
      f"{bolt.model.config.num_decoder_layers} layers, "
      f"d_model {bolt.model.config.d_model}")

def prev_bolt(y):
    v = np.stack([y[i - C:i] for i in origenes])
    q, _ = bolt.predict_quantiles(
        inputs=torch.tensor(v, dtype=torch.float32),
        prediction_length=1)
    return q[:, 0, :].numpy().astype(float)     # quantiles 0.1…0.9

q_bolt = {a: prev_bolt(y) for a, y in series.items()}
OUT [03]Chronos-Bolt-small: size and forecasts
Chronos-Bolt-small: 47.7 M parameters, T5 with 6 + 6 layers, d_model 512

47.7 million parameters counted in the weights, the card's “48M”: a small T5 with six layers in the encoder and six in the decoder.

Chronos-2: covariates and group attention

Chronos-2 (October 2025) is the family's conceptual leap. According to its paper, it is an encoder-only transformer with 120 million parameters that alternates two kinds of attention: time attention, between patches of the same series, and group attention, between related series and covariates of the same example. With that it does, without retraining, univariate and multivariate forecasting and forecasting with past or known-future covariates (numerical or categorical); it reaches a context of 8,192 steps and a horizon of 1,024, and returns 21 quantiles (from 0.01 to 0.99). Its authors attribute those capabilities to pretraining on synthetic data that impose multivariate structures on univariate series. On their own benchmark (fev-bench) it comes first, ahead of TiRex and TimesFM-2.5.

The installed library (chronos-forecasting 2.3.2) ships Chronos2Pipeline, but the amazon/chronos-2 weights are not in the environment's cache, so its code is left unexecuted. The shape of the inputs is the one in its documentation (one dictionary per series); lesson 5.8.3 uses it with covariates:

IN [04]python
from chronos import Chronos2Pipeline

chronos2 = Chronos2Pipeline.from_pretrained("amazon/chronos-2",
                                            device_map="cpu")
entradas = [{"target": series[a][-2048:]} for a in series]
cuantiles, media = chronos2.predict_quantiles(
    entradas, prediction_length=1, quantile_levels=[0.1, 0.5, 0.9])
# a list: one tensor per series (variables, horizon, quantiles)

TimesFM-2.5: decoder-only

TimesFM (Das et al., 2024, Google) is a decoder-only transformer: it reads the context in patches of 32 points with causal attention, like a language model, and each output token predicts a block of 128 steps. It was pretrained on around 100 billion points from Google Trends, Wikipedia page views and synthetic series. Version 2.5 (September 2025) goes down from 500 to 200 million parameters, raises the context from 2,048 to 16,384 steps, adds an optional continuous quantile head of about 30 million parameters (up to 1,024 steps) and drops the frequency indicator the previous version required, according to the project README. Its card adds the GIFT-Eval pretraining set to the corpus. The weights in the cache let you check the figures:

IN [05]python
import timesfm

tfm = timesfm.TimesFM_2p5_200M_torch.from_pretrained(
    "google/timesfm-2.5-200m-pytorch", torch_compile=False)
for nombre, m in tfm.model.named_children():
    n = sum(p.numel() for p in m.parameters())
    print(f"{nombre:<28} {n / 1e6:6.1f} M")
n_tfm = sum(p.numel() for p in tfm.model.parameters())
cfg = tfm.model.config
print(f"{'total':<28} {n_tfm / 1e6:6.1f} M | max. context "
      f"{cfg.context_limit}, patch {cfg.input_patch_len}, output "
      f"{cfg.output_patch_len}, quantiles up to "
      f"{cfg.output_quantile_len}")
OUT [05]TimesFM-2.5: size by block and configuration
tokenizer                       1.8 M
stacked_xf                    196.7 M
output_projection_point         4.9 M
output_projection_quantiles    27.9 M
total                         231.3 M | max. context 16384, patch 32, output 128, quantiles up to 1024

The transformer (stacked_xf) and its input and point-output projections add up to about 203 million, the “200M” in the name; the continuous quantile head adds 27.9 million, the head of about 30 million in the README. In total, 231.3 million parameters.

Before forecasting you have to compile the configuration: the maximum context, the maximum horizon and several options. Two of them matter here. torch_compile=False avoids torch.compile, which on CPU needs a C compiler (README of the ml environment). And force_flip_invariance, on by default, runs the model on the series and on the sign-flipped series and combines the two; it guarantees a property that the equivariance section checks, at the cost of doubling the compute. The lesson turns it off for the comparison (in an earlier test, with it on the MSE changed in the third decimal place):

IN [06]python
def compilar(flip):
    tfm.compile(timesfm.ForecastConfig(
        max_context=C, max_horizon=128, normalize_inputs=True,
        use_continuous_quantile_head=True, fix_quantile_crossing=True,
        force_flip_invariance=flip, per_core_batch_size=64))

compilar(flip=False)

def prev_tfm(y):
    punto, q = tfm.forecast(horizon=1,
                            inputs=[y[i - C:i] for i in origenes])
    return punto[:, 0], q[:, 0, :]     # q: mean and deciles 0.1…0.9

r_tfm = {a: prev_tfm(y) for a, y in series.items()}
punto, q = r_tfm["indice"]
print("quantile columns:", q.shape[1],
      "| point = quantile 0.5?", np.allclose(punto, q[:, 5]))
OUT [06]TimesFM-2.5: forecasts with the same origins
quantile columns: 10 | point = quantile 0.5? True

The quantile output has ten columns, the mean and the deciles from 0.1 to 0.9, and the point forecast returned by forecast is the 0.5 quantile, the median. The two families therefore compare the same thing: medians. Another detail: per_core_batch_size is the batch size; by default it is 1 and TimesFM forecasts series by series.

Head to head on volatility

Same origins, same 512-session context, same target (tomorrow's log RV). Besides the squared error and the R², the table gives the mean pinball loss over the nine deciles (the scoring rule from lesson 5.7.3: it rewards the whole distribution, not just the median) and the coverage of the 10 to 90 % interval.

IN [07]python
tau = np.arange(1, 10) / 10

def pinball(y, q):
    u = y[:, None] - q
    return np.maximum(tau * u, (tau - 1) * u).mean()

filas = {}
for a, y in series.items():
    real = y[origenes]
    cand = {"naive": (y[origenes - 1], None),
            "Chronos-Bolt": (q_bolt[a][:, 4], q_bolt[a]),
            "TimesFM-2.5": (r_tfm[a][0], r_tfm[a][1][:, 1:])}
    for m, (f, qs) in cand.items():
        fila = {"MSE": np.mean((real - f) ** 2),
                "R²": np.corrcoef(real, f)[0, 1] ** 2}
        if qs is not None:
            fila["pinball"] = pinball(real, qs)
            fila["cov. 10-90"] = np.mean((real >= qs[:, 0])
                                         & (real <= qs[:, 8]))
        filas[(a, m)] = fila
tabla = pd.DataFrame(filas).T
tabla.round(3)
OUT [07]The two families against the naive forecast
                       MSE     R²  pinball  cov. 10-90
indice naive         0.241  0.511      NaN         NaN
       Chronos-Bolt  0.205  0.543    0.140       0.762
       TimesFM-2.5   0.210  0.532    0.141       0.782
cripto naive         0.621  0.303      NaN         NaN
       Chronos-Bolt  0.370  0.514    0.167       0.807
       TimesFM-2.5   0.378  0.505    0.169       0.787
Fig. — Mean squared error of zero-shot Chronos-Bolt-small and TimesFM-2.5 in the one-day log RV forecast, relative to the naive forecast, over 202 origins in 2018-2025

Both models beat the naive forecast and end up very close: on the index, MSE of 0.205 (Bolt) and 0.210 (TimesFM) versus 0.241; on crypto, 0.370 and 0.378 versus 0.621. The pinball loss is also almost the same (0.140 versus 0.141 on the index) and the coverage of the 10-90 % interval ranges from 76 to 81 %, around the nominal 80 %. The index figures do not match those of lesson 5.8.1 because here one origin in ten is used (202 days instead of 2,011). With so few days, the differences have to be measured:

IN [08]python
def pareada(y, f1, f2):
    real = y[origenes]
    d = (real - f1) ** 2 - (real - f2) ** 2
    t = d.mean() / d.std(ddof=1) * np.sqrt(len(d))
    return f"{d.mean():+.4f} (t = {t:+.1f})"

for a, y in series.items():
    fb, ft = q_bolt[a][:, 4], r_tfm[a][0]
    print(f"{a:<7} Bolt − naive {pareada(y, fb, y[origenes - 1])}  "
          f"TimesFM − naive {pareada(y, ft, y[origenes - 1])}  "
          f"TimesFM − Bolt {pareada(y, ft, fb)}")
OUT [08]Paired squared-error differences
indice  Bolt − naive -0.0366 (t = -2.0)  TimesFM − naive -0.0316 (t = -1.7)  TimesFM − Bolt +0.0050 (t = +1.0)
cripto  Bolt − naive -0.2503 (t = -2.6)  TimesFM − naive -0.2423 (t = -2.6)  TimesFM − Bolt +0.0080 (t = +1.2)

Against the naive forecast, t between −1.7 and −2.6 with 202 days (in lesson 5.8.1, with every day of the index, Bolt gave t = −9.8: the sample rules). Between the two models, t = 1.0 and 1.2: they cannot be told apart. A model with 47.7 million parameters and another with 231.3 million, from two companies, with different data and architectures, give almost the same forecast here. That is to be expected on a series with a simple structure (a HAR in logs): any competent model extracts almost all the information in the context, and the rest is noise nobody can forecast.

Equivariance: what the architecture guarantees

A useful property of a forecasting model: if you multiply the series by aa and add bb, the forecast should transform the same way, f(ax+b)=a f(x)+bf(ax + b) = a\,f(x) + b. If it does not, the result depends on the units (percent or fraction, dollars or euros). Both models standardize the context before processing it, so they satisfy it by construction for a>0a > 0. For a<0a < 0 (flipping the series) there is no guarantee: the standardized series changes shape.

IN [09]python
x = series["indice"][-C:]

def una_bolt(v):
    q, _ = bolt.predict_quantiles(
        inputs=torch.tensor(v, dtype=torch.float32),
        prediction_length=1)
    return q[0, 0, 4].item()

def una_tfm(v):
    return tfm.forecast(horizon=1, inputs=[v])[0][0, 0]

for nombre, f in (("Bolt", una_bolt), ("TimesFM, no flip", una_tfm)):
    base = f(x)
    e1 = abs(f(2 * x + 3) - (2 * base + 3))
    e2 = abs(f(-x) - (-base))
    print(f"{nombre:<18} |f(2x+3) − (2f(x)+3)| = {e1:.4f}   "
          f"|f(−x) − (−f(x))| = {e2:.4f}")
compilar(flip=True)
base = una_tfm(x)
print(f"{'TimesFM, with flip':<18} |f(−x) − (−f(x))| = "
      f"{abs(una_tfm(-x) + base):.4f}")
OUT [09]Equivariance: scaling, shifting and flipping the series
Bolt               |f(2x+3) − (2f(x)+3)| = 0.0000   |f(−x) − (−f(x))| = 0.0931
TimesFM, no flip   |f(2x+3) − (2f(x)+3)| = 0.0000   |f(−x) − (−f(x))| = 0.0982
TimesFM, with flip |f(−x) − (−f(x))| = 0.0000

Scaling by 2 and adding 3 changes nothing in either model. Flipping the series does: about 0.09 units of log RV in both, of the order of 10 % of the variance. Volatility has upward spikes and no downward ones; the inverted series has troughs in their place, and the model has no reason to treat it as the mirror image of the original. With force_flip_invariance=True, TimesFM averages f(x) and −f(−x), and the property holds exactly. For a signed series (returns, rate differences) that may be desirable; for volatility, which only spikes upward, it is not clear that it is, and it costs twice as much.

Moirai-2.0, TiRex, TimeGPT and Kronos

The other four families in the syllabus are not run in the course, each for its own reason (README of the ml environment). Their code is the one from their official documentation.

Moirai (Woo et al., 2024, Salesforce) was introduced as a masked encoder transformer with several patch sizes, “any-variate” attention (any number of variables in the same sequence) and a mixture-of-distributions output (Student's t, log-normal, negative binomial, low-variance normal), pretrained on LOTSA, more than 27 billion observations from nine domains. Moirai-2.0 (November 2025) simplifies everything: decoder-only, a single patch size, quantile loss and several output tokens per step, with a new corpus of 36 million series (about 295 billion observations). Its authors describe it as twice as fast and 30 times smaller than Moirai 1.0-Large, and better. Its library, uni2ts, requires numpy~=1.26 and torch<2.5, which do not exist for Python 3.14, and its weights are CC-BY-NC-4.0, for research only. The code from the README:

IN [10]python
from uni2ts.model.moirai2 import Moirai2Forecast, Moirai2Module

modelo = Moirai2Forecast(
    module=Moirai2Module.from_pretrained(
        "Salesforce/moirai-2.0-R-small"),
    prediction_length=100,
    context_length=1680,
    target_dim=1,
    feat_dynamic_real_dim=0,
    past_feat_dynamic_real_dim=0,
)
predictor = modelo.create_predictor(batch_size=32)
# datos_gluonts: entries of a GluonTS PandasDataset
previsiones = predictor.predict(datos_gluonts)

TiRex (Auer et al., 2025, NX-AI; NeurIPS 2025) is the architectural exception: it is not a transformer but an xLSTM (specifically, with sLSTM blocks), a modern recurrent network that, according to its authors, keeps the ability to track the state of the series, useful at long horizons. It has 35 million parameters, was trained on 47.5 million series (from Chronos, augmented with TSMixup, synthetic and part of the GIFT-Eval pretraining set) and in its paper it beats larger models on GIFT-Eval and on the Chronos benchmark. Its license is the NXAI community license: a company with more than 100 million euros in annual revenue that builds it into a commercial product or service needs a commercial license. It is installed with pip install tirex-ts; it is not in the course environment. The example from its README:

IN [11]python
from tirex import load_model, ForecastModel

modelo: ForecastModel = load_model("NX-AI/TiRex")
contexto = torch.tensor(np.stack([series[a][-512:] for a in series]))
cuantiles, media = modelo.forecast(context=contexto,
                                   prediction_length=1)

TimeGPT-1 (Garza, Challu and Mergenthaler-Canseco, 2023, Nixtla) was one of the first: an encoder-decoder transformer pretrained on “over 100 billion data points”. It is closed: there are no weights, it is used through a paid API with the nixtla SDK (Apache-2.0), and its intervals come from conformal prediction on the historical errors, not from the model. For a fund, the cost is not just the fee: the series travels to someone else's server. The signature is the one in the SDK reference (the key comes from an environment variable, never from the code):

IN [12]python
import os
from nixtla import NixtlaClient

cliente = NixtlaClient(api_key=os.environ["NIXTLA_API_KEY"])
df = pd.DataFrame({"unique_id": "indice", "ds": fechas,
                   "y": series["indice"]})
prevision = cliente.forecast(df=df, h=5, freq="B", level=[80])

Kronos (Shi et al., 2025; accepted at AAAI 2026) is the only one on the list that is specific to markets. It is pretrained on more than 12 billion candles (OHLCV records) from 45 markets and seven frequencies, and its tokenizer turns each multivariate candle into two hierarchical subtokens (one coarse and one fine) with binary spherical quantization; a decoder-only transformer predicts the next candles by sampling. There are open versions of 4.1 (mini), 24.7 (small) and 102.3 million parameters (base), with a context of 512 candles in small and base, and an unreleased large version of 499.2 million; MIT license. It is not a PyPI package (the kronos package on PyPI is something else): you clone the repository and import from its model folder. The example from its README, with the columns it expects:

IN [13]python
from model import Kronos, KronosTokenizer, KronosPredictor

tokenizer = KronosTokenizer.from_pretrained(
    "NeoQuasar/Kronos-Tokenizer-base")
modelo = Kronos.from_pretrained("NeoQuasar/Kronos-small")
predictor = KronosPredictor(modelo, tokenizer, max_context=512)

columnas = ["open", "high", "low", "close", "volume", "amount"]
x_df = velas.loc[:399, columnas]
pred_df = predictor.predict(
    df=x_df,
    x_timestamp=velas.loc[:399, "timestamps"],
    y_timestamp=velas.loc[400:519, "timestamps"],
    pred_len=120,
    T=1.0,          # sampling temperature
    top_p=0.9,      # nucleus sampling
    sample_count=1, # trajectories that are averaged
)

Without the device argument, the repository code picks CUDA, then MPS (Apple) and otherwise CPU.

What to look at when choosing

CriterionQuestionExample
Weights licenseDoes it allow the commercial use I have in mind?Moirai-2.0 no; TiRex with limits; TimesFM 3.0, non-commercial weights
Where it runsDo the data leave the building?TimeGPT: external API
InputsDo I need covariates or several series at once?Chronos-2 and TimesFM-2.5 (XReg, lesson 5.8.3)
OutputQuantiles, samples or just a point?Bolt and TimesFM: quantiles; Kronos: candle samples
CPU costHow many forecasts per day and on what machine?Bolt-small is several times faster than TimesFM-2.5 in this test
DependenciesDoes it install on my Python version?uni2ts requires numpy 1.26 and torch < 2.5
EvidenceDoes it win on my data, with an honest test?lessons 5.8.4 and 5.8.5

Exercises

  1. With the forecasts already computed, measure the quantile-by-quantile calibration of the two models on crypto: the fraction of days on which the realized value falls below each forecast quantile. Which one is closer to the diagonal?
Solution
IN [14]python
real = series["cripto"][origenes]
frec = pd.DataFrame({
    "nominal": tau,
    "Chronos-Bolt": (real[:, None] <= q_bolt["cripto"]).mean(0),
    "TimesFM-2.5": (real[:, None] <= r_tfm["cripto"][1][:, 1:]).mean(0),
})
print(frec.round(3).to_string(index=False))
print("mean absolute deviation:",
      (frec.iloc[:, 1:].sub(frec["nominal"], axis=0)).abs().mean()
      .round(3).to_dict())
OUT [14]Calibration by quantile on crypto
 nominal  Chronos-Bolt  TimesFM-2.5
     0.1         0.104        0.094
     0.2         0.168        0.173
     0.3         0.277        0.287
     0.4         0.386        0.391
     0.5         0.475        0.505
     0.6         0.559        0.569
     0.7         0.644        0.644
     0.8         0.767        0.752
     0.9         0.911        0.881
mean absolute deviation: {'Chronos-Bolt': 0.026, 'TimesFM-2.5': 0.024}

Both columns follow the diagonal with a mean deviation of 2.6 points (Bolt) and 2.4 (TimesFM). With 202 days, each frequency has a standard error of about 2-3 points (√(0.5·0.5/202) ≈ 0.035 in the middle), so differences of that size between the two models say nothing. Lesson 5.8.3 repeats it with every day and by volatility bucket, which is where the problems show up.

  1. TimesFM-2.5 accepts contexts of up to 16,384 steps. Repeat its index forecast with a context of 128 sessions (recompile with max_context=128). How much does the MSE worsen?
Solution
IN [15]python
tfm.compile(timesfm.ForecastConfig(
    max_context=128, max_horizon=128, normalize_inputs=True,
    use_continuous_quantile_head=True, fix_quantile_crossing=True,
    force_flip_invariance=False, per_core_batch_size=64))
y = series["indice"]
p128, _ = tfm.forecast(horizon=1,
                       inputs=[y[i - 128:i] for i in origenes])
real = y[origenes]
print(f"MSE with 512: {np.mean((real - r_tfm['indice'][0]) ** 2):.3f}"
      f"   with 128: {np.mean((real - p128[:, 0]) ** 2):.3f}")
print("paired difference:",
      pareada(y, p128[:, 0], r_tfm["indice"][0]))
OUT [15]TimesFM with 128 sessions of context
MSE with 512: 0.210   with 128: 0.213
paired difference: +0.0034 (t = +0.5)

With 128 sessions the MSE goes from 0.210 to 0.213 and the paired difference gives t = 0.5: it is not significant. On a series whose useful memory is a few weeks (the HAR uses 1-, 5- and 22-day averages), half a year of context already contains almost all the information for the next day. Lesson 5.8.3 sweeps the context length systematically.

  1. A fund with 500 million euros in revenue wants to build a foundation model into a forecasting product it will sell to clients, without sending data outside. Which models from the lesson would you rule out on license or deployment grounds, and which would remain?
Solution

I would rule out TimeGPT (external API: the data leave the building and the model cannot be deployed in-house), Moirai-2.0 (CC-BY-NC-4.0 weights, “research purposes only”) and TimesFM 3.0 (weights under a non-commercial license). TiRex is not ruled out outright, but the NXAI community license requires a commercial license from companies with more than 100 million euros in revenue that build it into a commercial product, which is the case here: it would have to be negotiated. That leaves Chronos-Bolt, Chronos-2 and TimesFM-2.5 (Apache-2.0 weights) and Kronos (MIT). The license is the necessary condition; the sufficient one is that the chosen model wins in an honest test on the fund's data, which is what lessons 5.8.4 and 5.8.5 do.

Each answer is folded under its question

Self-assessment

  1. What architectural difference is there between Chronos-Bolt and TimesFM-2.5?

    • AChronos-Bolt is a recurrent network and TimesFM a convolution
    • BChronos-Bolt is an encoder-decoder T5 that produces the quantiles of the whole horizon at once; TimesFM is a decoder-only transformer, with causal attention between patches, that generates output patches of 128 steps
    • CNone: they are the same model under another name
    • DTimesFM quantizes each data point into a token, like the original Chronos
    Show answer

    Answer B. Chronos-Bolt is an encoder-decoder T5 that produces the quantiles of the whole horizon at once; TimesFM is a decoder-only transformer, with causal attention between patches, that generates output patches of 128 steps

    Both work with patches (16 and 32 points), but Bolt encodes the context and decodes all the quantiles in one step, while TimesFM reads the patches with causal attention, like a language model, and predicts the next block.

  2. In the test on vol5 (202 origins per asset), TimesFM-2.5 has an MSE 0.005 higher than Chronos-Bolt on the index, with t = 1.0. What do you conclude?

    • AThat Chronos-Bolt is the better model
    • BThat TimesFM-2.5 is misconfigured
    • CThat you should use the model with more parameters
    • DThat with this sample they cannot be told apart: the difference is of the order of its standard error, and both beat the naive forecast by a similar margin
    Show answer

    Answer D. That with this sample they cannot be told apart: the difference is of the order of its standard error, and both beat the naive forecast by a similar margin

    A t of 1.0 is compatible with there being no difference. Ranking two models this similar would take a much larger sample (or more assets), and even then the conclusion would hold for these data.

  3. Moirai-2.0 is released under a CC-BY-NC-4.0 license and TiRex under the “NXAI community license”. What does that imply for a fund that wants to use them in production?

    • AMoirai-2.0 does not allow commercial use (“research purposes only” on its card) and TiRex requires a commercial license from companies with more than 100 million euros in revenue that build it into a commercial product; Chronos-Bolt, Chronos-2 and TimesFM-2.5 are Apache-2.0
    • BNothing: weights published on Hugging Face are always free to use
    • CThat you have to pay Hugging Face
    • DThat they can only be used on a GPU
    Show answer

    Answer A. Moirai-2.0 does not allow commercial use (“research purposes only” on its card) and TiRex requires a commercial license from companies with more than 100 million euros in revenue that build it into a commercial product; Chronos-Bolt, Chronos-2 and TimesFM-2.5 are Apache-2.0

    The license of the weights matters as much as that of the code. uni2ts (Moirai's code) is Apache-2.0, but the weights of version 2.0 are CC-BY-NC-4.0. And TimesFM 3.0, from August 2026, already releases its weights under a non-commercial license.

  4. The equivariance cell gives |f(2x+3) − (2f(x)+3)| = 0 for both models, but |f(−x) − (−f(x))| ≈ 0.09 without the force_flip_invariance option. Why?

    • ABecause the models have a programming bug
    • BBecause the series is negative
    • CBecause both standardize the context (subtract the mean and divide by the standard deviation), which guarantees equivariance for a > 0; flipping the series changes its shape (the asymmetry of the spikes), and the model has no reason to return the inverted forecast, unless it is forced to by averaging f(x) and −f(−x)
    • DBecause the horizon is one day
    Show answer

    Answer C. Because both standardize the context (subtract the mean and divide by the standard deviation), which guarantees equivariance for a > 0; flipping the series changes its shape (the asymmetry of the spikes), and the model has no reason to return the inverted forecast, unless it is forced to by averaging f(x) and −f(−x)

    With standardization, f(ax + b) = a·f(x) + b for any a > 0. With a < 0 the standardized series is a different one (volatility spikes become troughs). TimesFM enforces it with force_flip_invariance=True, which runs the model twice; that is why the lesson turns it off to measure.

  5. What sets Kronos apart from the other families?

    • AThat it does not use transformers
    • BThat it is the only one with a paid commercial license
    • CThat it only forecasts volatility
    • DThat it is pretrained on OHLCV candles (more than 12 billion records from 45 markets) and tokenizes each whole candle, instead of an arbitrary univariate series
    Show answer

    Answer D. That it is pretrained on OHLCV candles (more than 12 billion records from 45 markets) and tokenizes each whole candle, instead of an arbitrary univariate series

    Kronos is specific to financial markets: its tokenizer turns the multivariate candle (open, high, low, close, volume, amount) into hierarchical tokens. Being specific does not make it able to forecast returns: lesson 5.8.4 reviews the evidence.

Key points

  • Almost all families are transformers over patches that return quantiles: Chronos-Bolt (encoder-decoder T5, patches of 16), TimesFM-2.5 (decoder-only, patches of 32), Chronos-2 (encoder-only, with covariates), Moirai-2.0 (decoder-only). TiRex uses xLSTM and Kronos tokenizes OHLCV candles.
  • Size does not rank quality: Chronos-Bolt-small (47.7 M parameters counted) matches TimesFM-2.5 here (231.3 M in the file, 200 M according to the card), and the Moirai-2.0 authors find that their large versions perform worse than the small one.
  • On vol5, the two runnable models cut the naive MSE by 13-15 % on the index and by 39-40 % on crypto; between them, differences with t ≈ 1: they cannot be told apart.
  • Both are equivariant to positive scaling and shifting by construction; against flipping the series, only TimesFM with force_flip_invariance=True, which doubles the compute.
  • The license of the weights counts: Apache-2.0 for Chronos-Bolt, Chronos-2 and TimesFM-2.5; CC-BY-NC-4.0 for Moirai-2.0; community license with limits for TiRex; MIT for Kronos; paid API for TimeGPT (the data leave the building).

Resources