Scoring Probabilistic Forecasts: CRPS, PIT Calibration, and DeepAR
Dieser Blog hat bereits begrundet, warum man Verteilungen statt einzelner Punkte prognostizieren sollte, und auch gezeigt, was man mit dem Intervall macht, sobald es vorliegt: Konforme Prognose fur risikobewusste Positionsgroessen leitet verteilungsfreie Intervalle und die daraus folgende Positionsregel her, und der Artikel zum Temporal Fusion Transformer liefert eine Ausgabeschicht mit mehreren Quantilen. Was beide nicht behandeln, ist die Frage, die entscheidet, ob man dem Ganzen vertrauen darf: Wie bewertet man eine Prognoseverteilung, und wie pruft man, ob ihre angegebene Unsicherheit ehrlich ist?
Darum geht es in diesem Artikel. Konkret um drei Dinge:
- CRPS - die proper scoring rule fur probabilistische Prognosen und ihre genaue Beziehung zum Pinball-Loss, uber den der TFT-Artikel bereits berichtet.
- Das PIT-Histogramm - eine Kalibrierungsdiagnose, die zeigt, wie ein Modell fehlkalibriert ist, nicht nur, ob es fehlkalibriert ist.
- DeepAR - die Modellfamilie mit autoregressiver Stichprobenziehung, die in diesem Blog bisher nur als Benchmark-Fussnote auftaucht und nie erklart wurde.
Zuerst ein Rahmen, denn er bestimmt, welche Werkzeuge man braucht. Eine Punktprognose scheitert je nach Regime unterschiedlich: In einem Trend mit niedriger Volatilitaet ist die bedingte Verteilung schmal und nahezu symmetrisch, eine Punktprognose ist eine gute Zusammenfassung; vor einem geplanten Ereignis ist sie bimodal, und der bedingte Mittelwert liegt genau dort, wo der Preis am wenigsten wahrscheinlich landet; in einer Krise dominiert der linke Rand, und der Mittelwert unterschaetzt die Abwaertsbewegung stark. Drei Wege stellen die vollstaendige Verteilung wieder her: parametrisch (Parameter einer angenommenen Familie vorhersagen: schnell, aber Spezifikationsrisiko), quantilbasiert (ein festes Gitter vorhersagen: ohne Verteilungsannahme, diskret) und stichprobenbasiert (Monte-Carlo-Pfade aus autoregressiver Stichprobenziehung, Dropout oder Ensembles: flexibel, teuer). Die letzten beiden Verfahren sind im Finanzwesen gerade deshalb besonders wichtig, weil die Verteilungsform zwischen diesen Regimen wechselt.
Quantilprognosen kurz erklaert

Der Pinball-Loss und ein konkretes Quantilgitter sind bereits in der TFT-Quantil-Ausgabeschicht angegeben, daher wiederhole ich die Formel nicht. Wichtig ist die Asymmetrie: Bei kosten Uber- und Unterprognosen gleich viel und der Loss wird zu MAE; bei wird eine Unterprognose staerker bestraft als eine Uberprognose. Das Verhaeltnis ist der Mechanismus: Es zieht den geschaetzten Wert auf ein Niveau, das nur 5% der Beobachtungen uberschreiten sollten.
Die praktische Feinheit, die der veroeffentlichte Artikel nicht erwaehnt, ist das Quantilkreuzen: Ein Loss pro Quantil verhindert nicht, dass gilt. Bei genug Daten und gemeinsamem Backbone ist das selten, bei extremen Quantilen und kleinen Stichproben kommt es aber vor und zerstoert stillschweigend jede nachgelagerte CRPS- oder Coverage-Berechnung. Die billige Loesung ist, den vorhergesagten Quantilvektor nachtraeglich zu sortieren; die prinzipielle Loesung ist eine monotone Ausgabeparametrisierung ( plus nichtnegative Zuwachse vorhersagen).
Eine minimale Implementierung von Grund auf, wenn man Quantilausgaben ohne ein Forecasting-Framework braucht:
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()
Die Intervallbreite wird auf die Positionsgroesse abgebildet. Diese Abbildung, einschliesslich des No-Trade-Filters auf Basis des Randverhaeltnisses, leiten wir in konformer Prognose fur risikobewusste Positionsgroessen vollstaendig her; die -Aequivalenz zum Volatilitaets-Targeting wird in Volatilitaets-Targeting mit GARCH-Prognosen hergeleitet.
DeepAR: autoregressive probabilistische Prognosen

DeepAR (Salinas et al., 2020) verfolgt den stichprobenbasierten Weg. Statt Quantile direkt vorherzusagen, parametrisiert es mit einem autoregressiven RNN in jedem Schritt eine Likelihood und erzeugt dann die vollstaendige Prognoseverteilung, indem es diese Likelihood vorwaerts abrollt.
Architektur. In jedem Schritt verarbeitet das Netz die vorherige Beobachtung (mit einem Faktor pro Serie skaliert), die Kovariaten und optionale statische Merkmale. Ein LSTM traegt den Zustand weiter:
Ein dichter Kopf bildet auf Likelihood-Parameter ab - fur die Gaußverteilung, fur Student-t. Das Training maximiert uber alle Serien . Bei der Inferenz zieht man Stichproben aus , fuhrt die Stichprobe als naechste Eingabe zuruck und wiederholt dies -mal, um Trajektorien zu erhalten.
Fur den Handel sind zwei Folgen wichtig. Die Likelihood ist eine Modellierungsentscheidung, daher muss man fette Rander auswahlen, statt auf sie zu hoffen - Student-t fur schwere Rander oder eine Gauß-Mischung fur bimodales Ereignisverhalten. Mehrschrittprognosen sind konsistent: Jeder Stichprobenpfad ist eine plausible Trajektorie, die serielle Korrelation bewahrt, was man fur Horizonte langer als einen Schritt braucht. (Der dritte ubliche Vorteil - ein globales Modell uber viele Serien schlaegt Modelle mit je einem Asset - ist dasselbe Dateneffizienz-Argument wie im TFT-Artikel; dort spielen Skalierung pro Serie und statische Kovariaten die Rolle der statischen Encoder.)
DeepAR mit GluonTS
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 steuert die Anzahl der Monte-Carlo-Pfade. Fur den Live-Betrieb reichen meist 100-200; fur Offline-Evaluation sollte man 500-1000 verwenden, da CRPS bei zu wenigen Stichproben nach unten verzerrt ist.
Weitere Wege zu einer Prognoseverteilung
Drei Alternativen sind wissenswert, brauchen aber keinen eigenen Abschnitt. MC Dropout (Gal und Ghahramani, 2016) lasst Dropout bei der Inferenz aktiv und nimmt Mittelwert und Varianz uber Forward-Passes; das ist ungefahr variationale Inferenz und der schnellste Weg, einem bereits trainierten Modell Unsicherheit hinzuzufugen. Deep Ensembles (Lakshminarayanan et al., 2017) trainieren Kopien mit verschiedenen Seeds und behandeln die Prognoseverteilung als Mischung - in Benchmarks konstant stark, aber mit Kosten von , was sie fur latenzkritische Horizonte ausschliesst, nicht jedoch fur 4-Stunden- oder Tageshorizonte. Normalizing Flows (Rasul et al., 2021) lernen eine invertierbare Abbildung von einer einfachen Basisdichte auf ein beliebiges Ziel und erfassen Multimodalitaet sowie Asymmetrie ohne die Wahl einer parametrischen Familie. Alle drei erzeugen Stichproben, daher gilt alles im naechsten Abschnitt unveraendert.
CRPS: Die richtige Metrik fur eine Prognoseverteilung

MSE und MAE bewerten Punktprognosen. Sie konnen nicht sagen, ob eine Verteilung gut war, weil sie immer nur eine Zusammenfassung davon betrachten. Der Standardersatz ist der Continuous Ranked Probability Score, und er ist das Wichtigste in diesem Artikel.
CRPS ist der integrierte quadratische Abstand zwischen der vorhergesagten CDF und der degenerierten CDF, die ihre gesamte Masse auf das tatsachlich eingetretene Ergebnis legt:
Drei Eigenschaften begrunden seinen Platz:
- Es ist eine proper scoring rule (Gneiting und Raftery, 2007): Im Erwartungswert wird sie nur minimiert, wenn die vorhergesagte Verteilung der wahren Verteilung entspricht. Weder bewusstes Ubervertrauen noch eine kunstlich breite Absicherung verbessern den Score. MSE auf dem Median hat diese Eigenschaft nicht, daher ist CRPS nicht optional.
- Es verallgemeinert MAE. Fur eine degenerierte Punktprognose fallt es auf den absoluten Fehler zuruck, daher steht CRPS in den Einheiten des Targets - bei stundenweisen Log-Returns ist CRPS 0.004 direkt mit einem mittleren absoluten Fehler von 40 bps vergleichbar und nicht mit einer dimensionslosen Zahl, die man nicht plausibilisieren kann.
- Es belohnt Schaerfe unter der Bedingung der Kalibrierung. Zwischen zwei gleich gut kalibrierten Prognosen schneidet die schmalere besser ab. Deshalb reicht CRPS allein nicht: Ein schlechter Score sagt nicht, welche der beiden Bedingungen verletzt wurde, und genau darum geht es im nachsten Abschnitt.
Die Verbindung zum Quantil-Loss
Das ist die Verbindung, die dem restlichen Blog fehlt. Wenn statt einer vollstaendigen CDF ein Quantilgitter gegeben ist, wird CRPS durch den Pinball-Loss angenahert:
Lesen Sie das woertlich: Der im TFT-Artikel berichtete "Quantile loss" und das hier besprochene CRPS sind bis auf den Faktor 2 dieselbe Groesse, wobei sich die Approximation bei dichterem Gitter verbessert. Es sind keine konkurrierenden Metriken, und es gibt keinen Grund, beide zu berichten.
Ein Hinweis zu den Einheiten, weil er leicht ubersehen wird. GluonTS' mean_wQuantileLoss ist derselbe gemittelte Pinball-Loss, normalisiert durch die Summe der absoluten Targetwerte. Dadurch ist er dimensionslos und zwischen Assets mit unterschiedlicher Skala vergleichbar. Das eigentliche CRPS wird nicht normalisiert und bleibt in Return-Einheiten. Geben Sie mean_wQuantileLoss nicht unter einem Label "CRPS" aus - genau das tat die Entwurfsversion dieses Artikels; so vergleicht man leicht zwei Zahlen auf unterschiedlichen Skalen.
CRPS aus Stichproben
Bei Monte-Carlo-Stichproben (DeepAR, Ensembles, Flows) verwenden Sie die Energieform:
Der erste Term belohnt Genauigkeit, der zweite bestraft Uberdispersion. Naiv berechnet ist der zweite Term und wird beim Bewerten tausender Prognosen zum Engpass. Eine vorherige Sortierung reduziert dies: Fur aufsteigende Ordnungsstatistiken ist die Doppelsumme gleich , sodass der gesamte Aufwand von der Sortierung dominiert betraegt.
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)
Beide Formen sollten bei derselben Prognoseverteilung gut ubereinstimmen; falls nicht, sind Quantilkreuzen oder zu wenige Stichproben verdachtig. In der Produktion ist properscoring.crps_ensemble(observation, samples) ein gut getesteter Drop-in-Ersatz.
Kalibrierung: Ist die angegebene Unsicherheit ehrlich?

Ein guter CRPS garantiert nicht, dass Intervalle die behauptete Bedeutung haben. Ein Modell, dessen nominales 90%-Intervall nur 70% der Ergebnisse erfasst, ist uberkonfident; in einer Positionsregel, die die Intervallbreite verwendet, hebelt es Sie genau dann hoch, wenn es das nicht sollte. Der TFT-Artikel nennt diese Warnung und empfiehlt konforme Prognosen als Loesung. Im Folgenden geht es darum, wie man das Versagen tatsaechlich erkennt und diagnostiziert.
Das PIT-Histogramm
Berechnen Sie fur jede Beobachtung ihr Quantil in der fur diesen Schritt prognostizierten CDF:
Ist das Modell kalibriert, ist gleichverteilt auf . Die diagnostische Kraft liegt darin, dass die Form der Abweichung den Fehlermodus benennt:
- U-foermig: uberkonfident - zu viel Masse liegt in den Randbereichen, die Verteilung ist zu schmal.
- Buckelfoermig: unterkonfident - Beobachtungen sammeln sich in der Mitte, die Verteilung ist zu breit.
- Linksschief: Das Modell uberschaetzt systematisch.
- Rechtsschief: Das Modell unterschaetzt systematisch.
Eine Anmerkung zur Abgrenzung von Copula-Modellen fur gemeinsames Risiko, die ebenfalls die Probability Integral Transform verwenden: Dort ist sie eine marginale Transformation, also ein Vorverarbeitungsschritt, der GARCH-EVT-Marginalverteilungen in pseudo-uniforme Beobachtungen umwandelt, damit eine Copula daran angepasst werden kann. Hier wird die Transformation auf Out-of-Sample-Prognosen angewandt, und die Gleichverteilung ist das zu testende Ergebnis, keine erzeugte Eingabe. Gleiche Mathematik, entgegengesetzte Inferenzrichtung.
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
Zwei Vorbehalte zum KS-Test: Er setzt unabhaengige PIT-Werte voraus, waehrend sich uberlappende Mehrhorizont-Prognosen stark autokorreliert verhalten. Betrachten Sie den p-Wert daher als grobes Signal und die Histogrammform als eigentliche Evidenz. Bei genuegend Beobachtungen verwirft der Test ausserdem die Gleichverteilung auch bei einer Fehlkalibrierung, die praktisch zu klein ist; die Effektgroesse sollte die Entscheidung bestimmen.
Coverage-Prufung
Eine grobere, aber direkt handlungsrelevante Diagnose: Enthalten die %-Intervalle % der Ergebnisse?
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
Als Faustregel gelten Abweichungen unter 2 Prozentpunkten bei typischen Stichprobengroessen als Rauschen; Abweichungen uber 3-5 Punkte sind ein echtes Kalibrierungsproblem, das sich in der Positionsgroesse zeigt. Fuhren Sie die Prufung pro Horizont aus, nicht gepoolt - die Coverage sinkt fast immer mit wachsendem Horizont, und Pooling versteckt diesen Effekt.
Wenn die Kalibrierung scheitert
Drei Standardreparaturen mit zunehmendem Garantiegrad. Temperature Scaling teilt den vorhergesagten Skalenparameter durch ein auf Holdout-Daten gelerntes - ein Parameter, trivial billig; es behebt gleichmaessiges Uber- oder Untervertrauen, aber nichts Formabhaengiges. Isotone Rekalibrierung bildet vorhergesagte Quantilniveaus monoton auf beobachtete Haeufigkeiten ab. Sie behandelt Formverzerrungen, braucht aber einen ausreichend grossen Kalibrierungssatz. Konforme Prognose umhuellt jedes Modell und liefert Coverage-Garantien fur endliche Stichproben; der vollstaendige Split-Conformal-Algorithmus, der exakte Rang der Ordnungsstatistik sowie die Interpolations- und Clamping-Fallen, die eine Einzeilen-Zusammenfassung verschweigt, stehen in konformer Prognose fur den Handel.
Praktische Uberlegungen

Nichtstationaritaet. Die Kalibrierung driftet mit wechselnden Volatilitaetsregimen, daher veraltet ein fester Kalibrierungssatz. Der genau dafur entwickelte Mechanismus ist Adaptive Conformal Inference: Er aktualisiert online das Miscoverage-Niveau und garantiert langfristige Coverage selbst bei adversarialen Sequenzen - siehe den ACI-Abschnitt von konformer Prognose fur den Handel.
Rechenkosten. DeepAR mit 500 Sample-Pfaden kostet ungefaehr einen einzelnen Forward-Pass. Fur Intraday-Arbeit bevorzugen Sie Quantilregression (alle Quantile in einem Pass) oder einen parametrischen Kopf ( einmal vorhersagen); autoregressives Sampling ist fur 4-Stunden- und Tageshorizonte aufzuheben.
Regimewechsel. Kombinieren Sie den Prognostiker mit einem Regime-Detektor und halten Sie Kalibrierungsparameter pro Regime vor - Regimeerkennung mit HMM enthalt eine durchgerechnete Implementierung und einen Backtest.
Multivariate Prognosen. Marginalverteilungen je Asset reichen fur das Portfoliorisiko nicht aus; entscheidend ist der gemeinsame Rand, und Copula-Modelle fur gemeinsames Risiko quantifizieren, wie stark Unabhaengigkeitsannahmen ihn unterschaetzen.
Nachgelagerte Nutzer. VaR und Expected Shortfall ergeben sich aus einer stichprobenbasierten Prognose unmittelbar als Quantil und bedingter Randmittelwert - Definitionen und Monte-Carlo-Rezept stehen in Copula-Modellen fur gemeinsames Risiko. Kelly-Sizing ist das eine, was nicht kostenlos entsteht: Ein Kelly-Anteil aus einem Prognoseintervall erfordert eine zusaetzliche Annahme uber die Verteilung innerhalb dieses Intervalls, und der Konformal-Artikel spricht sich ausdrucklich dagegen aus, ein Intervallverhaeltnis an anzubauen. Siehe das Kelly-Kriterium fur Strategien, um zu erfahren, was der Anteil tatsaechlich erfordert.
Fazit

Der Bewertungsstapel fur eine probabilistische Prognose ist kurz und nicht verhandelbar: CRPS fur den Score, weil es proper ist und in den Einheiten des Targets liegt; das PIT-Histogramm fur die Diagnose, weil es den Fehlermodus benennt statt nur einen Alarm auszulosen; Coverage pro Horizont fur die Entscheidung, weil genau diese Zahl auf die Positionsgroesse abgebildet wird. Alles, was als "Quantile loss" berichtet wird, ist CRPS bis auf den Faktor 2, also gibt es hier eine Metrik und nicht zwei.
Der unbequeme Teil ist, dass diese gesamte Maschinerie dazu dient festzustellen, dass ein Modell schlechter ist als erhofft. Genau das ist der Sinn. Ein Modell, das seine Intervalle ehrlich verbreitert, wenn es etwas nicht weiss, ist strikt nuetzlicher als eines, das selbstsicher schmal bleibt. Der einzige Weg, beide zu unterscheiden, ist eine korrekte Bewertung.
Literatur
- 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.