Konforme Prädiktion für risikobewusste Positionsgrößenbestimmung
Jede Formel zur Positionsgrößenbestimmung braucht eine Schätzung der Unsicherheit. Das Kelly-Kriterium braucht eine Gewinnwahrscheinlichkeit und ein Auszahlungsverhältnis (siehe Das Kelly-Kriterium für Strategien). Mean-Variance-Optimierung braucht eine Kovarianzmatrix. VaR braucht eine Renditeverteilung. All das erfordert Annahmen über den datengenerierenden Prozess — Annahmen, die Finanzmärkte routinemäßig verletzen.
Konforme Prädiktion bietet etwas anderes: Prognoseintervalle mit Abdeckungsgarantien für endliche Stichproben, ohne jegliche parametrische Verteilungsannahmen. Wenn Sie 90 % Abdeckung verlangen, bekommen Sie mindestens 90 % Abdeckung — unabhängig davon, ob die Renditen gaußverteilt, fat-tailed, schief oder heteroskedastisch sind. Die einzige Voraussetzung ist Austauschbarkeit (exchangeability) — oder schwächere Bedingungen, wie wir noch sehen werden.
Dieser Beitrag behandelt die Theorie, die wichtigsten Varianten und eine praktische Implementierung für die Positionsgrößenbestimmung in Python.
Die Kernidee: Nonkonformitäts-Scores

Konforme Prädiktion funktioniert, indem gemessen wird, wie "seltsam" eine neue Beobachtung im Verhältnis zu vergangenen Daten ist. Die Seltsamkeit wird durch einen Nonkonformitäts-Score quantifiziert — eine beliebige Funktion, die misst, wie schlecht ein Datenpunkt zum Muster der übrigen Daten passt.
Für Regression (Vorhersage eines kontinuierlichen Werts wie Renditen) ist der einfachste Nonkonformitäts-Score das absolute Residuum:
wobei ein beliebiger Punktschätzer ist (lineare Regression, Random Forest, neuronales Netz — es spielt keine Rolle) und ein Datenpunkt ist.
Die zentrale Erkenntnis: Wenn die Datenpunkte austauschbar sind, dann ist der Rang von unter gleichverteilt über . Dies ist eine rein kombinatorische Tatsache — sie erfordert keinerlei Annahmen über die Verteilung von oder .
Aus dieser Rang-Gleichverteilung können wir Prognoseintervalle mit endlicher-Stichproben-Abdeckung konstruieren.
Split-Conformal-Prädiktion

Split-Conformal-Prädiktion (Papadopoulos et al., 2002; Lei et al., 2018) ist die praktikabelste Variante. Der Algorithmus ist einfach:
Schritt 1. Teile die Daten in ein Trainingsset und ein Kalibrierungsset .
Schritt 2. Fitte ein beliebiges Modell auf .
Schritt 3. Berechne Nonkonformitäts-Scores auf dem Kalibrierungsset:
Schritt 4. Für ein gewünschtes Fehlabdeckungsniveau nimm als das -empirische Quantil von . Konkret ist das das -kleinste Residuum (und , wann immer , d. h. bei sehr kleinem ).
Schritt 5. Das Prognoseintervall für einen neuen Punkt ist:
Die Abdeckungsgarantie
Unter Austauschbarkeit der Kalibrierungsdaten und des neuen Testpunkts:
Dies ist eine Garantie für endliche Stichproben — keine asymptotische Näherung. Sie gilt für jedes Modell , jede Datenverteilung und jede Stichprobengröße . Ist ein miserabler Prädiktor, werden die Intervalle einfach breiter. Die Abdeckungsgarantie bleibt trotzdem bestehen.
Es gibt auch eine Obergrenze, wenn die Scores keine Bindungen (ties) aufweisen: , sodass die Abdeckung nicht unnötig konservativ ist.
Warum das für den Handel wichtig ist
Traditionelle Prognoseintervalle, etwa aus einer linearen Regression, setzen gaußsche Fehler voraus. Ein gaußsches Intervall, das auf dem Großteil der Daten kalibriert wurde, kann die Ränder stark falsch einschätzen, wenn die wahren Residuen dickschwänzig sind (z. B. Student- mit wenigen Freiheitsgraden): Die zentrale Masse ist dünner als bei Gauß, sodass ein varianz-angepasstes gaußsches Intervall nahe der Mitte überdeckt, aber in den Rändern unterdeckt — ein an die Ränder angepasstes Intervall macht das Gegenteil. Der Punkt ist nicht eine einzelne magische Zahl — sondern dass die realisierte Abdeckung eines parametrischen Intervalls von einer Verteilungsannahme abhängt, die Sie nicht überprüft haben.
Konforme Prognoseintervalle umgehen dies. Sie verbreitern sich automatisch, wenn das Modell unsicher ist, und behalten die marginale Abdeckung bei, unabhängig von der wahren Fehlerverteilung. Für einen Trader bedeutet das:
- Wenn Sie Positionen umgekehrt proportional zur Intervallbreite bemessen, reduzieren Sie automatisch das Exposure, wenn das Modell unsicher ist.
- Die Abdeckungsgarantie bedeutet, dass Ihre Risikoschätzungen ehrlich sind — wenn Sie sagen "90 % der realisierten Renditen werden in dieses Intervall fallen", ist diese Aussage statistisch gültig (marginal, unter Austauschbarkeit).
Full Conformal und Jackknife+
Split-Conformal ist einfach, verschwendet aber Daten: Das Kalibrierungsset kann nicht zum Training verwendet werden. Zwei Alternativen adressieren dies.
Full-Conformal-Prädiktion
Full-Conformal-Prädiktion (Vovk et al., 2005) nutzt alle Daten sowohl für Training als auch Kalibrierung. Für jeden Kandidatenwert von :
- Erweitere den Datensatz um .
- Fitte das Modell auf dem erweiterten Datensatz neu.
- Berechne alle Nonkonformitäts-Scores.
- Nimm in die Prognosemenge auf, wenn der Score für nicht zu extrem ist.
Die Prognosemenge ist:
wobei die mit dem erweiterten Datensatz berechneten Nonkonformitäts-Scores sind.
Full Conformal liefert die engsten Intervalle, ist aber für die meisten Modelle rechnerisch untragbar — man muss das Modell für jeden Kandidaten auf einem Gitter neu fitten. Für eine Renditevorhersage könnte das Tausende von Refits pro Vorhersage bedeuten.
Jackknife+ (Barber et al., 2021)
Der Jackknife+ schafft einen Ausgleich. Er nutzt Leave-One-Out-(LOO-)Residuen, berücksichtigt dabei aber die Variabilität des gefitteten Modells über die LOO-Folds hinweg.
Sei das Modell, das auf allen Daten außer Punkt trainiert wurde. Definiere den LOO-Nonkonformitäts-Score mit dem einzelnen absoluten Residuum:
Das Jackknife+-Prognoseintervall wird dann aus den LOO-Vorhersagen am Testpunkt gebildet, erweitert um diese Residuen:
Hier bezeichnet den -kleinsten Wert der Menge , und den -kleinsten Wert. Die untere Grenze subtrahiert das Residuum von jeder LOO-Vorhersage; die obere Grenze addiert es. Diese Asymmetrie ist der ganze Punkt — beide Grenzen auf zu reduzieren, würde die untere Grenze über die Vorhersage setzen, was falsch ist.
Der Jackknife+ liefert eine Abdeckungsgarantie von mindestens (etwas schwächer als das von Split-Conformal), nutzt aber alle Daten sowohl für Training als auch Kalibrierung. In der Praxis liegt die Abdeckung typischerweise nahe bei .
Für Handelsmodelle, die auf begrenzten Daten trainiert werden (z. B. regimespezifische Modelle mit nur wenigen hundert Beobachtungen), ist der Jackknife+ oft die beste Wahl — er opfert keine knappen Daten für die Kalibrierung. Die Kosten sind Modell-Refits.
Das Problem mit Finanzzeitreihen: Nicht-Austauschbarkeit

Die Standard-Konformitätsgarantie erfordert Austauschbarkeit: die gemeinsame Verteilung von ist invariant unter Permutationen. Für i.i.d.-Daten gilt dies trivial.
Finanzzeitreihen sind nicht austauschbar. Renditen zeigen:
- Volatilitätsclustering: Perioden hoher Volatilität folgen auf Perioden hoher Volatilität (GARCH-Effekte).
- Momentum und Mean Reversion: Autokorrelation in Renditen oder quadrierten Renditen.
- Regimewechsel: Die Verteilung verschiebt sich mit der Zeit (Bullen- vs. Bärenmärkte).
Wenn Sie Split-Conformal naiv auf eine Zeitreihe anwenden — mit einer zufälligen Kalibrierungsaufteilung —, verletzen Sie die zeitliche Struktur. Die Kalibrierungs-Scores aus einem ruhigen Jahr 2017 spiegeln nicht die Unsicherheit eines volatilen Jahres 2020 wider. Ihre Abdeckungsgarantie bricht zusammen.
Adaptive Conformal Inference (ACI)

Gibbs und Candes (2021, NeurIPS) führten Adaptive Conformal Inference (ACI) ein, um Verteilungsverschiebungen und nicht-austauschbare Daten zu handhaben. Die Idee ist elegant: Statt ein festes Abdeckungsniveau zu verwenden, passt man das Ziel-Fehlabdeckungsniveau online an, basierend darauf, ob die jüngsten Intervalle das tatsächliche Ergebnis abgedeckt haben, und leitet das Quantil bei jedem Schritt neu aus der Score-Verteilung ab.
Der ACI-Algorithmus
ACI verschiebt nicht direkt die Intervallbreite. Es unterhält einen adaptiven Fehlabdeckungsparameter und berechnet daraus das konforme Quantil neu. Bei jedem Zeitschritt :
- Berechne den konformen Schwellenwert als das empirische -Quantil der aktuellen Residuenmenge (die Kalibrierungs-Scores plus alle bisher realisierten Scores): .
- Beobachte die Merkmale , erzeuge das Intervall .
- Beobachte den tatsächlichen Wert und berechne den Fehlerindikator .
- Aktualisiere das Niveau (nicht die Breite):
wobei eine Schrittweite ist und die Ziel-Fehlabdeckung. Wenn ein Intervall verfehlte (), schrumpft , was das nächste Quantil nach oben drückt und das Intervall verbreitert; wenn es abdeckte, wächst und die Intervalle verengen sich. Entscheidend: ist hier in Wahrscheinlichkeitseinheiten — es verschiebt ein Niveau in , nicht den Schwellenwert in rohen Rendite-Einheiten — sodass dasselbe sinnvoll funktioniert, egal ob Residuen in der Größenordnung von liegen oder nicht.
Abdeckungsgarantie für ACI
ACI liefert eine langfristige Abdeckungsgarantie, die nicht von einem Verteilungsmodell abhängt:
Da auf begrenzt ist, ist der Zähler durch mal eine Konstante beschränkt, sodass die rechte Seite ist und die empirische Fehlabdeckungshäufigkeit gegen konvergiert. Die präzise Aussage: ACI garantiert, dass die langfristige empirische Fehlabdeckungshäufigkeit für beliebige (auch adversarielle) Sequenzen gegen konvergiert, sofern die angepassten Niveaus beschränkt bleiben — was das Clipping erzwingt. Es ist eine Garantie für die Abdeckungshäufigkeit, nicht für die Informativität der Intervalle: unter einer wirklich adversariellen Sequenz können die Intervalle uninformativ breit werden, während sie das Abdeckungsziel dennoch erreichen.
Dynamically-Tuned ACI (DtACI)
Gibbs und Candes (2024, JMLR) verfeinerten ACI mit dynamischer Anpassung der Schrittweite . Statt fest zu wählen, unterhalten sie ein Kandidatenset und kombinieren diese über eine Expertenaggregationsregel, wobei das bevorzugt wird, dessen jüngste Abdeckung dem Ziel am nächsten kommt.
Dies adressiert ein praktisches Problem: Ein großes passt sich schnell an Regimewechsel an, erzeugt aber volatile Intervallbreiten; ein kleines ist stabil, aber langsam in der Anpassung. DtACI gleicht diese Abwägung automatisch aus.
Warum das für den Handel wichtig ist
Betrachten Sie eine Market-Making-Strategie, die ein Rendite-Prognosemodell nutzt. In ruhigen Märkten sind die konformen Intervalle eng — das Modell ist zuversichtlich, und Sie können größere Positionen eingehen. Wenn die Volatilität ansteigt (Earnings-Saison, FOMC-Ankündigungen, geopolitische Schocks), passt sich das ACI-Niveau an, und die Intervalle verbreitern sich innerhalb weniger Zeitschritte. Ihre Positionsgröße schrumpft entsprechend, ohne ein explizites Volatilitätsmodell oder eine Regime-Erkennungslogik.
Dies ist Unsicherheitsquantifizierung als erstklassiges Signal, nicht als nachträglicher Gedanke.
Positionsgrößenbestimmung mit konformen Intervallen

Verbinden wir nun die konforme Prädiktion mit konkreter Positionsgrößenbestimmung. Die Schlüsselvariable ist die halbe Breite des Prognoseintervalls relativ zum symmetrischen Fall des absoluten Residuums. Mit dem symmetrischen Intervall ist die volle Breite . Um Formeln und Code konsistent zu halten, messen wir durchgehend alles gegen die volle Breite .
Inverse-Breite-Bemessung
Der einfachste Ansatz: Bemessung umgekehrt proportional zur Intervallbreite.
wobei eine Skalierungskonstante ist, kalibriert auf Ihr Risikobudget. Wenn das Modell zuversichtlich ist (enges Intervall), gehen Sie eine größere Position ein. Bei Unsicherheit (breites Intervall) eine kleinere.
Dies ist analog zum Volatility Targeting (), aber mit einem entscheidenden Unterschied: Die konforme Intervallbreite ist ein verteilungsfreies Unsicherheitsmaß, keine parametrische Volatilitätsschätzung. Sie erfasst die prädiktive Unsicherheit unter der Abdeckungsgarantie, nicht nur die Renditevarianz.
Edge-Ratio-Bemessung und der No-Trade-Filter
Reine Inverse-Breite-Bemessung ignoriert die Stärke des Signals selbst. Eine natürliche Verfeinerung skaliert nach dem Edge Ratio — der Punktvorhersage relativ zur Intervallbreite:
Dies ist ein konformes Analogon zu einem Signal-Rausch-Verhältnis: erwartete Rendite geteilt durch ein verteilungsfreies Unsicherheitsmaß. Wir nutzen es sowohl für die Bemessung als auch für einen No-Trade-Filter.
Der Filter ist prinzipienbasiert. Wenn das Intervall die Null überspannt,
dann schließt das -Prognoseintervall sowohl positive als auch negative Renditen ein — die realisierte Rendite kann plausibel das entgegengesetzte Vorzeichen Ihrer Vorhersage haben. Definieren Sie einen minimalen Edge-Schwellenwert und handeln Sie nur, wenn . Beachten Sie, dass der geometrische Gehalt von " groß genug, dass das Intervall die Null nicht mehr überspannt" genau ist (da das Intervall die Null verlässt, wenn ). Wählen Sie auf der tatsächlichen Skala von per Backtesting; für Tagesrendite-Residuen liegt üblicherweise deutlich unter , sodass ein winziges nahezu alle Trades zulassen kann und ein großes keinen einzigen. Kalibrieren Sie es auf Ihre Daten.
Zum "Conformal Kelly"
Es ist verlockend, konforme Intervalle an den Kelly-Bruch anzuschrauben. Aber ist bereits ein vollständiger, beschränkter Bruch, der aus einer Gewinnwahrscheinlichkeit und einem Auszahlungsverhältnis abgeleitet wird; ihn mit einem unbeschränkten Verhältnis wie zu multiplizieren, hat keine entscheidungstheoretische Rechtfertigung — es kann 1 überschreiten oder das Vorzeichen unabhängig von umkehren, und es zählt den Edge, den bereits erfasst, doppelt. Deshalb präsentieren wir kein "Conformal-Kelly"-Multiplikator.
Wenn Sie Kelly aus dem Intervall ableiten wollen, müssen Sie tatsächlich und daraus ableiten, was eine explizite Annahme über die Verteilung innerhalb des Intervalls erfordert (konforme Intervalle sagen absichtlich nichts darüber aus — siehe Einschränkungen). Zum Beispiel können Sie unter einer angenommenen Form innerhalb des Intervalls und ein Auszahlungsverhältnis aus der Intervallgeometrie annähern — aber weisen Sie diese Annahme deutlich aus, denn sie führt genau das parametrische Commitment wieder ein, das konforme Prädiktion vermeiden sollte.
Die ehrliche, annahmearme Alternative besteht darin, das Edge Ratio als fraktionale Kelly-Schrumpfung zu verwenden: größer bemessen, wenn die erwartete Rendite im Verhältnis zum Intervall groß ist, kleiner bemessen, wenn sie klein ist, und dies zusätzlich zu einer harten Obergrenze anwenden — ausdrücklich als Heuristik, nicht als "den Kelly-Bruch".
Python-Implementierung
Hier eine praktische Implementierung. Wir zeigen sowohl den Split/Prefit-Pfad als auch den zeitlichen (EnbPI-)Pfad, denn der ganze Sinn dieses Beitrags ist, dass Finanzdaten nicht austauschbar sind.
Setup und Datenvorbereitung
import numpy as np
import pandas as pd
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.model_selection import KFold
from mapie.regression import MapieRegressor, MapieTimeSeriesRegressor
from mapie.subsample import BlockBootstrap
def prepare_features(prices: pd.Series, lookback: int = 20) -> pd.DataFrame:
"""Create features from a price series."""
df = pd.DataFrame()
returns = prices.pct_change()
for lag in range(1, lookback + 1):
df[f"ret_lag_{lag}"] = returns.shift(lag)
for window in [5, 10, 20]:
df[f"ret_mean_{window}"] = returns.rolling(window).mean().shift(1)
df[f"ret_std_{window}"] = returns.rolling(window).std().shift(1)
df[f"ret_skew_{window}"] = returns.rolling(window).skew().shift(1)
df["target"] = returns.shift(-1)
return df.dropna()
Split Conformal mit MAPIE (Prefit)
Für Split/Prefit-Conformal erfordert cv="prefit" method="base" (den naiven Split-Conformal-Schätzer). Die Option method="plus" ist der CV+/Jackknife+-Schätzer und ist mit cv="prefit" inkompatibel — er benötigt stattdessen ein Cross-Validation-Objekt. Wir verwenden hier die korrekte Kombination und standardisieren die Bemessung auf die volle Breite.
def split_conformal_sizing(
prices: pd.Series,
alpha: float = 0.1,
k: float = 1e-3, # scaling constant, in width units
max_position: float = 1.0,
min_edge: float = 0.05, # threshold on |pred| / width
) -> pd.DataFrame:
"""
Position sizing using split (prefit) conformal prediction intervals.
Sizing rule (consistent with the prose):
edge_t = |pred_t| / width_t
size_t = clip(k / width_t, 0, max_position) # inverse-width
size_t = 0 if edge_t < min_edge # no-trade filter
"""
df = prepare_features(prices)
X = df.drop(columns=["target"])
y = df["target"]
n_train = int(len(X) * 0.6)
n_cal = int(len(X) * 0.2)
X_train, y_train = X.iloc[:n_train], y.iloc[:n_train]
X_cal, y_cal = X.iloc[n_train:n_train + n_cal], y.iloc[n_train:n_train + n_cal]
X_test, y_test = X.iloc[n_train + n_cal:], y.iloc[n_train + n_cal:]
base_model = GradientBoostingRegressor(
n_estimators=200, max_depth=3, learning_rate=0.05, random_state=42,
)
base_model.fit(X_train, y_train)
mapie = MapieRegressor(estimator=base_model, cv="prefit", method="base")
mapie.fit(X_cal, y_cal)
y_pred, y_intervals = mapie.predict(X_test, alpha=alpha)
lower = y_intervals[:, 0, 0]
upper = y_intervals[:, 1, 0]
width = upper - lower
raw_size = k / np.where(width > 0, width, np.inf)
position_size = np.clip(raw_size, 0.0, max_position)
edge_ratio = np.abs(y_pred) / np.where(width > 0, width, np.inf)
position_size = np.where(edge_ratio < min_edge, 0.0, position_size)
position_size = position_size * np.sign(y_pred)
return pd.DataFrame({
"prediction": y_pred,
"lower": lower,
"upper": upper,
"width": width,
"edge_ratio": edge_ratio,
"position_size": position_size,
"actual": y_test.values,
}, index=X_test.index)
Zeitreihen-Conformal mit EnbPI
Da Renditen nicht austauschbar sind, ist der obige zufällige/Prefit-Split nur eine Baseline. MAPIEs MapieTimeSeriesRegressor mit method="enbpi" (Xu & Xie, 2021) nutzt Block-Bootstrap und Residuen-Updating, konzipiert für zeitliche Abhängigkeit. Dies ist das Werkzeug, das zum eigentlichen Argument dieses Beitrags passt.
def enbpi_sizing(
prices: pd.Series,
alpha: float = 0.1,
k: float = 1e-3,
max_position: float = 1.0,
min_edge: float = 0.05,
) -> pd.DataFrame:
"""Position sizing with EnbPI (block-bootstrap, time-series conformal)."""
df = prepare_features(prices)
X = df.drop(columns=["target"])
y = df["target"]
n_train = int(len(X) * 0.7)
X_train, y_train = X.iloc[:n_train], y.iloc[:n_train]
X_test, y_test = X.iloc[n_train:], y.iloc[n_train:]
base_model = GradientBoostingRegressor(
n_estimators=200, max_depth=3, learning_rate=0.05, random_state=42,
)
cv = BlockBootstrap(n_resamplings=30, length=20, overlapping=False, random_state=42)
mapie_ts = MapieTimeSeriesRegressor(base_model, method="enbpi", cv=cv, agg_function="mean")
mapie_ts.fit(X_train, y_train)
y_pred, y_intervals = mapie_ts.predict(X_test, alpha=alpha, ensemble=True)
lower = y_intervals[:, 0, 0]
upper = y_intervals[:, 1, 0]
width = upper - lower
raw_size = k / np.where(width > 0, width, np.inf)
position_size = np.clip(raw_size, 0.0, max_position)
edge_ratio = np.abs(y_pred) / np.where(width > 0, width, np.inf)
position_size = np.where(edge_ratio < min_edge, 0.0, position_size)
position_size = position_size * np.sign(y_pred)
return pd.DataFrame({
"prediction": y_pred, "lower": lower, "upper": upper,
"width": width, "edge_ratio": edge_ratio,
"position_size": position_size, "actual": y_test.values,
}, index=X_test.index)
Adaptive Conformal Inference (Online)
Für den Live-Handel implementieren wir echtes ACI von Grund auf: das Fehlabdeckungs-Niveau pflegen, additiv aktualisieren und das Quantil bei jedem Schritt neu aus der Residuenmenge ableiten. Zwei Details für endliche Stichproben sind wichtig:
- Verwenden Sie die Ordnungsstatistik, nicht ein interpoliertes Quantil.
np.quantileinterpoliert standardmäßig, was gerade unter den erforderlichen Rang fallen und zur Unterdeckung führen kann; übergeben Siemethod="higher"(äquivalent"inverted_cdf"). - Wenn der erforderliche Rang übersteigt (kleines , hohe Zielabdeckung), ist der korrekte Schwellenwert (Intervall = die ganze Zahlengerade), keine Klammerung auf das größte Residuum. Klammerung bricht die -Garantie stillschweigend.
class AdaptiveConformalSizer:
"""
Online position sizing with Adaptive Conformal Inference (Gibbs & Candes,
2021). Updates the miscoverage LEVEL alpha_t and re-derives the quantile
from the residual set each step -- gamma is in probability units.
"""
def __init__(self, base_model, alpha=0.1, gamma=0.02,
max_position=1.0, min_edge=0.05, k=1e-3):
self.base_model = base_model
self.alpha_target = alpha # target miscoverage
self.alpha_t = alpha # adaptive miscoverage level
self.gamma = gamma # step size, in [0, 1] units
self.max_position = max_position
self.min_edge = min_edge
self.k = k
self.residuals = []
self.q_hat = np.inf
self.coverage_history = []
@staticmethod
def _conformal_quantile(residuals, alpha_t):
"""(1 - alpha_t) conformal quantile via the order statistic."""
n = len(residuals)
if n == 0:
return np.inf
rank = int(np.ceil((1.0 - alpha_t) * (n + 1)))
if rank > n: # required order statistic does not exist
return np.inf # -> interval is the whole line
level = rank / n
return float(np.quantile(residuals, min(level, 1.0), method="higher"))
def calibrate(self, X_cal, y_cal):
preds = self.base_model.predict(X_cal)
self.residuals = list(np.abs(np.asarray(y_cal) - preds))
self.q_hat = self._conformal_quantile(self.residuals, self.alpha_t)
def predict_and_size(self, X_t) -> dict:
mu_hat = self.base_model.predict(np.asarray(X_t).reshape(1, -1))[0]
lower, upper = mu_hat - self.q_hat, mu_hat + self.q_hat
width = upper - lower # = 2 * q_hat
edge_ratio = abs(mu_hat) / width if np.isfinite(width) and width > 0 else 0.0
if edge_ratio < self.min_edge:
size = 0.0
else:
size = min(self.k / width, self.max_position) if width > 0 else 0.0
return {
"prediction": mu_hat, "lower": lower, "upper": upper,
"width": width, "edge_ratio": edge_ratio,
"position_size": size * np.sign(mu_hat),
"alpha_t": self.alpha_t, "q_hat": self.q_hat,
}
def update(self, X_t, y_t: float):
"""ACI update: adapt the LEVEL, then re-derive the quantile."""
mu_hat = self.base_model.predict(np.asarray(X_t).reshape(1, -1))[0]
residual = abs(y_t - mu_hat)
covered = int(residual <= self.q_hat)
err_t = 1 - covered
self.alpha_t = float(np.clip(
self.alpha_t + self.gamma * (self.alpha_target - err_t), 0.0, 1.0
))
self.residuals.append(residual)
self.q_hat = self._conformal_quantile(self.residuals, self.alpha_t)
self.coverage_history.append(covered)
@property
def running_coverage(self) -> float:
if not self.coverage_history:
return float("nan")
return float(np.mean(self.coverage_history))
Alles zusammen: Backtest-Loop
def backtest_aci_sizing(prices: pd.Series, alpha=0.1, gamma=0.02) -> pd.DataFrame:
"""Backtest position sizing with Adaptive Conformal Inference."""
df = prepare_features(prices)
X = df.drop(columns=["target"]).values
y = df["target"].values
index = df.index
n_train = int(len(X) * 0.5)
n_cal = int(len(X) * 0.2)
X_train, y_train = X[:n_train], y[:n_train]
X_cal, y_cal = X[n_train:n_train + n_cal], y[n_train:n_train + n_cal]
X_test, y_test = X[n_train + n_cal:], y[n_train + n_cal:]
test_index = index[n_train + n_cal:]
model = GradientBoostingRegressor(
n_estimators=200, max_depth=3, learning_rate=0.05, random_state=42,
)
model.fit(X_train, y_train)
sizer = AdaptiveConformalSizer(base_model=model, alpha=alpha, gamma=gamma)
sizer.calibrate(X_cal, y_cal)
records = []
for i in range(len(X_test)):
result = sizer.predict_and_size(X_test[i])
result["actual"] = y_test[i]
result["pnl"] = result["position_size"] * y_test[i]
records.append(result)
sizer.update(X_test[i], y_test[i]) # online residual + level update
results = pd.DataFrame(records, index=test_index)
results["cumulative_pnl"] = results["pnl"].cumsum()
results["running_coverage"] = (
((results["lower"] <= results["actual"]) &
(results["actual"] <= results["upper"])).expanding().mean()
)
return results
Auswertung der Ergebnisse
def evaluate(results: pd.DataFrame, alpha: float = 0.1):
"""Print evaluation metrics for conformal position sizing."""
covered = ((results["actual"] >= results["lower"]) &
(results["actual"] <= results["upper"]))
print(f"Target coverage: {1 - alpha:.1%}")
print(f"Empirical coverage: {covered.mean():.1%}")
print(f"Mean interval width: {results['width'].mean():.6f}")
print(f"Median position size: {results['position_size'].abs().median():.4f}")
print(f"Fraction no-trade: {(results['position_size'] == 0).mean():.1%}")
print(f"Total PnL (bps): {results['pnl'].sum() * 10000:.1f}")
sd = results['pnl'].std()
sharpe = results['pnl'].mean() / sd * np.sqrt(252) if sd > 0 else float('nan')
print(f"Sharpe (annualized): {sharpe:.2f}")
Praktische Überlegungen
Auswahl des Nonkonformitäts-Scores
Das absolute Residuum ist der Standard, setzt aber voraus, dass das Prognoseintervall symmetrisch um die Punktvorhersage sein soll. Für Finanzrenditen ergeben asymmetrische Intervalle oft mehr Sinn:
- Conformalized Quantile Regression (CQR): Fitte Quantil-Regressoren auf den Niveaus und , dann konformalisiere (Romano et al., 2019). Die Intervalle passen ihre Form an die lokale Verteilung an — breiter auf der Abwärtsseite während Drawdowns, breiter auf der Aufwärtsseite während Rallyes. (Mit CQR ist das Intervall nicht mehr symmetrisch, sodass die tatsächliche Breite oben-minus-unten ist — verwenden Sie überall weiterhin als Nenner.)
- Normalisierte Scores: , wobei eine lokale Volatilitätsschätzung ist. Dies erzeugt bedingt adaptive Intervalle — enger in Niedrigvolatilitätsregimen, breiter in Hochvolatilitätsregimen — bei gleichzeitiger Beibehaltung der marginalen Abdeckung.
Größe des Kalibrierungssets
Die Abdeckungsgarantie von Split-Conformal gilt für jede Kalibrierungssetgröße , aber die Intervallbreite nimmt mit wachsendem ab. Für sehr kleines existiert die erforderliche Ordnungsstatistik möglicherweise nicht; in diesem Fall ist der ehrliche Schwellenwert (ein uninformatives, aber gültiges Intervall) — eine Klammerung auf das größte Residuum hebt die Garantie still auf. Für praktischen Handel:
- Kalibrierungspunkte liefern angemessen enge Intervalle.
- ist für stabile Quantilschätzung vorzuziehen.
- Bei ACI dient das Kalibrierungsset nur der Initialisierung; die Online-Niveau-Updates übernehmen den Rest.
Häufigkeit des Neutrainings
Das Basismodell kann veralten. Zwei Ansätze:
- Regelmäßig neu trainieren (z. B. monatlich) und das konforme Quantil neu kalibrieren.
- ACI verwenden und das adaptive Niveau die Modellveralterung kompensieren lassen — die Intervalle verbreitern sich automatisch, während die Residuen des Modells wachsen.
Option 2 ist einfacher und überraschend effektiv. Die konforme Schicht wirkt wie ein Sicherheitsnetz: Selbst wenn sich das Modell verschlechtert, konvergiert die langfristige ACI-Abdeckungshäufigkeit weiterhin gegen das Ziel.
Transaktionskosten
Konforme Intervalle interagieren auf nützliche Weise mit Transaktionskosten. Wenn Intervalle breit sind (hohe Unsicherheit), sind Positionen klein, sodass der Umsatz gering ist. Wenn sich Intervalle verengen (das Modell ist zuversichtlich), wachsen die Positionen — aber das Modell liegt mit größerer Wahrscheinlichkeit richtig, sodass sich der Umsatz lohnt.
Sie können Transaktionskosten auch direkt in den No-Trade-Filter einbeziehen:
Dies stellt sicher, dass Sie nur handeln, wenn der erwartete Nettoedge einen Schwellenwert übersteigt, der auf die konforme Breite skaliert ist — mit demselben -Nenner wie überall sonst.
Vergleich mit traditionellen Methoden
| Eigenschaft | Gauß-KI | Bootstrap-KI | Konformes PI |
|---|---|---|---|
| Verteilungsannahme | Normale Fehler | i.i.d. + asymptotisch | Keine (Austauschbarkeit) |
| Endliche-Stichproben-Garantie | Nein | Nein | Ja |
| Funktioniert mit jedem Modell | Nein | Ja | Ja |
| Passt sich an Heteroskedastizität an | Nein | Teilweise | Mit CQR / normalisierten Scores |
| Handhabt Verteilungsverschiebung | Nein | Nein | ACI / EnbPI-Variante |
| Rechenkosten | Niedrig | Mittel | Split: niedrig; Jackknife+: Refits; Full: untragbar |
Der Bootstrap ist nur in seiner Garantie "asymptotisch"; er setzt weiterhin i.i.d./austauschbare Daten und Glätte voraus, sodass seine Verteilungsannahme-Zelle nicht "annahmefrei" ist. Und die einzelne Spalte "konform" verbirgt sehr unterschiedliche Kosten über die Varianten hinweg, die die Kostenzeile nun aufschlüsselt.
Einschränkungen
Konforme Prädiktion ist keine Magie. Wichtige Einschränkungen für den Handel:
-
Marginale, keine bedingte Abdeckung. Die Garantie ist, dass marginal gilt — gemittelt über die Zufälligkeit in sowohl als auch . Für einen spezifischen Bedingungswert kann die lokale Abdeckung über oder unter liegen. Conformalized Quantile Regression adressiert dies teilweise.
-
Austauschbarkeit ist eine echte Voraussetzung. Für Split-Conformal müssen die Kalibrierungsdaten und der Testpunkt austauschbar sein. Finanzdaten sind es nicht. ACI und EnbPI lockern dies zu einer langfristigen Garantie, aber die kurzfristige Abdeckung kann abweichen.
-
Intervallbreite ist keine Wahrscheinlichkeitsdichte. Ein konformes Intervall sagt Ihnen, wo mit Wahrscheinlichkeit liegen wird, aber es sagt nichts über die Verteilung innerhalb des Intervalls aus. Es ist kein Ersatz für eine vollständige prädiktive Verteilung — genau deshalb erfordert die Ableitung eines Kelly- und - daraus eine zusätzliche, explizite Annahme.
-
Müll rein, breiter raus. Ein schlechtes Basismodell erzeugt breite Intervalle. Konforme Prädiktion garantiert Abdeckung, aber nicht Nützlichkeit. Wenn Ihr Modell keine prädiktive Kraft hat, werden die Intervalle so breit, dass der Positionsgrößen-Bemesser nie handelt.
Zusammenfassung
Konforme Prädiktion bietet ein rigoroses, verteilungsfreies Framework für Unsicherheitsquantifizierung, das sich natürlich in die Positionsgrößenbestimmung einfügt:
- Split Conformal für statische, Offline-Kalibrierung mit endlicher-Stichproben-Abdeckung.
- Jackknife+, wenn Kalibrierungsdaten knapp sind und Sie alle Beobachtungen nutzen wollen (auf Kosten von Refits).
- Adaptive konforme Inferenz / EnbPI für Online-Handel mit nicht-stationären Märkten.
- Positionsgrößenbestimmung über inverse Intervallbreite und einen Edge-Ratio-No-Trade-Filter — und, falls Sie zu Kelly übergehen, nur nachdem Sie und ehrlich abgeleitet haben, nicht über einen unbegründeten Multiplikator.
Der wesentliche Vorteil gegenüber parametrischen Alternativen: Sie müssen nie eine parametrische Verteilungsannahme spezifizieren oder validieren. Die Intervalle sind konstruktionsbedingt ehrlich (marginal, unter Austauschbarkeit). Für einen systematischen Trader bedeutet das eine Risikoquelle weniger im Modell — und in einem Geschäft, in dem Modellrisiko existenziell ist, zählt das.
Referenzen:
- Papadopoulos, H., Proedrou, K., Vovk, V., Gammerman, A. (2002). Inductive confidence machines for regression. ECML.
- Vovk, V., Gammerman, A., Shafer, G. (2005). Algorithmic Learning in a Random World. Springer.
- Lei, J., G'Sell, M., Rinaldo, A., Tibshirani, R.J., Wasserman, L. (2018). Distribution-free predictive inference for regression. JASA.
- Xu, C., Xie, Y. (2021). Conformal prediction interval for dynamic time-series (EnbPI). ICML.
- Barber, R.F., Candes, E.J., Ramdas, A., Tibshirani, R.J. (2021). Predictive inference with the jackknife+. Annals of Statistics.
- Gibbs, I., Candes, E.J. (2021). Adaptive conformal inference under distribution shift. NeurIPS.
- Gibbs, I., Candes, E.J. (2024). Conformal inference for online prediction with arbitrary distribution shifts. JMLR.
- Romano, Y., Patterson, E., Candes, E.J. (2019). Conformalized quantile regression. NeurIPS.
- Cordier, T. et al. (2022). MAPIE: an open-source library for distribution-free uncertainty quantification. arXiv:2207.12274.
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.