• bitcoinBitcoin(BTC)$64,164.00-0.50%
  • ethereumEthereum(ETH)$1,898.34-0.20%
  • tetherTether(USDT)$1.000.00%
  • binancecoinBNB(BNB)$591.08-0.50%
  • usd-coinUSDC(USDC)$1.000.00%
  • rippleXRP(XRP)$1.03-2.50%
  • solanaSolana(SOL)$72.38-1.90%
  • tronTRON(TRX)$0.326918-0.10%
  • Figure HelocFigure Heloc(FIGR_HELOC)$1.02-3.00%
  • HyperliquidHyperliquid(HYPE)$56.00-1.00%
  • dogecoinDogecoin(DOGE)$0.068856-1.50%
  • USDSUSDS(USDS)$1.000.00%
  • RainRain(RAIN)$0.012547-0.10%
  • leo-tokenLEO Token(LEO)$9.760.00%
  • zcashZcash(ZEC)$496.28-4.20%
  • cardanoCardano(ADA)$0.2003945.60%
  • moneroMonero(XMR)$368.721.00%
  • whitebitWhiteBIT Coin(WBT)$55.59-0.50%
  • chainlinkChainlink(LINK)$8.170.50%
  • stellarStellar(XLM)$0.160987-2.70%
  • daiDai(DAI)$1.000.00%
  • bitcoin-cashBitcoin Cash(BCH)$212.36-0.80%
  • USD1USD1(USD1)$1.000.00%
  • Ethena USDeEthena USDe(USDE)$1.000.00%
  • the-open-networkGram (prev. Toncoin)(GRAM)$1.37-2.10%
  • CantonCanton(CC)$0.091514-12.20%
  • litecoinLitecoin(LTC)$45.390.50%
  • Global DollarGlobal Dollar(USDG)$1.000.00%
  • Circle USYCCircle USYC(USYC)$1.130.00%
  • hedera-hashgraphHedera(HBAR)$0.068134-1.00%
  • avalanche-2Avalanche(AVAX)$6.42-2.90%
  • paypal-usdPayPal USD(PYUSD)$1.000.00%
  • shiba-inuShiba Inu(SHIB)$0.000005-4.50%
  • suiSui(SUI)$0.67-2.30%
  • BlackRock USD Institutional Digital Liquidity FundBlackRock USD Institutional Digital Liquidity Fund(BUIDL)$1.000.00%
  • tether-goldTether Gold(XAUT)$4,227.98-0.20%
  • crypto-com-chainCronos(CRO)$0.053330-1.30%
  • uniswapUniswap(UNI)$4.00-1.90%
  • nearNEAR Protocol(NEAR)$1.66-1.90%
  • Ondo US Dollar YieldOndo US Dollar Yield(USDY)$1.140.20%
  • pax-goldPAX Gold(PAXG)$4,240.28-0.20%
  • BittensorBittensor(TAO)$191.69-1.90%
  • okbOKB(OKB)$85.28-0.40%
  • OndoOndo(ONDO)$0.358035-3.80%
  • World Liberty FinancialWorld Liberty Financial(WLFI)$0.052642-1.70%
  • HTX DAOHTX DAO(HTX)$0.000002-0.20%
  • AsterAster(ASTER)$0.60-0.90%
  • Ripple USDRipple USD(RLUSD)$1.000.00%
  • usddUSDD(USDD)$1.000.10%
  • MemeCoreMemeCore(M)$1.14-6.90%
TradePoint.io
  • Main
  • AI & Technology
  • Stock Charts
  • Market & News
  • Business
  • Finance Tips
  • Trade Tube
  • Blog
  • Shop
No Result
View All Result
TradePoint.io
No Result
View All Result

End-to-End Forecasting with TimesFM 2.5: Backtesting, Covariates, Anomaly Detection, and Scalable Colab Deployment

August 2, 2026
in AI & Technology
Reading Time: 17 mins read
A A
End-to-End Forecasting with TimesFM 2.5: Backtesting, Covariates, Anomaly Detection, and Scalable Colab Deployment
ShareShareShareShareShare

In this tutorial, we build an advanced end-to-end time-series forecasting workflow with TimesFM 2.5. We begin by configuring the runtime, installing the required dependencies, detecting available hardware, and generating a realistic multi-store retail dataset with trend, seasonality, pricing, promotions, holidays, temperature effects, and random variation. We then load and compile the TimesFM 2.5 model, examine its forecast configuration, and use it for zero-shot point and probabilistic forecasting. As we progress, we evaluate forecast quality with metrics such as MAE, RMSE, sMAPE, MASE, pinball loss, and prediction-interval coverage, while also testing batched inference, rolling-origin backtesting, context-length sensitivity, covariate integration through XReg, anomaly detection, long-horizon forecasting, throughput tuning, and input robustness. By working through these stages, we develop a practical understanding of how we configure, validate, benchmark, and deploy TimesFM for realistic forecasting tasks.

FAST_MODE = False
SEED = 7
import subprocess, sys, os, time, json, math, warnings
warnings.filterwarnings("ignore")
def _pip(*args):
   subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", *args])
try:
   import timesfm
except ImportError:
   print("Installing timesfm[torch] ... (~1-2 min)")
   _pip("timesfm[torch]")
   import timesfm
import numpy as np
import pandas as pd
import torch
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
np.random.seed(SEED)
torch.manual_seed(SEED)
torch.set_float32_matmul_precision("high")
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
print("=" * 78)
print(f"timesfm  : {getattr(timesfm, '__version__', 'n/a')}")
print(f"torch    : {torch.__version__}")
print(f"device   : {DEVICE}")
if DEVICE == "cuda":
   print(f"gpu      : {torch.cuda.get_device_name(0)} "
         f"({torch.cuda.get_device_properties(0).total_memory/1e9:.1f} GB)")
print("=" * 78)
try:
   import jax
   from sklearn import preprocessing
   HAS_XREG_DEPS = True
except Exception as e:
   HAS_XREG_DEPS = False
   print(f"[warn] XReg deps missing ({e}); section 10 will be skipped.")
N_DAYS   = 1200
N_STORES = 6
REGIONS  = ["north", "north", "south", "south", "coast", "coast"]
dates = pd.date_range("2021-01-01", periods=N_DAYS, freq="D")
t     = np.arange(N_DAYS)
dow   = dates.dayofweek.values
doy   = dates.dayofyear.values
temp_base = 18 + 12 * np.sin(2 * np.pi * (doy - 105) / 365.25)
temp = temp_base + np.cumsum(np.random.normal(0, 0.6, N_DAYS)) * 0.15
temp = temp - np.linspace(0, temp[-1] - temp_base[-1], N_DAYS)
holiday_doy = {1, 2, 45, 100, 120, 185, 240, 300, 358, 359, 360, 361, 362, 363, 364, 365}
is_holiday = np.isin(doy, list(holiday_doy)).astype(int)
rows = []
for s in range(N_STORES):
   level      = 180 + 60 * s
   slope      = np.random.uniform(0.02, 0.09)
   week_amp   = np.random.uniform(15, 35)
   year_amp   = np.random.uniform(20, 45)
   elasticity = np.random.uniform(18, 32)
   promo_lift = np.random.uniform(35, 70)
   temp_beta  = np.random.uniform(0.8, 2.2)
   phase      = np.random.uniform(0, 2 * np.pi)
   base_price = np.random.uniform(9.0, 13.0)
   price = base_price + np.random.normal(0, 0.25, N_DAYS)
   promo = (np.random.rand(N_DAYS) < 0.09).astype(int)
   price = price - promo * np.random.uniform(1.2, 2.2)
   weekly = week_amp * np.array([0.9, 0.7, 0.7, 0.85, 1.25, 1.8, 1.5])[dow]
   yearly = year_amp * np.sin(2 * np.pi * doy / 365.25 + phase)
   sales = (level
            + slope * t
            + weekly
            + yearly
            - elasticity * (price - base_price)
            + promo_lift * promo
            + 55 * is_holiday
            + temp_beta * (temp - 18)
            + np.random.normal(0, 14, N_DAYS))
   sales = np.clip(sales, 5, None)
   rows.append(pd.DataFrame({
       "date": dates,
       "store": f"store_{s}",
       "region": REGIONS[s],
       "sales": sales.astype(np.float32),
       "price": price.astype(np.float32),
       "promo": promo.astype(np.int32),
       "holiday": is_holiday.astype(np.int32),
       "dow": dow.astype(np.int32),
       "temp": temp.astype(np.float32),
   }))
df = pd.concat(rows, ignore_index=True)
STORES = sorted(df["store"].unique())
print(f"\nDataset: {df.shape[0]:,} rows | {len(STORES)} stores | "
     f"{dates[0].date()} → {dates[-1].date()}")
print(df.head(3).to_string(index=False))
wide = df.pivot(index="date", columns="store", values="sales")
SEASON = 7
HORIZON = 56
print("\nLoading google/timesfm-2.5-200m-pytorch ...")
t0 = time.time()
model = timesfm.TimesFM_2p5_200M_torch.from_pretrained(
   "google/timesfm-2.5-200m-pytorch"
)
print(f"loaded in {time.time() - t0:.1f}s")
BASE_CFG = dict(
   max_context=1024,
   max_horizon=256,
   normalize_inputs=True,
   per_core_batch_size=16,
   use_continuous_quantile_head=True,
   force_flip_invariance=True,
   infer_is_positive=True,
   fix_quantile_crossing=True,
   return_backcast=False,
)
model.compile(timesfm.ForecastConfig(**BASE_CFG))
print("compiled:", {k: v for k, v in BASE_CFG.items() if k in
                   ("max_context", "max_horizon", "per_core_batch_size")})
def recompile(**overrides):
   cfg = {**BASE_CFG, **overrides}
   model.compile(timesfm.ForecastConfig(**cfg))
   return cfg

We configure the Google Colab environment, install TimesFM and its supporting libraries, detect the available CPU or GPU, and initialize reproducible random seeds. We generate a realistic multi-store retail dataset containing trends, weekly and yearly seasonality, pricing effects, promotions, holidays, temperature variations, and random demand noise. We then load the TimesFM 2.5 model, define its baseline forecast configuration, compile it, and create a reusable function for changing model settings in later experiments.

YOU MAY ALSO LIKE

OpenAI’s Ring-Shaped Smart Speaker Will Reportedly Cost Between $300 And $400

Cloudflare Introduces Kitesurf: An Agent-First Web Browser That Runs Entirely in V8 Isolates on Cloudflare Workers

IDX_MEAN, IDX_Q10, IDX_Q50, IDX_Q90 = 0, 1, 5, 9
QUANTILES = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]
target_store = STORES[0]
series = wide[target_store].values.astype(np.float32)
train, actual = series[:-HORIZON], series[-HORIZON:]
point, quant = model.forecast(horizon=HORIZON, inputs=[train.copy()])
print(f"\npoint    {point.shape}   # (n_series, horizon)")
print(f"quantile {quant.shape}   # (n_series, horizon, 10)")
fig, ax = plt.subplots(figsize=(14, 5))
hist_n = 180
ax.plot(dates[-HORIZON - hist_n:-HORIZON], train[-hist_n:], color="#334155",
       lw=1.2, label="history")
ax.plot(dates[-HORIZON:], actual, color="#0f172a", lw=1.6, label="actual")
ax.plot(dates[-HORIZON:], point[0], color="#ea580c", lw=2, label="TimesFM median")
for lo, hi, a in [(1, 9, .12), (2, 8, .16), (3, 7, .20), (4, 6, .24)]:
   ax.fill_between(dates[-HORIZON:], quant[0, :, lo], quant[0, :, hi],
                   color="#ea580c", alpha=a, lw=0)
ax.axvline(dates[-HORIZON], color="#94a3b8", ls="--", lw=1)
ax.set_title(f"TimesFM 2.5 zero-shot — {target_store}, {HORIZON}-day horizon "
            f"(fan = q10…q90)")
ax.legend(loc="upper left")
ax.xaxis.set_major_formatter(mdates.DateFormatter("%b %Y"))
plt.tight_layout()
plt.show()
print("\n--- output anatomy ---")
print("index 0 = MEAN (not q0!). indices 1..9 = q10..q90. index 5 = median.")
print("point_forecast is literally quantile[..., 5]:",
     np.allclose(point, quant[..., IDX_Q50]))
print("monotone quantiles (fix_quantile_crossing):",
     bool(np.all(np.diff(quant[0, :, 1:], axis=-1) >= -1e-4)))
row = pd.DataFrame({
   "index": range(10),
   "meaning": ["mean"] + [f"q{int(q*100)}" for q in QUANTILES],
   "day+1": quant[0, 0].round(1),
   f"day+{HORIZON}": quant[0, -1].round(1),
})
print(row.to_string(index=False))
print("Interval width grows with horizon — day+1 q10..q90 span "
     f"{quant[0,0,9]-quant[0,0,1]:.1f}, day+{HORIZON} span "
     f"{quant[0,-1,9]-quant[0,-1,1]:.1f}")
def seasonal_naive(history, horizon, season=SEASON):
   """Repeat the last full season forward — the baseline you must beat."""
   reps = int(np.ceil(horizon / season))
   return np.tile(history[-season:], reps)[:horizon]
def pinball(actual, q, quantiles=QUANTILES):
   """Mean pinball (quantile) loss over q10..q90 — the probabilistic metric."""
   out = []
   for i, tau in enumerate(quantiles, start=1):
       e = actual - q[:, i]
       out.append(np.mean(np.maximum(tau * e, (tau - 1) * e)))
   return float(np.mean(out))
def evaluate(actual, pred, history, q=None, season=SEASON):
   actual, pred = np.asarray(actual, float), np.asarray(pred, float)
   err = actual - pred
   scale = np.mean(np.abs(history[season:] - history[:-season])) + 1e-9
   m = {
       "MAE": float(np.mean(np.abs(err))),
       "RMSE": float(np.sqrt(np.mean(err ** 2))),
       "MAPE%": float(
           np.mean(np.abs(err / np.maximum(np.abs(actual), 1e-9))) * 100
       ),
       "sMAPE%": float(
           np.mean(
               2 * np.abs(err) /
               (np.abs(actual) + np.abs(pred) + 1e-9)
           ) * 100
       ),
       "MASE": float(np.mean(np.abs(err)) / scale),
   }
   if q is not None:
       m["pinball"] = pinball(actual, q)
       m["cov80%"] = float(
           np.mean(
               (actual >= q[:, IDX_Q10]) &
               (actual <= q[:, IDX_Q90])
           ) * 100
       )
   return m
base_pred = seasonal_naive(train, HORIZON)
cmp = pd.DataFrame({
   "TimesFM 2.5": evaluate(actual, point[0], train, quant[0]),
   "seasonal naive": evaluate(actual, base_pred, train),
   "last value": evaluate(
       actual,
       np.repeat(train[-1], HORIZON),
       train
   ),
}).T
print("\n--- single-series accuracy ---")
print(cmp.round(3).to_string())
print("MASE < 1 means you beat seasonal naive. cov80% should sit near 80.")
inputs = [
   wide[s].values[:-HORIZON].astype(np.float32).copy()
   for s in STORES
]
t0 = time.time()
b_point, b_quant = model.forecast(
   horizon=HORIZON,
   inputs=list(inputs)
)
elapsed = time.time() - t0
print(f"\n{len(STORES)} series forecast in {elapsed:.2f}s "
     f"({elapsed/len(STORES)*1000:.0f} ms/series)")
per_series = {}
for i, s in enumerate(STORES):
   hist = wide[s].values[:-HORIZON]
   act = wide[s].values[-HORIZON:]
   per_series[s] = evaluate(
       act,
       b_point[i],
       hist,
       b_quant[i]
   )
tbl = pd.DataFrame(per_series).T
print(tbl.round(3).to_string())
print("mean MASE:", round(tbl["MASE"].mean(), 3),
     "| mean 80% coverage:", round(tbl["cov80%"].mean(), 1))
fig, axes = plt.subplots(2, 3, figsize=(16, 7), sharex=True)
for ax, s, i in zip(
   axes.ravel(),
   STORES,
   range(len(STORES))
):
   hist = wide[s].values[:-HORIZON]
   ax.plot(
       dates[-HORIZON-90:-HORIZON],
       hist[-90:],
       color="#475569",
       lw=1
   )
   ax.plot(
       dates[-HORIZON:],
       wide[s].values[-HORIZON:],
       color="#0f172a",
       lw=1.4
   )
   ax.plot(
       dates[-HORIZON:],
       b_point[i],
       color="#ea580c",
       lw=1.8
   )
   ax.fill_between(
       dates[-HORIZON:],
       b_quant[i, :, IDX_Q10],
       b_quant[i, :, IDX_Q90],
       color="#ea580c",
       alpha=.18,
       lw=0
   )
   ax.set_title(
       f"{s}  MASE={per_series[s]['MASE']:.2f}",
       fontsize=10
   )
   ax.tick_params(labelsize=8)
fig.suptitle(
   "Batched zero-shot forecasts "
   "(orange = median, band = 80% PI)"
)
plt.tight_layout()
plt.show()

We generate our first zero-shot forecast and visualize the historical data, actual observations, median predictions, and probabilistic quantile bands in a fan chart. We inspect the structure of the point and quantile outputs, define evaluation functions for MAE, RMSE, MAPE, sMAPE, MASE, pinball loss, and prediction-interval coverage, and compare TimesFM against simple forecasting baselines. We also forecast all store series in a single batch, calculate store-level performance metrics, and plot the resulting forecasts and uncertainty intervals across the complete retail dataset.

N_FOLDS = 3 if FAST_MODE else 6
STEP = HORIZON
print(f"\n--- rolling-origin backtest: {N_FOLDS} folds x {HORIZON}d x "
     f"{len(STORES)} series ---")
fold_rows = []
for k in range(N_FOLDS):
   end = N_DAYS - k * STEP
   cut = end - HORIZON
   fold_inputs, fold_actuals, fold_hists = [], [], []
   for s in STORES:
       v = wide[s].values.astype(np.float32)
       fold_inputs.append(v[:cut].copy())
       fold_actuals.append(v[cut:end])
       fold_hists.append(v[:cut])
   fp, fq = model.forecast(
       horizon=HORIZON,
       inputs=list(fold_inputs)
   )
   for i, s in enumerate(STORES):
       m_tfm = evaluate(
           fold_actuals[i],
           fp[i],
           fold_hists[i],
           fq[i]
       )
       m_snv = evaluate(
           fold_actuals[i],
           seasonal_naive(fold_hists[i], HORIZON),
           fold_hists[i]
       )
       fold_rows.append({
           "fold": k,
           "store": s,
           "model": "TimesFM",
           **m_tfm
       })
       fold_rows.append({
           "fold": k,
           "store": s,
           "model": "snaive",
           **m_snv
       })
bt = pd.DataFrame(fold_rows)
summary = bt.groupby("model")[
   ["MAE", "RMSE", "sMAPE%", "MASE"]
].mean()
print(summary.round(3).to_string())
lift = (
   1 -
   summary.loc["TimesFM", "MASE"] /
   summary.loc["snaive", "MASE"]
)
print(
   f"TimesFM reduces MASE by "
   f"{lift*100:.1f}% vs seasonal naive, zero-shot."
)
fig, ax = plt.subplots(figsize=(10, 4))
piv = bt.pivot_table(
   index="fold",
   columns="model",
   values="MASE"
)
piv.plot(
   kind="bar",
   ax=ax,
   color=["#ea580c", "#94a3b8"],
   width=.8
)
ax.set_ylabel("MASE (lower is better)")
ax.axhline(1.0, color="k", ls=":", lw=1)
ax.set_title("Rolling-origin backtest — MASE per fold")
plt.tight_layout()
plt.show()
ctx_grid = (
   [128, 512]
   if FAST_MODE
   else [64, 128, 256, 512, 1024]
)
print(f"\n--- context ablation {ctx_grid} ---")
abl = []
for ctx in ctx_grid:
   recompile(max_context=ctx)
   ins = [
       wide[s].values[:-HORIZON][-ctx:]
       .astype(np.float32)
       .copy()
       for s in STORES
   ]
   t0 = time.time()
   p, q = model.forecast(
       horizon=HORIZON,
       inputs=list(ins)
   )
   dt = time.time() - t0
   ms = [
       evaluate(
           wide[s].values[-HORIZON:],
           p[i],
           wide[s].values[:-HORIZON],
           q[i]
       )
       for i, s in enumerate(STORES)
   ]
   abl.append({
       "context": ctx,
       "MASE": np.mean([m["MASE"] for m in ms]),
       "sMAPE%": np.mean([m["sMAPE%"] for m in ms]),
       "cov80%": np.mean([m["cov80%"] for m in ms]),
       "sec": round(dt, 2)
   })
abl = pd.DataFrame(abl)
print(abl.round(3).to_string(index=False))
fig, ax1 = plt.subplots(figsize=(9, 4))
ax1.plot(
   abl["context"],
   abl["MASE"],
   "o-",
   color="#ea580c"
)
ax1.set_xscale("log", base=2)
ax1.set_xlabel("context length (days)")
ax1.set_ylabel("MASE", color="#ea580c")
ax2 = ax1.twinx()
ax2.bar(
   abl["context"],
   abl["sec"],
   width=abl["context"] * 0.35,
   alpha=.25,
   color="#0369a1"
)
ax2.set_ylabel("wall-clock (s)", color="#0369a1")
ax1.set_title("Accuracy vs. context length vs. cost")
plt.tight_layout()
plt.show()
recompile()

We perform rolling-origin backtesting across multiple historical cutoffs to evaluate TimesFM under several realistic forecasting periods instead of relying on one holdout window. We compare the model with a seasonal-naive baseline, summarize the average error metrics, calculate the improvement in MASE, and visualize performance across backtest folds. We then run a context-length ablation study to determine how different amounts of historical data influence forecast accuracy, interval coverage, and inference time.

if HAS_XREG_DEPS:
   CTX_X, H_X = 512, 56
   recompile(
       max_context=CTX_X,
       max_horizon=128,
       return_backcast=True
   )
   x_inputs = []
   dyn_num = {"price": [], "temp": []}
   dyn_cat = {"promo": [], "holiday": [], "dow": []}
   stat_cat = {"region": [], "store": []}
   for s in STORES:
       g = df[df["store"] == s].reset_index(drop=True)
       n = len(g)
       ctx_lo, ctx_hi = n - H_X - CTX_X, n - H_X
       x_inputs.append(
           g["sales"]
           .values[ctx_lo:ctx_hi]
           .astype(np.float32)
       )
       for name, col in [
           ("price", "price"),
           ("temp", "temp")
       ]:
           dyn_num[name].append(
               g[col]
               .values[ctx_lo:n]
               .astype(np.float32)
           )
       for name, col in [
           ("promo", "promo"),
           ("holiday", "holiday"),
           ("dow", "dow")
       ]:
           dyn_cat[name].append(
               g[col]
               .values[ctx_lo:n]
               .astype(np.int32)
               .tolist()
           )
       stat_cat["region"].append(g["region"].iloc[0])
       stat_cat["store"].append(s)
   x_actuals = [
       wide[s].values[-H_X:]
       for s in STORES
   ]
   x_hists = [
       wide[s].values[:-H_X]
       for s in STORES
   ]
   print(
       f"\n--- XReg: inputs len={len(x_inputs[0])}, "
       f"covariate len={len(dyn_num['price'][0])} "
       f"=> inferred horizon "
       f"{len(dyn_num['price'][0]) - len(x_inputs[0])} ---"
   )
   xreg_results = {}
   for mode in ["xreg + timesfm", "timesfm + xreg"]:
       t0 = time.time()
       p_list, q_list = model.forecast_with_covariates(
           inputs=[a.copy() for a in x_inputs],
           dynamic_numerical_covariates=dyn_num,
           dynamic_categorical_covariates=dyn_cat,
           static_categorical_covariates=stat_cat,
           xreg_mode=mode,
           normalize_xreg_target_per_input=True,
           ridge=1.0,
           force_on_cpu=False,
       )
       dt = time.time() - t0
       ms = [
           evaluate(
               x_actuals[i],
               np.asarray(p_list[i]),
               x_hists[i],
               np.asarray(q_list[i])
           )
           for i in range(len(STORES))
       ]
       xreg_results[mode] = (
           np.asarray(p_list),
           np.asarray(q_list),
           ms,
           dt
       )
       print(
           f"{mode:>16}: "
           f"MASE={np.mean([m['MASE'] for m in ms]):.3f} "
           f"sMAPE={np.mean([m['sMAPE%'] for m in ms]):.2f}% "
           f"({dt:.1f}s)"
       )
   recompile(
       max_context=CTX_X,
       max_horizon=128,
       return_backcast=False
   )
   p_uni, q_uni = model.forecast(
       horizon=H_X,
       inputs=[a.copy() for a in x_inputs]
   )
   ms_uni = [
       evaluate(
           x_actuals[i],
           p_uni[i],
           x_hists[i],
           q_uni[i]
       )
       for i in range(len(STORES))
   ]
   print(
       f"{'univariate':>16}: "
       f"MASE={np.mean([m['MASE'] for m in ms_uni]):.3f} "
       f"sMAPE={np.mean([m['sMAPE%'] for m in ms_uni]):.2f}%"
   )
   comp = pd.DataFrame({
       "univariate": pd.DataFrame(ms_uni).mean(),
       "xreg + timesfm": pd.DataFrame(
           xreg_results["xreg + timesfm"][2]
       ).mean(),
       "timesfm + xreg": pd.DataFrame(
           xreg_results["timesfm + xreg"][2]
       ).mean(),
   }).T
   print("\n", comp.round(3).to_string())
   best_mode = min(
       xreg_results,
       key=lambda m: np.mean(
           [x["MASE"] for x in xreg_results[m][2]]
       )
   )
   p_best = xreg_results[best_mode][0]
   fig, axes = plt.subplots(
       2,
       2,
       figsize=(15, 7),
       sharex=True
   )
   for ax, i in zip(axes.ravel(), range(4)):
       ax.plot(
           dates[-H_X - 60:-H_X],
           x_hists[i][-60:],
           color="#475569",
           lw=1
       )
       ax.plot(
           dates[-H_X:],
           x_actuals[i],
           color="#0f172a",
           lw=1.5,
           label="actual"
       )
       ax.plot(
           dates[-H_X:],
           p_uni[i],
           color="#94a3b8",
           lw=1.6,
           ls="--",
           label="univariate"
       )
       ax.plot(
           dates[-H_X:],
           p_best[i],
           color="#ea580c",
           lw=1.8,
           label=f"{best_mode}"
       )
       pr = df[
           df["store"] == STORES[i]
       ]["promo"].values[-H_X:]
       for j in np.where(pr == 1)[0]:
           d = dates[-H_X + j]
           ax.axvspan(
               d - pd.Timedelta(hours=12),
               d + pd.Timedelta(hours=12),
               color="#16a34a",
               alpha=.25,
               lw=0
           )
       ax.set_title(STORES[i], fontsize=10)
       ax.tick_params(labelsize=8)
   axes[0, 0].legend(fontsize=8)
   fig.suptitle(
       "Covariate forecasting "
       "(green = promotion days known in advance)"
   )
   plt.tight_layout()
   plt.show()
else:
   print(
       "\n[skip] section 10 — "
       "install scikit-learn + jax to run XReg."
   )
recompile()

We incorporate numerical, categorical, and static covariates into the forecasting workflow through TimesFM’s XReg functionality. We compare the xreg + timesfm and timesfm + xreg fusion modes against a univariate forecast while using price, temperature, promotion, holiday, weekday, region, and store information. We evaluate the competing approaches, identify the best-performing covariate mode, and visualize how its predictions respond to known future promotion periods.

sig = wide[STORES[1]].values.astype(np.float32).copy()
truth_idx = [
   N_DAYS - 210,
   N_DAYS - 128,
   N_DAYS - 61,
   N_DAYS - 20
]
sig[truth_idx[0]] *= 1.9
sig[truth_idx[1]] *= 0.35
sig[truth_idx[2]:truth_idx[2] + 5] *= 1.5
sig[truth_idx[3]] *= 0.45
DET_WIN, DET_START = 14, N_DAYS - 280
flags = []
for start in range(
   DET_START,
   N_DAYS - DET_WIN + 1,
   DET_WIN
):
   ctx = sig[max(0, start - 512):start]
   p, q = model.forecast(
       horizon=DET_WIN,
       inputs=[ctx.copy()]
   )
   act = sig[start:start + DET_WIN]
   lo90 = q[0, :, IDX_Q10]
   hi90 = q[0, :, IDX_Q90]
   lo_ext = (
       q[0, :, IDX_Q10] -
       1.5 * (
           q[0, :, IDX_Q50] -
           q[0, :, IDX_Q10]
       )
   )
   hi_ext = (
       q[0, :, IDX_Q90] +
       1.5 * (
           q[0, :, IDX_Q90] -
           q[0, :, IDX_Q50]
       )
   )
   for j in range(DET_WIN):
       sev = (
           "CRITICAL"
           if (
               act[j] < lo_ext[j] or
               act[j] > hi_ext[j]
           )
           else "WARNING"
           if (
               act[j] < lo90[j] or
               act[j] > hi90[j]
           )
           else "ok"
       )
       flags.append({
           "i": start + j,
           "date": dates[start + j],
           "actual": act[j],
           "lo": lo90[j],
           "hi": hi90[j],
           "sev": sev,
           "z": (
               act[j] -
               q[0, j, IDX_Q50]
           ) / max(
               (hi90[j] - lo90[j]) / 2.563,
               1e-6
           )
       })
fl = pd.DataFrame(flags)
alerts = fl[fl.sev != "ok"]
print(
   f"\n--- anomaly detection: "
   f"{len(alerts)} flags in {len(fl)} points "
   f"({len(alerts)/len(fl)*100:.1f}% — "
   f"expect ~20% by construction) ---"
)
print(
   alerts[
       alerts.sev == "CRITICAL"
   ][
       ["date", "actual", "lo", "hi", "z", "sev"]
   ]
   .round(2)
   .to_string(index=False)
)
print(
   "injected anomalies at:",
   [str(dates[i].date()) for i in truth_idx]
)
fig, ax = plt.subplots(figsize=(14, 4.5))
ax.plot(
   fl["date"],
   fl["actual"],
   color="#0f172a",
   lw=1.1,
   label="observed"
)
ax.fill_between(
   fl["date"],
   fl["lo"],
   fl["hi"],
   color="#0369a1",
   alpha=.15,
   lw=0,
   label="80% predictive interval"
)
w = fl[fl.sev == "WARNING"]
c = fl[fl.sev == "CRITICAL"]
ax.scatter(
   w["date"],
   w["actual"],
   s=28,
   color="#f59e0b",
   zorder=3,
   label="warning"
)
ax.scatter(
   c["date"],
   c["actual"],
   s=60,
   color="#dc2626",
   zorder=4,
   marker="X",
   label="critical"
)
ax.set_title(
   "Anomaly detection from TimesFM predictive intervals"
)
ax.legend(loc="upper left", fontsize=8)
plt.tight_layout()
plt.show()
LONG_H = 256
long_train = (
   wide[STORES[2]]
   .values[:-LONG_H]
   .astype(np.float32)
)
long_act = wide[STORES[2]].values[-LONG_H:]
recompile(max_horizon=256)
p_direct, q_direct = model.forecast(
   horizon=LONG_H,
   inputs=[long_train.copy()]
)
recompile(max_horizon=128)
CHUNK = 64
ctx_roll = long_train.copy()
rec = []
for _ in range(LONG_H // CHUNK):
   p_c, _ = model.forecast(
       horizon=CHUNK,
       inputs=[ctx_roll[-1024:].copy()]
   )
   rec.append(p_c[0])
   ctx_roll = np.concatenate([
       ctx_roll,
       p_c[0]
   ])
p_rec = np.concatenate(rec)
print(f"\n--- long horizon ({LONG_H} days) ---")
print(
   pd.DataFrame({
       "direct": evaluate(
           long_act,
           p_direct[0],
           long_train,
           q_direct[0]
       ),
       f"recursive x{CHUNK}": evaluate(
           long_act,
           p_rec,
           long_train
       ),
   })
   .T
   .round(3)
   .to_string()
)
fig, ax = plt.subplots(figsize=(14, 4.5))
ax.plot(
   dates[-LONG_H-120:-LONG_H],
   long_train[-120:],
   color="#475569",
   lw=1
)
ax.plot(
   dates[-LONG_H:],
   long_act,
   color="#0f172a",
   lw=1.4,
   label="actual"
)
ax.plot(
   dates[-LONG_H:],
   p_direct[0],
   color="#ea580c",
   lw=1.8,
   label="direct"
)
ax.plot(
   dates[-LONG_H:],
   p_rec,
   color="#7c3aed",
   lw=1.5,
   ls="--",
   label=f"recursive ({CHUNK}-day chunks)"
)
ax.fill_between(
   dates[-LONG_H:],
   q_direct[0, :, IDX_Q10],
   q_direct[0, :, IDX_Q90],
   color="#ea580c",
   alpha=.15,
   lw=0
)
ax.legend()
ax.set_title(
   "Direct vs recursive long-horizon forecasting"
)
plt.tight_layout()
plt.show()
recompile()

We create an anomaly-detection workflow by comparing observed values against TimesFM’s predictive quantile intervals and assigning warning or critical severity levels to unusual observations. We inject spikes, outages, and sustained shifts into a retail series, calculate interval-based anomaly scores, and visualize the detected events alongside the expected forecasting range. We also compare direct and recursive long-horizon forecasting to examine how each strategy affects accuracy, uncertainty, and error accumulation over an extended prediction period.

many = [
   wide[STORES[i % len(STORES)]]
   .values[:-HORIZON]
   .astype(np.float32)
   .copy()
   for i in range(48)
]
print("\n--- throughput (48 series, 1024 context) ---")
bench = []
for bs in (
   [8, 32]
   if FAST_MODE
   else [1, 4, 16, 32, 48]
):
   recompile(per_core_batch_size=bs)
   model.forecast(
       horizon=32,
       inputs=[many[0].copy()]
   )
   t0 = time.time()
   model.forecast(
       horizon=HORIZON,
       inputs=list(many)
   )
   dt = time.time() - t0
   bench.append({
       "per_core_batch_size": bs,
       "sec": round(dt, 2),
       "series/sec": round(len(many) / dt, 1)
   })
bench = pd.DataFrame(bench)
print(bench.to_string(index=False))
print(
   "Tip: also try force_flip_invariance=False "
   "for a ~2x speedup when your series are "
   "strictly positive — it removes the second "
   "forward pass."
)
recompile()
print("\n--- robustness checks ---")
dirty = (
   wide[STORES[0]]
   .values[:-HORIZON]
   .astype(np.float32)
   .copy()
)
dirty[:20] = np.nan
dirty[400:415] = np.nan
p_nan, _ = model.forecast(
   horizon=HORIZON,
   inputs=[dirty.copy()]
)
print(
   f"(a) NaN input  -> finite output: "
   f"{np.isfinite(p_nan).all()}, "
   f"MASE "
   f"{evaluate(actual, p_nan[0], train)['MASE']:.3f} "
   f"(clean: "
   f"{evaluate(actual, point[0], train)['MASE']:.3f})"
)
def trim_trailing_nans(a):
   a = np.asarray(a, dtype=np.float32)
   return (
       a[:len(a) - np.argmax(~np.isnan(a[::-1]))]
       if np.isnan(a[-1])
       else a
   )
for L in [8, 32, 128]:
   ps, _ = model.forecast(
       horizon=14,
       inputs=[train[-L:].copy()]
   )
   print(
       f"(b) context={L:>4} -> 14d MASE "
       f"{evaluate("
       f"series[-HORIZON:-HORIZON+14], "
       f"ps[0], "
       f"train"
       f")['MASE']:.3f}"
   )
decay = np.clip(
   np.linspace(400, 8, 400) +
   np.random.normal(0, 6, 400),
   0,
   None
).astype(np.float32)
recompile(infer_is_positive=True)
p_clip, _ = model.forecast(
   horizon=64,
   inputs=[decay.copy()]
)
recompile(infer_is_positive=False)
p_free, _ = model.forecast(
   horizon=64,
   inputs=[decay.copy()]
)
print(
   f"(c) decaying series: "
   f"infer_is_positive=True "
   f"min={p_clip.min():+.1f} "
   f"(floored at 0) | "
   f"False min={p_free.min():+.1f}"
)
recompile()
probe = [
   train.copy(),
   train.copy(),
   train.copy()
]
before = len(probe)
model.forecast(
   horizon=16,
   inputs=probe
)
print(
   f"(d) list length before={before} "
   f"after={len(probe)} "
   f"<-- pass list(inputs)!"
)
r1, _ = model.forecast(
   horizon=32,
   inputs=[train.copy()]
)
r2, _ = model.forecast(
   horizon=32,
   inputs=[train.copy()]
)
print(
   f"(e) deterministic: "
   f"{np.allclose(r1, r2)}"
)
fc_dates = pd.date_range(
   dates[-1] + pd.Timedelta(days=1),
   periods=HORIZON,
   freq="D"
)
final_in = [
   wide[s]
   .values
   .astype(np.float32)
   .copy()
   for s in STORES
]
fp, fq = model.forecast(
   horizon=HORIZON,
   inputs=list(final_in)
)
out = []
for i, s in enumerate(STORES):
   out.append(pd.DataFrame({
       "date": fc_dates,
       "store": s,
       "forecast": fp[i],
       "q10": fq[i, :, IDX_Q10],
       "q25": fq[i, :, 3],
       "q50": fq[i, :, IDX_Q50],
       "q75": fq[i, :, 7],
       "q90": fq[i, :, IDX_Q90],
       "mean": fq[i, :, IDX_MEAN],
   }))
out = pd.concat(out, ignore_index=True)
out.to_csv(
   "timesfm_forecasts.csv",
   index=False
)
with open("timesfm_backtest.json", "w") as f:
   json.dump({
       "backtest": summary.round(4).to_dict(),
       "context_ablation": abl.round(4).to_dict(
           orient="records"
       ),
       "throughput": bench.to_dict(
           orient="records"
       )
   }, f, indent=2)
print(
   "\nWrote timesfm_forecasts.csv "
   "and timesfm_backtest.json"
)
print(
   out.head(4)
   .round(1)
   .to_string(index=False)
)
print("""
============================ USE IT ON YOUR OWN CSV ==========================
df   = pd.read_csv("mydata.csv", parse_dates=["date"]).sort_values("date")
vals = df["value"].astype("float32").to_numpy()          # 1-D, evenly spaced!
model = timesfm.TimesFM_2p5_200M_torch.from_pretrained(
           "google/timesfm-2.5-200m-pytorch")
model.compile(timesfm.ForecastConfig(
   max_context=1024, max_horizon=128, normalize_inputs=True,
   per_core_batch_size=32, use_continuous_quantile_head=True,
   fix_quantile_crossing=True, infer_is_positive=True))
point, quant = model.forecast(horizon=30, inputs=[vals])
lower, median, upper = quant[0,:,1], point[0], quant[0,:,9]
CHECKLIST
 [ ] regular spacing, one row per period, gaps as NaN (not dropped rows)
 [ ] trim trailing NaNs yourself
 [ ] max_context multiple of 32; max_horizon multiple of 128
 [ ] max_context + max_horizon <= 16384; horizon <= 1024 w/ quantile head
 [ ] quantile index 0 = MEAN, 1..9 = q10..q90, 5 = median
 [ ] infer_is_positive=False for anything that can go negative
 [ ] return_backcast=True only for forecast_with_covariates()
 [ ] dynamic covariates must cover context + horizon
 [ ] pass list(inputs) — forecast() mutates the list it is given
 [ ] backtest with rolling origins and compare against seasonal naive
=============================================================================""")

We benchmark forecasting throughput across different per-core batch sizes and measure how many time series we process per second. We test model robustness with leading and interior missing values, very short histories, positive-value clipping, mutable input lists, and deterministic repeated inference. We finally generate future forecasts for every store, export the results and experiment summaries to CSV and JSON files, and provide a reusable template for applying TimesFM 2.5 to our own time-series dataset.

In conclusion, we completed a comprehensive TimesFM 2.5 forecasting pipeline that extends well beyond generating a basic prediction. We used probabilistic quantiles to measure uncertainty, compare the model against seasonal-naive and last-value baselines, and apply rolling-origin backtesting to obtain a more reliable view of real-world performance. We also explored how context length, batch size, exogenous covariates, compilation flags, and direct or recursive forecasting strategies influence accuracy and computational cost. Through anomaly detection and robustness tests, we examined how the model behaves with missing values, short histories, positive-value constraints, mutable inputs, and long forecast horizons. Finally, we exported forecast, backtest, ablation, and throughput results into reusable files and provided a template for applying the workflow to our own evenly spaced time-series data. We finished with a structured and production-oriented foundation for adapting TimesFM 2.5 to demand forecasting, operational planning, anomaly monitoring, and other large-scale forecasting applications.


Check out the Full Codes here. Also, feel free to follow us on Twitter and don’t forget to join our 150k+ML SubReddit and Subscribe to our Newsletter. Wait! are you on telegram? now you can join us on telegram as well.

Need to partner with us for promoting your GitHub Repo OR Hugging Face Page OR Product Release OR Webinar etc.? Connect with us


Sana Hassan, a consulting intern at Marktechpost and dual-degree student at IIT Madras, is passionate about applying technology and AI to address real-world challenges. With a keen interest in solving practical problems, he brings a fresh perspective to the intersection of AI and real-life solutions.

Credit: Source link

ShareTweetSendSharePin

Related Posts

OpenAI’s Ring-Shaped Smart Speaker Will Reportedly Cost Between 0 And 0
AI & Technology

OpenAI’s Ring-Shaped Smart Speaker Will Reportedly Cost Between $300 And $400

August 6, 2026
Cloudflare Introduces Kitesurf: An Agent-First Web Browser That Runs Entirely in V8 Isolates on Cloudflare Workers
AI & Technology

Cloudflare Introduces Kitesurf: An Agent-First Web Browser That Runs Entirely in V8 Isolates on Cloudflare Workers

August 6, 2026
Suno Is Adding Audio Watermarks So AI-Generated Songs Are More Easily Identifiable
AI & Technology

Suno Is Adding Audio Watermarks So AI-Generated Songs Are More Easily Identifiable

August 6, 2026
OpenAI Gives Free ChatGPT Users Unlimited Text Chats on GPT-5.6 Luna – Unite.AI
AI & Technology

OpenAI Gives Free ChatGPT Users Unlimited Text Chats on GPT-5.6 Luna – Unite.AI

August 6, 2026
Next Post
NVIDIA AI Releases Molt: A PyTorch-Native Agentic Reinforcement Learning Framework

NVIDIA AI Releases Molt: A PyTorch-Native Agentic Reinforcement Learning Framework

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Search

No Result
View All Result
From Jefferson’s desk to FDR’s microphone: Tour the Smithsonian with Lonnie Bunch

From Jefferson’s desk to FDR’s microphone: Tour the Smithsonian with Lonnie Bunch

August 5, 2026
Apple stock sinks 10% after Tim Cook warns of ‘100-year flood’ in memory prices

Apple stock sinks 10% after Tim Cook warns of ‘100-year flood’ in memory prices

July 31, 2026
Hercules Capital: The Software Panic Was Overblown

Hercules Capital: The Software Panic Was Overblown

August 3, 2026

About

Learn more

Our Services

Legal

Privacy Policy

Terms of Use

Bloggers

Learn more

Article Links

Contact

Advertise

Ask us anything

©2020- TradePoint.io - All rights reserved!

Tradepoint.io, being just a publishing and technology platform, is not a registered broker-dealer or investment adviser. So we do not provide investment advice. Rather, brokerage services are provided to clients of Tradepoint.io by independent SEC-registered broker-dealers and members of FINRA/SIPC. Every form of investing carries some risk and past performance is not a guarantee of future results. “Tradepoint.io“, “Instant Investing” and “My Trading Tools” are registered trademarks of Apperbuild, LLC.

This website is operated by Apperbuild, LLC. We have no link to any brokerage firm and we do not provide investment advice. Every information and resource we provide is solely for the education of our readers. © 2020 Apperbuild, LLC. All rights reserved.

No Result
View All Result
  • Main
  • AI & Technology
  • Stock Charts
  • Market & News
  • Business
  • Finance Tips
  • Trade Tube
  • Blog
  • Shop

© 2023 - TradePoint.io - All Rights Reserved!