Aller au contenu principal

Séries temporelles et évaluation à origine glissante

Prévoir les ventes du prochain trimestre demande de reconstituer les données accessibles au moment de chaque prévision. Séparer en deux une table historique téléchargée aujourd’hui peut encore introduire des révisions ultérieures dans le passé. L’évaluation doit préciser à la fois ce que l’on pouvait savoir et jusqu’à quel horizon il fallait prévoir.

Découpage des données et fuites définit la frontière générale de l’information ; Dates et séries temporelles avec pandas traite de l’analyse des dates, du rééchantillonnage et des fenêtres. Ces frontières se traduisent ici par une succession d’origines de prévision, de modèles de référence et de calculs d’erreur.

Distinguer observation, publication et millésime​

Le guide d’ALFRED distingue les périodes d’observation des millésimes historiques : les données économiques sont révisées, et ALFRED conserve les versions accessibles à des dates passées. Les nouvelles valeurs et révisions y sont généralement ajoutées dans le jour ouvré suivant leur publication ; une date de publication manquante peut aussi être remplacée par une autre date. Une prévision intrajournalière exige donc de vérifier l’heure réelle de publication et celle de réception par le système.

Date ou versionQuestion poséeExemple fictif de ventes mensuelles
Période d’observationQuand l’activité a-t-elle eu lieu ?Janvier 2026
Date de publicationQuand la valeur est-elle devenue publique ?Première valeur de 100 publiée le 10 février
Millésime des données (vintage)Quelle estimation était visible à une date passée ?Valeur de janvier révisée à 104 le 10 mars
Réception par le systèmeQuand le système a-t-il réellement obtenu la valeur ?Peut suivre la publication publique

À l’origine du 28 février, la valeur utilisable de janvier est 100. Supposons que les ventes de février, égales à 90, ne soient publiées que le 12 mars : elles sont également indisponibles. Ce programme utilise date.fromisoformat, dans la bibliothèque standard de Python, pour lire les dates de publication. Il filtre selon la date limite, puis retient la dernière valeur admissible de chaque période. La date limite désigne la fin du jour indiqué. L’exemple suppose une réception immédiate et au plus une publication par période d’observation et par jour ; plusieurs versions le même jour exigent des horodatages ou un ordre de publication explicite.

from datetime import date

rows = [
("2026-01", "2026-02-10", 100),
("2026-01", "2026-03-10", 104),
("2026-02", "2026-03-12", 90),
]
cutoff = date(2026, 2, 28)
latest = {}
dated_rows = [(period, date.fromisoformat(released), value)
for period, released, value in rows]
for period, released, value in sorted(dated_rows, key=lambda row: row[1]):
if released <= cutoff:
latest[period] = value
print("as_of", cutoff.isoformat(), latest)
assert latest == {"2026-01": 100}

Sortie :

as_of 2026-02-28 {'2026-01': 100}

Conserver l’historique des publications permet de filtrer selon la disponibilité avant de choisir un millésime. Prendre les dernières valeurs connues aujourd’hui, puis filtrer selon la période d’observation, inverse cet ordre. Les cibles d’entraînement doivent elles aussi être disponibles à la date limite d’entraînement. Il faut décider à l’avance si l’évaluation porte sur la première publication ou sur une révision à une date définie. Le laboratoire de scénarios macroéconomiques conserve déjà ces contraintes au moyen de dates limites d’information.

Examiner la structure avant de différencier​

StructureSensQuestion pour le modèle
TendanceHausse ou baisse durable du niveauExtrapoler le niveau ou prévoir ses variations ?
SaisonnalitéRépétition selon une période calendaire fixeFaut-il prendre m=4m=4 pour une série trimestrielle ?
Dépendance temporelleLes valeurs à des dates différentes sont liéesLe passé aide-t-il à prévoir, et les erreurs sont-elles liées aussi ?
StationnaritéLes propriétés statistiques restent stables dans le tempsLes relations historiques décrivent-elles la période d’entraînement actuelle ?

Le chapitre de FPP sur la stationnarité et la différenciation explique comment les différences peuvent atténuer tendance et saisonnalité. En présence d’une forte saisonnalité, essayer d’abord une différence saisonnière et éviter les différences supplémentaires inutiles. Les notes de cours de Berkeley sur les séries temporelles définissent la stationnarité faible : une moyenne et une variance finie constantes, avec une covariance qui dépend seulement du décalage. Elle autorise la dépendance temporelle.

La différence première est Δyt=yt−yt−1\Delta y_t=y_t-y_{t-1} ; la différence saisonnière est Δmyt=yt−yt−m\Delta_m y_t=y_t-y_{t-m}. Dans les ventes fictives ci-dessous, y1=100y_1=100, y2=120y_2=120 et y5=110y_5=110, d’où Δy2=20\Delta y_2=20 et Δ4y5=10\Delta_4 y_5=10. Ces calculs répondent à deux questions distinctes : la variation depuis le trimestre précédent et celle depuis le même trimestre de l’année précédente. Des modèles comme Holt–Winters traitent directement tendance et saisonnalité, sans différenciation préalable. Une stationnarité apparente dans l’entraînement ne garantit pas que les relations persisteront dans la période suivante.

Construire des références avec les retards disponibles​

Les méthodes simples de prévision de FPP fournissent deux références utiles : la prévision naïve répète la dernière observation ; la prévision naïve saisonnière reprend la dernière observation de la saison correspondante. Notons tt l’origine de prévision et hh l’horizon, en supposant les observations jusqu’à tt déjà disponibles. La référence saisonnière exige aussi un cycle complet, soit t≥mt\ge m :

y^t+h∣tnaive=yt,y^t+h∣tseasonal=yt+h−m,1≤h≤m.\hat y_{t+h\mid t}^{\mathrm{naive}}=y_t,\qquad \hat y_{t+h\mid t}^{\mathrm{seasonal}}=y_{t+h-m},\quad 1\le h\le m.

Au-delà d’un cycle saisonnier, la prévision naïve saisonnière continue de répéter le dernier cycle complet ; elle ne doit pas lire les observations futures. Ici, les données sont trimestrielles, m=4m=4 et h=1,2h=1,2.

Les variables explicatives peuvent inclure les ventes passées, la moyenne des quatre derniers trimestres et les informations calendaires du trimestre prévu. Si une ligne d’entraînement est indexée par la date de sa cible t+ht+h, les ventes retardées de kk périodes sont yt+h−ky_{t+h-k}. Avec une publication immédiate, elles restent dans les limites de l’origine uniquement si k≥hk\ge h. Avec un délai de publication, il faut également vérifier leur disponibilité ; décaler une colonne d’une ligne ne suffit pas.

Pour chaque ligne historique, reconstruire les entrées accessibles à son heure initiale de prévision ; à l’origine actuelle, ajuster le modèle uniquement sur des cibles déjà publiées. Imputation par la moyenne, standardisation, sélection des variables et choix de différenciation doivent être ajustés dans cet intervalle d’entraînement. Le chapitre de FPP sur la prévision par régression distingue les entrées connues à l’avance de celles observées plus tard : le calendrier futur ou un planning déjà connu peuvent servir d’entrées, tandis que les futurs taux de change réalisés, les observations météorologiques et les révisions publiées après la date limite ne peuvent pas être traités comme connus. Les révisions déjà publiées à cette date sont utilisables. Pour retrouver les niveaux, ajouter une prévision de différence première au niveau précédent, ou une prévision de différence saisonnière au niveau situé mm périodes plus tôt. Partir de niveaux connus à l’origine ou avant ; si le calcul exige un niveau postérieur à l’origine, utiliser une prévision déjà produite. Comparer ensuite les niveaux reconstitués aux références en niveau.

Faire avancer l’origine de prévision​

La validation croisée temporelle de FPP construit chaque prévision à partir d’observations antérieures au point testé et permet l’évaluation à plusieurs horizons. Il faut y ajouter les contraintes de publication définies plus haut. Réserver d’abord la dernière période, puis comparer les modèles aux origines précédentes. Les observations de validation passées peuvent entrer dans les entraînements suivants une fois disponibles.

Prenons ces ventes trimestrielles fictives, exprimées dans la même unité, publiées immédiatement à la clôture de chaque trimestre et jamais révisées :

TrimestreQ1Q2Q3Q4Q5Q6Q7Q8Q9Q10Q11Q12Q13Q14
Ventes1001209013011013299143121145109157133160
OrigineIntervalle d’entraînement disponibleCible à un pasCible à deux pasRôle
Fin de Q8Q1–Q8Q9Q10Validation
Fin de Q9Q1–Q9Q10Q11Validation
Fin de Q10Q1–Q10Q11Q12Validation
Fin de Q12Q1–Q12Q13Q14Test final après verrouillage du choix du modèle

Cette fenêtre croissante conserve tout l’historique disponible. Une fenêtre glissante de taille fixe ne garde que les WW observations les plus récentes. Choisir WW, la longueur minimale d’entraînement et la fréquence de réajustement pendant la validation. L’exemple commence avec deux cycles saisonniers complets ; ses origines de validation permettent toutes d’évaluer les deux horizons sans atteindre Q13.

Aux deux références s’ajoute un candidat ajustable, seasonal_drift. Il ajoute à la prévision naïve saisonnière la moyenne des variations annuelles entre trimestres correspondants, dtd_t, calculée sur l’entraînement. Il suppose un même accroissement annuel absolu pour tous les trimestres et sert ici uniquement aux prévisions à un et deux pas. La fonction fit ci-dessous prépare l’état des trois candidats. Elle exige des valeurs trimestrielles complètes, finies, régulièrement espacées et classées dans l’ordre temporel, avec t>mt>m pour calculer au moins une variation annuelle. forecast accepte seulement les noms de modèles indiqués et des horizons entiers 1≤h≤m1\le h\le m.

dt=1t−m∑i=m+1t(yi−yi−m),t>m,y^t+h∣tseasonal_drift=yt+h−m+dt,1≤h≤m.d_t=\frac{1}{t-m}\sum_{i=m+1}^{t}(y_i-y_{i-m}),\quad t>m,\qquad \hat y_{t+h\mid t}^{\mathrm{seasonal\_drift}}=y_{t+h-m}+d_t,\quad 1\le h\le m.

À Q8, les variations annuelles sont 10, 12, 9 et 13, soit une moyenne de 11. On prévoit donc 110+11=121110+11=121 pour Q9 et 132+11=143132+11=143 pour Q10. Le réajustement à Q10 donne d10=68/6≈11.333d_{10}=68/6\approx11.333. Le code réajuste à chaque origine et ne lit les cibles qu’après avoir produit les prévisions, pour calculer les erreurs. La sélection repose sur la MAE, avec un poids égal pour les deux horizons.

from math import sqrt
from statistics import mean

# Synthetic quarterly values; Q13-Q14 are reserved for the final test.
y = [100, 120, 90, 130, 110, 132, 99, 143,
121, 145, 109, 157, 133, 160]
m, H, dev_end = 4, 2, 12
names = ("naive", "seasonal", "seasonal_drift")

def fit(train):
if len(train) <= m:
raise ValueError("fit requires more than m observations")
change = mean(train[i] - train[i-m] for i in range(m, len(train)))
return train[-1], train[-m:], change

def forecast(state, name, h):
last, season, change = state
if name not in names:
raise ValueError("unknown model name")
if not isinstance(h, int) or not 1 <= h <= m:
raise ValueError("h must be an integer between 1 and m")
if name == "naive":
return last
return season[h-1] + (change if name == "seasonal_drift" else 0)

errors = {(name, h): [] for name in names for h in range(1, H+1)}
print("origin h actual naive seasonal seasonal_drift")
for t in (8, 9, 10):
assert t + H <= dev_end
state = fit(y[:t])
for h in range(1, H+1):
predictions = [forecast(state, name, h) for name in names]
actual = y[t+h-1] # Read the target only to score the forecasts.
print(t, h, actual, *(f"{p:.3f}" for p in predictions))
for name, p in zip(names, predictions):
errors[name, h].append(actual - p)

print("model h MAE RMSE")
for name in names:
for h in range(1, H+1):
e = errors[name, h]
print(name, h, f"{mean(abs(v) for v in e):.3f}",
f"{sqrt(mean(v*v for v in e)):.3f}")

# Selection rule: MAE with equal weights for the two horizons.
chosen = min(names, key=lambda name: mean(
abs(v) for h in range(1, H+1) for v in errors[name, h]))
print("selected", chosen)
state = fit(y[:dev_end])
print("final_origin h actual forecast error")
for h in range(1, H+1):
p = forecast(state, chosen, h)
actual = y[dev_end+h-1]
print(dev_end, h, actual, f"{p:.3f}", f"{actual-p:.3f}")

Sortie :

origin h actual naive seasonal seasonal_drift
8 1 121 143.000 110.000 121.000
8 2 145 143.000 132.000 143.000
9 1 145 121.000 132.000 143.000
9 2 109 121.000 99.000 110.000
10 1 109 145.000 99.000 110.333
10 2 157 145.000 143.000 154.333
model h MAE RMSE
naive 1 27.333 28.024
naive 2 8.667 9.866
seasonal 1 11.333 11.402
seasonal 2 12.333 12.450
seasonal_drift 1 1.111 1.388
seasonal_drift 2 1.889 2.009
selected seasonal_drift
final_origin h actual forecast error
12 1 133 132.500 0.500
12 2 160 156.500 3.500

Interpréter les erreurs par horizon​

Le chapitre de FPP sur l’exactitude des prévisions ponctuelles distingue les résidus d’entraînement des véritables erreurs de prévision hors échantillon et définit MAE, RMSE et erreurs en pourcentage. Pour un hh fixé, notons nhn_h le nombre de prévisions à cet horizon :

et,h=yt+h−y^t+h∣t,MAEh=1nh∑t∣et,h∣,RMSEh=1nh∑tet,h2.e_{t,h}=y_{t+h}-\hat y_{t+h\mid t},\qquad \mathrm{MAE}_h=\frac{1}{n_h}\sum_t|e_{t,h}|,\qquad \mathrm{RMSE}_h=\sqrt{\frac{1}{n_h}\sum_t e_{t,h}^2}.

La MAE conserve l’unité des ventes ; la RMSE donne davantage de poids aux grandes erreurs. La MAPE est indéfinie pour une valeur réelle nulle et instable près de zéro. La MASE permet de comparer des séries d’unités différentes, mais son dénominateur de normalisation doit provenir de l’entraînement ; elle est indéfinie si toutes les différences saisonnières de ce dénominateur sont nulles.

Les erreurs à un pas de seasonal_drift sont 0,2,−4/30,2,-4/3, soit une MAE de 10/9≈1.11110/9\approx1.111. À deux pas, elles sont 2,−1,8/32,-1,8/3, soit une MAE de 17/9≈1.88917/9\approx1.889. Leur moyenne à poids égaux vaut 1.500, contre 18.000 pour la référence naïve et 11.833 pour la référence naïve saisonnière. Examiner chaque horizon avant de les agréger selon les besoins réels évite qu’une moyenne unique masque de mauvaises prévisions lointaines.

On ne peut pas supposer que ces erreurs constituent six essais indépendants. Q10 et Q11 sont chacun prévus depuis deux origines. Un calcul direct montre le mécanisme : supposons yt=yt−1+εty_t=y_{t-1}+\varepsilon_t, avec des accroissements indépendants, de moyenne nulle et de variance 0<σ2<∞0<\sigma^2<\infty. L’erreur naïve à deux pas est et,2=εt+1+εt+2e_{t,2}=\varepsilon_{t+1}+\varepsilon_{t+2} ; à l’origine suivante, elle devient et+1,2=εt+2+εt+3e_{t+1,2}=\varepsilon_{t+2}+\varepsilon_{t+3}. L’accroissement partagé donne une covariance de σ2\sigma^2 ; chaque erreur a une variance de 2σ22\sigma^2, d’où une corrélation de 1/21/2.

Comparer les pertes des modèles aux mêmes origines et horizons, et tenir compte de la dépendance temporelle pour estimer l’incertitude. Réduire le chevauchement ne garantit pas, à lui seul, l’indépendance des erreurs réelles. Trois origines de validation suffisent à montrer le calcul, sans permettre une conclusion fiable sur la significativité.

Passer de l’évaluation des prévisions au backtest​

Une règle de trading peut perdre de l’argent malgré de faibles erreurs de prévision. Définir la disponibilité du signal, la première transaction exécutable, les positions, la rotation du portefeuille et les coûts. Un signal formé après la clôture ne peut pas être supposé exécuté au même cours de clôture. L’univers historique doit aussi inclure les titres alors admissibles mais retirés de la cote depuis ; ne garder que les survivants actuels introduit un biais de survivance.

Supposons une opération au comptant : aux prix de référence avant coûts, le montant notionnel est de 100 000 unités monétaires à l’achat et de 100 120 à la vente, soit un bénéfice brut de 120. Le modèle de coûts fixe commission, écart acheteur-vendeur et glissement de prix à 8 points de base du montant notionnel correspondant, pour chaque transaction. Un point de base vaut 0,01 % : l’achat coûte donc 100000×0.0008=80100000\times0.0008=80 et la vente 100120×0.0008=80.096100120\times0.0008=80.096, soit 160.096 au total. Le bénéfice net est 120−160.096=−40.096120-160.096=-40.096. Ces frais sont des paramètres fictifs ; ajouter emprunt de titres, financement ou impact de marché lorsqu’ils concernent la stratégie.

Choisir plusieurs fois modèles, fenêtres ou seuils de trading fait entrer la validation dans la conception de la stratégie. The Probability of Backtest Overfitting, de Bailey et ses collègues, analyse cet effet de sélection et explique pourquoi un seul échantillon réservé ne mesure pas le biais lié aux nombreux essais précédents. Conserver tous les candidats, y compris les essais infructueux, comparer les résultats après coûts et terminer la sélection avant le test final.

Réserver une période finale qui n’a pas servi à la sélection​

Avant le test final, fixer les variables, fenêtres, horizons, pondérations des pertes et règles de réajustement, ainsi que les règles de trading et de coûts. Le programme choisit seasonal_drift sur la validation, puis l’ajuste sur Q1–Q12 : la variation annuelle moyenne vaut 11.5. Ses prévisions pour Q13 et Q14 sont 132.5 et 156.5, avec des erreurs de 0.5 et 3.5. La MAE finale vaut 2.000 et la RMSE 2.500. Seul le modèle retenu est évalué ici.

Un test final réel peut poursuivre les réajustements glissants selon une règle fixée à l’avance, en utilisant les observations antérieures de la période test une fois disponibles. Actualiser les données d’entraînement ne rouvre pas la sélection des règles. Présenter les erreurs à horizons fixes, les intervalles de dates, les millésimes disponibles et les résultats de trading après coûts. Si les résultats finaux conduisent à modifier le modèle, cette période devient de la validation ; il faut une nouvelle période qui n’a pas participé à la sélection. Pour un historique de marché bien connu, préserver cette séparation exige aussi de ne pas concevoir la stratégie à partir de souvenirs de ces résultats. Une nouvelle période consignée au fur et à mesure rend cette condition plus claire.

Pour les questions de modélisation de la finance quantitative, établir d’abord quelles données étaient accessibles, puis vérifier si le modèle dépasse régulièrement les références aux mêmes informations et horizons, et enfin si ce gain résiste aux prix exécutables et à l’ensemble des coûts.

Explorer les liensOuvrir le réseau