Scoring Probabilistic Forecasts: CRPS, PIT Calibration, and DeepAR
Бул блог чекиттик маанилердин ордуна бөлүштүрүүлөрдү болжолдоонун негизин жана интервал даяр болгондон кийинки кадамдарды буга чейин түшүндүргөн: тобокелдикти эске алган позиция өлчөмү үчүн конформдык болжолдоо бөлүштүрүүгө көз каранды эмес интервалдарды жана аларды пайдаланган өлчөө эрежесин чыгарат, ал эми Temporal Fusion Transformer макаласы көп-квантилдүү чыгыш катмарын берет. Бирок экөө тең ишенүүгө болор-болбосун аныктаган бөлүктү камтыбайт: болжолдуу бөлүштүрүүнү кантип баалоо жана анда көрсөтүлгөн белгисиздиктин чынчыл экенин кантип текшерүү керек.
Бул макала дал ушул жөнүндө. Үч нерсе өзгөчө маанилүү:
- CRPS — ыктымалдык божомолдор үчүн proper scoring rule жана анын TFT макаласында колдонулган pinball loss менен так байланышы.
- PIT гистограммасы — модель калибрленбей калганын гана эмес, кандайча калибрленбей калганын көрсөткөн диагностика.
- DeepAR — авторегрессиялык sampling моделдеринин үй-бүлөсү; бул блогдо ал азырынча benchmark эскертмеси катары гана кездешет.
Адегенде керектүү механизмди аныктаган контекст. Чекиттик божомол режимге жараша ар башкача жаңылат: төмөн волатилдүү трендде шарттуу бөлүштүрүү тар жана симметрияга жакын, чекиттик божомол жетиштүү жыйынтык; пландалган окуя алдында ал бимодалдуу болуп, шарттуу орточо баа баанын түшүү ыктымалдыгы эң аз болгон жерде турат; кризисте сол куйрук үстөмдүк кылып, орточо маани төмөндөө тобокелдигин баалабайт. Толук бөлүштүрүүнү калыбына келтирүүнүн үч жолу бар — параметрдик (божомолдонгон үй-бүлөнүн параметрлерин алдын ала айтуу: тез, бирок туура эмес спецификация коркунучу бар), квантилдик (туруктуу торду болжолдоо: божомолсуз, дискреттүү) жана үлгүгө негизделген (авторегрессиялык sampling, dropout же ансамблдерден Monte Carlo жолдору: ийкемдүү, кымбат). Акыркы эки ыкма финансыда үстөмдүк кылат, анткени режимдер өзгөргөндө бөлүштүрүүнүн формасы да өзгөрөт.
Квантилдик божомолдор кыскача

Pinball loss жана конкреттүү квантилдик тор TFT квантилдик чыгыш катмары макаласында берилген, ошондуктан формуланы кайталабайм. Эсте тутчу негизги нерсе — анын асимметриясы: болгондо ашыкча жана кем божомолдун баасы бирдей, loss MAE-ге түшөт, ал эми болгондо кем божомол ашыкча божомолго караганда катуураак жазаланат. катышы механизмдин өзү: ал fitted маанини байкоолордун 5% гана ашышы керек болгон деңгээлге сүйрөйт.
Жарыяланган макалада айтылбаган практикалык көйгөй — квантилдердин кайчылашуусу: ар бир квантилге өзүнчө loss колдонуу болушуна тоскоол болбойт. Маалымат жетиштүү жана backbone орток болсо сейрек кездешет, бирок жука үлгүдөгү экстремалдык квантилдерде болуп, кийинки CRPS же coverage эсептөөлөрүн үнсүз бузат. Арзан оңдоо — божомолдонгон квантил векторун post-hoc сорттоо; принциптүү чечим — монотондуу чыгыш параметризациясы ( жана терс эмес өсүмдөрдү болжолдоо).
Forecasting framework кабыл албай эле квантилдик чыгыш керек болгондо пайдалуу болгон минималдуу реализация:
import torch
import torch.nn as nn
class QuantileRegressionNet(nn.Module):
"""Multi-quantile forecasting network for financial returns."""
def __init__(self, input_dim: int, hidden_dim: int = 128,
quantiles: list[float] = [0.01, 0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 0.99]):
super().__init__()
self.quantiles = quantiles
self.backbone = nn.Sequential(
nn.Linear(input_dim, hidden_dim), nn.ReLU(), nn.Dropout(0.2),
nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.Dropout(0.2),
)
self.heads = nn.ModuleList([nn.Linear(hidden_dim, 1) for _ in quantiles])
def forward(self, x: torch.Tensor) -> torch.Tensor:
h = self.backbone(x)
out = torch.cat([head(h) for head in self.heads], dim=-1)
return torch.sort(out, dim=-1).values
def pinball_loss(predictions: torch.Tensor, targets: torch.Tensor,
quantiles: list[float]) -> torch.Tensor:
"""predictions: (batch, n_quantiles); targets: (batch, 1)."""
errors = targets - predictions
tau = torch.tensor(quantiles, device=predictions.device).unsqueeze(0)
return torch.max(tau * errors, (tau - 1) * errors).mean()
Интервалдын кеңдиги позициянын өлчөмүнө өтөт; бул байланышты, анын ичинде edge-ratio no-trade фильтрин, тобокелдикти эске алган позиция өлчөмү үчүн конформдык болжолдоо макаласында толук чыгарабыз. менен volatility targeting эквиваленти GARCH божомолдору менен volatility targeting макаласында чыгарылган.
DeepAR: Авторегрессиялык ыктымалдык божомолдоо

DeepAR (Salinas жана башкалар, 2020) үлгүгө негизделген жолду тандайт. Квантилдерди түздөн-түз божомолдоонун ордуна, ар бир кадамдагы likelihood-ди параметрлөө үчүн авторегрессиялык RNN колдонуп, андан соң ошол likelihood-ди алдыга жылдырып толук predictive distribution алат.
Архитектура. Ар бир кадамында тармак мурунку байкоону -ди (ар бир серияга тиешелүү фактор менен масштабдалган), ковариаттарды жана кошумча static features керектейт. LSTM абалды алып жүрөт:
Dense head -ни likelihood параметрлерине айлантат — Gaussian үчүн , Student-t үчүн . Окутуу бардык сериялар боюнча маанисин максималдаштырат. Inference учурунда -ден sample алып, аны кийинки input катары кайра берип, траектория алуу үчүн жолу кайталайсыз.
Соода үчүн эки натыйжа маанилүү. Likelihood — моделдөө тандоосу, демек майлуу куйруктар күтүлгөн нерсе эмес, тандалган нерсе: оор куйруктар үчүн Student-t же бимодалдуу окуя үчүн Gaussian mixture . Көп кадамдуу божомолдор ырааттуу: ар бир sample path сериялык корреляцияны сактаган ыктымал траектория, бул бир кадамдан узун горизонттор үчүн керек. (Үчүнчү артыкчылык — көп серияга бир global model колдонуу per-asset model-ден натыйжалуураак — TFT макаласында айтылган data-efficiency аргументи; ал жерде static encoder ролун per-series scaling жана static covariates аткарат.)
GluonTS менен DeepAR
import pandas as pd
from gluonts.dataset.pandas import PandasDataset
from gluonts.torch.model.deepar import DeepAREstimator
from gluonts.torch.distributions import StudentTOutput
from gluonts.evaluation import make_evaluation_predictions, Evaluator
def prepare_crypto_dataset(returns_df: pd.DataFrame, freq: str = "h"):
"""Wide return frame (index=datetime, columns=assets) -> GluonTS dataset.
from_long_dataframe wants LONG format: one row per (timestamp, item_id)
with the target in a column. Passing a DataFrame of dicts does not work.
"""
long_df = (
returns_df.stack()
.rename("target")
.rename_axis(index=["timestamp", "item_id"])
.reset_index()
.dropna(subset=["target"])
)
return PandasDataset.from_long_dataframe(
long_df, target="target", item_id="item_id",
timestamp="timestamp", freq=freq,
)
estimator = DeepAREstimator(
prediction_length=24, # 24 hours ahead
context_length=168, # one week of hourly data
freq="h",
num_layers=2,
hidden_size=64,
dropout_rate=0.1,
lr=1e-3,
batch_size=64,
trainer_kwargs={"max_epochs": 50},
distr_output=StudentTOutput(),
)
predictor = estimator.train(training_data=train_dataset)
forecast_it, ts_it = make_evaluation_predictions(
dataset=test_dataset, predictor=predictor, num_samples=500,
)
forecasts, actuals = list(forecast_it), list(ts_it)
evaluator = Evaluator(quantiles=[0.05, 0.25, 0.5, 0.75, 0.95])
agg_metrics, item_metrics = evaluator(actuals, forecasts)
print(f"mean_wQuantileLoss: {agg_metrics['mean_wQuantileLoss']:.4f}")
num_samples Monte Carlo жолдорунун санын башкарат. Live use үчүн адатта 100--200 жетиштүү; offline evaluation үчүн 500--1000 колдонуңуз, анткени sample аз болсо эсептелген CRPS төмөн карай bias болот.
Predictive distribution алуунун башка жолдору
Үч альтернативаны билүү керек, бирок аларга өзүнчө бөлүм талап кылынбайт. MC Dropout (Gal жана Ghahramani, 2016) inference учурунда dropout-ту активдүү калтырып, forward pass боюнча орточо жана дисперсияны алат; бул жашырылган approximate variational inference жана буга чейин окутулган моделге uncertainty кошуунун эң тез жолу. Deep ensembles (Lakshminarayanan жана башкалар, 2017) ар башка seed менен көчүрмө окутып, predictive distribution-ду mixture катары карайт — benchmark-терде туруктуу күчтүү, наркы , ошондуктан latency-sensitive горизонтторго жарабайт, бирок 4h же daily горизонтторго жарайт. Normalizing flows (Rasul жана башкалар, 2021) параметрдик family тандабай, multimodality жана asymmetry-ди кармап, жөнөкөй base density'ден каалаган target'ке invertible map үйрөнөт. Үчөө тең sample чыгарат, ошондуктан кийинки бөлүмдүн баары аларга өзгөрүүсүз колдонулат.
CRPS: Predictive distribution үчүн туура метрика

MSE жана MAE point forecast-тарды баалайт. Алар бөлүштүрмө жакшыбы, айта албайт, анткени анын бир гана summary'ине карайт. Стандарттык алмаштыргыч — Continuous Ranked Probability Score, бул макаладагы эң пайдалуу нерсе.
CRPS — болжолдонгон CDF менен бардык массаны чыныгы натыйжага койгон degenerate CDF ортосундагы интегралданган квадраттык аралык:
Анын маанилүүлүгүн үч касиет түшүндүрөт:
- Бул proper scoring rule (Gneiting жана Raftery, 2007): күтүлгөн мааниде болжолдонгон бөлүштүрмө чыныгы бөлүштүрмөгө тең болгондо гана минимумга жетет. Атайын overconfidence да, жасалма кең бөлүштүрмө менен hedging да score'ду жакшыртпайт. MSE-on-the-median мындай касиетке ээ эмес, ошондуктан CRPS милдеттүү.
- MAE'ни жалпылайт. Degenerate point forecast үчүн абсолюттук катага түшөт, демек CRPS target бирдиктеринде болот — hourly log return үчүн CRPS 0.004 unitless сан эмес, 40 bps mean absolute error менен түз салыштырылат.
- Calibration шартында sharpness'ти сыйлайт. Бирдей калибрленген эки forecast арасынан тарыраагы жакшы score алат. Ошондуктан CRPS жалгыз жетишсиз: начар score кайсы шарт бузулганын айтпайт, кийинки бөлүм дал ошого арналат.
Quantile loss менен байланыш
Бул байланыш блогдун калган бөлүгүндө жетишпейт. Толук CDF ордуна quantile grid берилсе, CRPS pinball loss аркылуу жакындатылат:
Муну түз маанисинде окуңуз: TFT макаласында reported "quantile loss" жана бул жердеги CRPS 2 факторуна чейин бир эле чоңдук, grid тыгыздаган сайын жакындаштыруу жакшырат. Алар атаандаш метрикалар эмес, экөөнү тең отчеттоого негиз жок.
Бирдик боюнча эскертүү. GluonTS'тин mean_wQuantileLoss ошол эле averaged pinball loss, бирок absolute target маанилеринин суммасы менен normalized, ошондуктан dimensionless жана масштабы башка активдер арасында салыштырылат. Туура CRPS normalized эмес, return бирдиктеринде калат. mean_wQuantileLoss-ти "CRPS" деген label астында чыгарбаңыз — бул макаланын draft версиясы дал ушундай кылган.
Sample-дерден CRPS
Monte Carlo sample-дери (DeepAR, ensembles, flows) болсо, energy form колдонулат:
Биринчи мүчө тактыкты сыйлайт, экинчиси overdispersion'ду жазалайт. Экинчи мүчөнү наивдүү эсептөө болуп, миңдеген forecast баалаганда bottleneck жаратат. Алгач сорттосо, ascending order statistics үчүн double sum болуп кыскарат; бүт эсеп , негизги чыгым sort.
import numpy as np
def crps_empirical(samples: np.ndarray, observation: float) -> float:
"""CRPS from Monte Carlo samples, O(n log n) via order statistics."""
n = len(samples)
mae = np.mean(np.abs(samples - observation))
x = np.sort(samples)
k = np.arange(1, n + 1)
dispersion = np.sum((2 * k - n - 1) * x) / n**2
return mae - dispersion
def crps_quantile(quantile_predictions: np.ndarray,
quantile_levels: np.ndarray,
observation: float) -> float:
"""CRPS approximation from a quantile grid (2x mean pinball loss)."""
errors = observation - quantile_predictions
pinball = np.where(errors >= 0,
quantile_levels * errors,
(quantile_levels - 1) * errors)
return 2.0 * np.mean(pinball)
Эки форма бир predictive distribution үчүн жакын жыйынтык бериши керек; айырмаланса quantile crossing же sample өтө аз деп шектениңиз. Production'до properscoring.crps_ensemble(observation, samples) жакшы текшерилген drop-in.
Calibration: Көрсөтүлгөн uncertainty чынчылбы?

Жакшы CRPS интервалдар өздөрү айткан нерсени билдирет деген кепилдик бербейт. Nominal 90% interval outcomes'тун 70%ын гана камтыган модель overconfident; interval width'ти окуган sizing rule'да ал болбошу керек учурда leverage'ти көбөйтөт. TFT макаласы бул эскертүүнү айтып, conformal prediction'ду оңдоо катары сунуштайт; төмөндө failure-ди кантип аныктап, диагноз коюу керектиги көрсөтүлөт.
PIT гистограммасы
Ар бир байкоосу үчүн анын ушул кадамдагы predicted CDF ичиндеги quantile'ин эсептеңиз:
Модель калибрленген болсо, ичинде uniform болот. Диагностиканын күчү deviation'дун формасы failure mode'ду атаганында:
- U-түрүндө: overconfident — куйруктарда масса өтө көп, бөлүштүрмө өтө тар.
- Дөңсөө түрүндө: underconfident — байкоолор борборго жакын топтолот, бөлүштүрмө өтө кең.
- Сол жакка кыйшайган: модель системалуу түрдө ашыкча болжолдойт.
- Оң жакка кыйшайган: модель системалуу түрдө кем божомолдойт.
Joint risk үчүн copula models менен чаташтырбаңыз, анда probability integral transform башкача колдонулат: ал GARCH-EVT marginal-дарын pseudo-uniform observations кылып өзгөртүп, copula fit кылган marginal transform, preprocessing кадамы. Бул жерде transform out-of-sample forecast-торго колдонулат жана uniformity — жасалган input эмес, текшериле турган натыйжа. Математикасы бир, inference багыты карама-каршы.
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import kstest
def pit_calibration_check(forecasts, actuals, n_bins=20):
"""PIT histogram + KS test against uniform.
forecasts: list of SampleForecast objects (GluonTS)
"""
pit_values = []
for forecast, actual in zip(forecasts, actuals):
samples = forecast.samples # (n_samples, prediction_length)
h = forecast.prediction_length
for t in range(h):
obs = actual.values[-h + t]
pit_values.append(np.mean(samples[:, t] <= obs))
pit_values = np.array(pit_values)
ks_stat, p_value = kstest(pit_values, "uniform")
fig, ax = plt.subplots(figsize=(8, 4))
ax.hist(pit_values, bins=n_bins, density=True, alpha=0.7, edgecolor="black")
ax.axhline(y=1.0, color="red", linestyle="--", label="Perfect calibration")
ax.set_xlabel("PIT value")
ax.set_ylabel("Density")
ax.set_title(f"PIT Histogram (KS={ks_stat:.3f}, p={p_value:.3f})")
ax.legend()
plt.tight_layout()
return pit_values, ks_stat, p_value
KS test боюнча эки эскертүү бар. Ал PIT маанилери independent деп эсептейт, ал эми overlapping multi-horizon forecast-тор күчтүү autocorrelated — p-value'ди болжолдуу flag катары гана көрүңүз, чыныгы далил histogram shape. Байкоо көп болгондо тест маанилүүлүгү аз miscalibration үчүн да uniformity'ди четке кагат; чечимди effect size аныкташы керек.
Coverage текшерүүсү
Орой, бирок түз колдонулуучу диагностика: % интервалдар outcomes'тун %ын камтыйбы?
import numpy as np
import pandas as pd
def coverage_table(forecasts, actuals, levels=(0.50, 0.80, 0.90, 0.95)):
"""Empirical coverage at multiple nominal levels."""
results = {}
for level in levels:
lower_q = (1 - level) / 2
upper_q = 1 - lower_q
covered = total = 0
for forecast, actual in zip(forecasts, actuals):
samples = forecast.samples
h = forecast.prediction_length
for t in range(h):
obs = actual.values[-h + t]
lo = np.quantile(samples[:, t], lower_q)
hi = np.quantile(samples[:, t], upper_q)
covered += int(lo <= obs <= hi)
total += 1
empirical = covered / total
results[f"{int(level*100)}% interval"] = {
"nominal": level, "empirical": empirical,
"gap": empirical - level,
}
return pd.DataFrame(results).T
Эреже катары: типтүү sample size'да 2 percentage point'тен кичине gap — noise, ал эми 3--5 point'тен чоң gap — position sizing'де көрүнгөн чыныгы calibration problem. Аны pooled эмес, ар бир horizon боюнча иштетиңиз — horizon узарган сайын coverage дээрлик дайыма начарлайт, pooling муну жашырат.
Calibration иштебей калганда
Кепилдик күчү өсүүчү тартипте үч стандарттык оңдоо бар. Temperature scaling held-out data'да үйрөтүлгөн менен predicted scale parameter'ди бөлөт — бир параметр, өтө арзан; uniform over/underconfidence'ди оңдойт, бирок shape-dependent катаны оңдобойт. Isotonic recalibration predicted quantile levels'ди observed frequencies'ке монотондуу mapping кылат, shape distortion'ду иштетет, бирок жетиштүү чоң calibration set керек. Conformal prediction каалаган моделди ороп, finite-sample coverage guarantees берет; толук split-conformal алгоритми, так order-statistic rank жана бир саптык summary жашырган interpolation жана clamping тузактары trading үчүн conformal prediction макаласында берилген.
Практикалык ойлор

Non-stationarity. Volatility regime өзгөргөн сайын calibration drift кылат, демек туруктуу calibration set эскирет. Дал ушул нерсеге арналган механизм — Adaptive Conformal Inference; ал online miscoverage level'ди жаңыртып, adversarial sequence'терде да long-run coverage кепилдигин сактайт — trading үчүн conformal prediction макаласынын ACI бөлүгүн караңыз.
Computational cost. 500 sample path'ы бар DeepAR бир forward pass'тен болжол менен кымбат. Intraday иш үчүн quantile regression'ди (бардык quantile бир pass'та) же parametric head'ти ( бир жолу болжолот) артык көрүңүз; autoregressive sampling'ди 4h жана daily горизонтторго калтырыңыз.
Regime changes. Forecaster'ди regime detector менен бириктирип, ар бир regime үчүн жеке calibration parameter'лерин сактаңыз — HMM менен regime detection иштеген implementation жана backtest менен камсыз кылат.
Multivariate forecasts. Ар бир asset'тин marginal distribution'у portfolio risk үчүн жетишсиз; маанилүүсү — joint tail, ал эми joint risk үчүн copula models independence assumption'дары аны канчалык төмөн баалай турганын санайт.
Downstream consumers. VaR жана expected shortfall sample-based forecast'тен quantile жана conditional tail mean катары түз чыгат — аныктамалар жана Monte Carlo рецепти joint risk үчүн copula models макаласында. Kelly sizing бекер келбеген жалгыз нерсе: forecast interval'ден Kelly fraction чыгаруу ошол interval ичиндеги distribution жөнүндө кошумча assumption талап кылат, ал эми conformal макаласы interval ratio'ну -ке жөн эле жалгоого каршы жүйө келтирет. Fraction эмнени талап кыларын стратегиялар үчүн Kelly критерийи макаласынан көрүңүз.
Жыйынтык

Ыктымалдык forecast үчүн evaluation stack кыска жана талашсыз: score үчүн CRPS, анткени ал proper жана target бирдиктеринде өлчөнөт; diagnosis үчүн PIT histogram, анткени ал жөн гана flag кылбай failure mode'ду атайт; decision үчүн per-horizon coverage, анткени позиция өлчөмүнө өтчү сан ушул. "Quantile loss" деп отчеттолгон нерсенин баары CRPS'ке 2 коэффициентине чейин тең, демек бул жерде эки эмес, бир метрика бар.
Ыңгайсыз жери — булардын баары модель сиз күткөндөн начар экенин аныктоочу механизмдер. Максат да ошол. Билбегенде интервалын чынчылдык менен кеңейткен модель ишенимдүү түрдө тар бойдон калган моделге караганда кыйла пайдалуу; аларды ажыратуунун жалгыз жолу — туура баалоо.
Шилтемелер
- Salinas, D., Flunkert, V., Gasthaus, J., & Januschowski, T. (2020). "DeepAR: Probabilistic forecasting with autoregressive recurrent networks." International Journal of Forecasting, 36(3), 1181-1191.
- Koenker, R. & Bassett, G. (1978). "Regression Quantiles." Econometrica, 46(1), 33-50.
- Gneiting, T. & Raftery, A. E. (2007). "Strictly Proper Scoring Rules, Prediction, and Estimation." Journal of the American Statistical Association, 102(477), 359-378.
- Gneiting, T., Balabdaoui, F., & Raftery, A. E. (2007). "Probabilistic forecasts, calibration and sharpness." Journal of the Royal Statistical Society: Series B, 69(2), 243-268.
- Gal, Y. & Ghahramani, Z. (2016). "Dropout as a Bayesian Approximation: Representing Model Uncertainty in Deep Learning." ICML.
- Lakshminarayanan, B., Pritzel, A., & Blundell, C. (2017). "Simple and Scalable Predictive Uncertainty Estimation using Deep Ensembles." NeurIPS.
- Rasul, K., Sheikh, A.-S., Schuster, I., Bergmann, U., & Vollgraf, R. (2021). "Multivariate Probabilistic Time Series Forecasting via Conditioned Normalizing Flows." ICLR.
- GluonTS: Probabilistic Time Series Modeling in Python. https://ts.gluon.ai
Authors
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.