जोखिम-सजग पोज़िशन साइज़िंग के लिए कॉन्फ़ॉर्मल प्रेडिक्शन
हर पोज़िशन साइज़िंग फ़ॉर्मूले को अनिश्चितता के अनुमान की ज़रूरत होती है। Kelly criterion को जीतने की संभावना और payoff ratio चाहिए (देखें रणनीतियों के लिए Kelly criterion)। Mean-variance optimization को covariance matrix चाहिए। VaR को returns के distribution की ज़रूरत होती है। इन सभी को डेटा-जनरेटिंग प्रक्रिया के बारे में मान्यताओं की आवश्यकता होती है — ऐसी मान्यताएं जिन्हें वित्तीय बाज़ार अक्सर तोड़ देते हैं।
Conformal prediction कुछ अलग पेश करता है: बिना किसी पैरामीट्रिक वितरण संबंधी मान्यता के, फ़िनाइट-सैंपल कवरेज गारंटी वाले प्रेडिक्शन इंटरवल्स। यदि आप 90% कवरेज मांगते हैं, तो आपको कम से कम 90% कवरेज मिलता है — चाहे returns Gaussian हों, fat-tailed हों, skewed हों, या heteroskedastic हों। एकमात्र आवश्यकता exchangeability है (या, जैसा हम देखेंगे, इससे कमज़ोर शर्तें)।
यह लेख सिद्धांत, प्रमुख वेरिएंट्स, और Python में पोज़िशन साइज़िंग के लिए एक व्यावहारिक implementation को कवर करता है।
मूल विचार: Nonconformity Scores

Conformal prediction यह मापकर काम करता है कि एक नया observation पिछले डेटा की तुलना में कितना "अजीब" है। इस अजीबपन को एक nonconformity score द्वारा मापा जाता है — कोई भी फ़ंक्शन जो यह मापता है कि एक data point बाकी डेटा में देखे गए पैटर्न के कितना कम अनुरूप है।
Regression के लिए (जैसे returns जैसी एक continuous value की भविष्यवाणी), सबसे सरल nonconformity score absolute residual है:
जहां कोई भी point predictor है (linear regression, random forest, neural network — इससे फ़र्क नहीं पड़ता) और एक data point है।
मुख्य अंतर्दृष्टि: यदि data points exchangeable हैं, तो में का rank पर uniformly distributed है। यह विशुद्ध रूप से एक combinatorial तथ्य है — इसे या के distribution के बारे में किसी मान्यता की आवश्यकता नहीं है।
इस rank uniformity से, हम फ़िनाइट-सैंपल कवरेज वाले प्रेडिक्शन इंटरवल्स बना सकते हैं।
Split Conformal Prediction

Split conformal prediction (Papadopoulos et al., 2002; Lei et al., 2018) सबसे व्यावहारिक वेरिएंट है। Algorithm सरल है:
चरण 1. डेटा को एक training set और एक calibration set में बांटें।
चरण 2. पर कोई भी model फ़िट करें।
चरण 3. Calibration set पर nonconformity scores की गणना करें:
चरण 4. एक वांछित miscoverage level के लिए, को का empirical quantile मानें। स्पष्ट रूप से, यह -वां सबसे छोटा residual है (और होने पर, यानी बहुत छोटे के लिए, )।
चरण 5. एक नए point के लिए प्रेडिक्शन इंटरवल है:
कवरेज गारंटी
Calibration data और नए test point की exchangeability के तहत:
यह एक फ़िनाइट-सैंपल गारंटी है — कोई asymptotic approximation नहीं। यह किसी भी model , डेटा के किसी भी distribution, और किसी भी sample size के लिए लागू होती है। यदि एक ख़राब predictor है, तो intervals बस अधिक चौड़े होंगे। कवरेज गारंटी फिर भी बनी रहती है।
जब scores में कोई ties न हों तो एक upper bound भी है: , इसलिए कवरेज बेवजह conservative नहीं है।
यह ट्रेडिंग के लिए क्यों मायने रखता है
पारंपरिक प्रेडिक्शन इंटरवल्स, मान लीजिए किसी linear regression से, Gaussian errors मान लेते हैं। डेटा के बल्क पर कैलिब्रेट किया गया एक Gaussian interval, जब वास्तविक residuals fat-tailed होते हैं (जैसे कि कुछ degrees of freedom वाला Student-), तो tails को बुरी तरह गलत आंक सकता है: केंद्रीय द्रव्यमान Gaussian से पतला होता है, इसलिए एक variance-matched Gaussian interval केंद्र के पास over-cover करता है फिर भी tails में under-cover करता है, और एक tail-fit interval इसका उल्टा करता है। मुद्दा एक एकल जादुई संख्या नहीं है — मुद्दा यह है कि एक पैरामीट्रिक interval का realized coverage एक distributional assumption पर निर्भर करता है जिसे आपने verify नहीं किया है।
Conformal प्रेडिक्शन इंटरवल्स इसे टाल देते हैं। ये तब स्वचालित रूप से चौड़े हो जाते हैं जब model अनिश्चित होता है, और असली error distribution की परवाह किए बिना marginal coverage बनाए रखते हैं। एक trader के लिए, इसका मतलब है:
- यदि आप positions का आकार interval width के व्युत्क्रमानुपाती (inversely proportional) रखते हैं, तो जब model अनिश्चित होता है तो आप स्वचालित रूप से exposure कम कर देते हैं।
- कवरेज गारंटी का मतलब है कि आपके risk estimates ईमानदार हैं — यदि आप कहते हैं "90% realized returns इस interval के भीतर आएंगे," तो यह कथन सांख्यिकीय रूप से मान्य है (marginally, exchangeability के तहत)।
Full Conformal और Jackknife+
Split conformal सरल है लेकिन डेटा बर्बाद करता है: calibration set को training के लिए इस्तेमाल नहीं किया जा सकता। दो विकल्प इसे संबोधित करते हैं।
Full Conformal Prediction
Full conformal prediction (Vovk et al., 2005) training और calibration दोनों के लिए सारा डेटा उपयोग करता है। के हर candidate value के लिए:
- Dataset को से augment करें।
- Augmented dataset पर model को refit करें।
- सभी nonconformity scores की गणना करें।
- यदि के लिए score बहुत extreme नहीं है तो को prediction set में शामिल करें।
Prediction set है:
जहां augmented dataset के साथ गणना किए गए nonconformity scores हैं।
Full conformal सबसे tight intervals प्रदान करता है लेकिन अधिकांश models के लिए computationally निषेधात्मक है — आपको grid पर हर candidate के लिए model refit करना होगा। Return prediction के लिए, इसका मतलब हो सकता है प्रति prediction हज़ारों refits।
Jackknife+ (Barber et al., 2021)
Jackknife+ एक संतुलन बनाता है। यह leave-one-out (LOO) residuals का उपयोग करता है लेकिन LOO folds में fitted model की variability को ध्यान में रखता है।
मान लीजिए point को छोड़कर सारे डेटा पर trained model को दर्शाता है। LOO nonconformity score को एकल absolute residual से परिभाषित करें:
Jackknife+ prediction interval फिर test point पर LOO predictions से बनाया जाता है, इन residuals द्वारा widen किया जाता है:
यहां set का -वां सबसे छोटा मान दर्शाता है, और -वां सबसे छोटा मान। Lower bound हर LOO prediction से residual को घटाता है; upper bound उसे जोड़ता है। यह असममिति (asymmetry) ही पूरा मुद्दा है — दोनों bounds को पर collapse करना lower bound को prediction से ऊपर रख देगा, जो गलत है।
Jackknife+ कम से कम की कवरेज गारंटी प्रदान करता है (split conformal के से थोड़ा कमज़ोर), लेकिन यह training और calibration दोनों के लिए सारा डेटा उपयोग करता है। व्यवहार में कवरेज आमतौर पर के करीब होता है।
सीमित डेटा पर trained trading models के लिए (जैसे, सिर्फ कुछ सौ observations वाले regime-specific models), jackknife+ अक्सर सबसे अच्छा विकल्प होता है — यह calibration के लिए दुर्लभ डेटा का त्याग नहीं करता। इसकी क़ीमत है model refits।
वित्तीय Time Series की समस्या: Non-Exchangeability

मानक conformal गारंटी को exchangeability की आवश्यकता होती है: का joint distribution permutations के तहत invariant होता है। i.i.d. डेटा के लिए, यह trivially सत्य है।
वित्तीय time series exchangeable नहीं होती हैं। Returns निम्नलिखित प्रदर्शित करते हैं:
- Volatility clustering: उच्च-volatility periods के बाद उच्च-volatility periods आते हैं (GARCH प्रभाव)।
- Momentum और mean reversion: returns या squared returns में autocorrelation।
- Regime changes: समय के साथ distribution बदलता है (bull बनाम bear markets)।
यदि आप naively split conformal को time series पर लागू करते हैं — random calibration split का उपयोग करते हुए — तो आप temporal structure का उल्लंघन करते हैं। एक शांत 2017 से calibration scores एक volatile 2020 की अनिश्चितता को प्रतिबिंबित नहीं करेंगे। आपकी कवरेज गारंटी टूट जाती है।
Adaptive Conformal Inference (ACI)

Gibbs और Candes (2021, NeurIPS) ने distribution shift और non-exchangeable डेटा से निपटने के लिए Adaptive Conformal Inference (ACI) पेश किया। विचार सुरुचिपूर्ण है: एक fixed coverage level का उपयोग करने के बजाय, हाल के intervals ने वास्तविक outcome को cover किया या नहीं, इसके आधार पर target miscoverage level को ऑनलाइन adapt करें, और प्रत्येक step पर score distribution से quantile को फिर से derive करें।
ACI Algorithm
ACI interval width को सीधे adjust नहीं करता। यह एक adaptive miscoverage parameter बनाए रखता है और इससे conformal quantile को फिर से गणना करता है। प्रत्येक time step पर:
- Conformal threshold की गणना वर्तमान residual set (calibration scores, plus अब तक realized कोई भी scores) के empirical -quantile के रूप में करें: ।
- Features observe करें, interval produce करें।
- वास्तविक मान observe करें और error indicator की गणना करें: ।
- Level को update करें (width को नहीं):
जहां एक step size है और target miscoverage है। यदि एक interval miss हुआ (), तो घटता है, जो अगले quantile को ऊपर धकेलता है और interval को widen करता है; यदि इसने cover किया, तो बढ़ता है और intervals tighten होते हैं। महत्वपूर्ण रूप से, यहां probability units में है — यह में एक level को नज-करता है, raw return units में threshold को नहीं — इसलिए वही समझदारी से व्यवहार करता है चाहे residuals के order पर हों या नहीं।
ACI के लिए कवरेज गारंटी
ACI एक long-run कवरेज गारंटी प्रदान करता है जो किसी distributional model पर निर्भर नहीं करती:
चूंकि को पर clip किया जाता है, numerator एक स्थिरांक के गुना से बाउंडेड है, इसलिए right-hand side है और empirical miscoverage frequency की ओर converge करती है। सटीक कथन: ACI यह गारंटी देता है कि arbitrary (adversarial सहित) sequences के लिए long-run empirical miscoverage frequency की ओर converge करती है, बशर्ते adapted levels bounded रहें — जिसे clip सुनिश्चित करता है। यह coverage frequency की गारंटी है, interval की informativeness की नहीं: एक सच में adversarial sequence के तहत intervals uninformatively wide हो सकते हैं फिर भी coverage target को hit करते हुए।
Dynamically-Tuned ACI (DtACI)
Gibbs और Candes (2024, JMLR) ने step size की dynamic tuning के साथ ACI को परिष्कृत किया। को fix करने के बजाय, वे एक candidate set बनाए रखते हैं और उन्हें एक expert-aggregation rule के माध्यम से combine करते हैं, जो उस को प्राथमिकता देता है जिसका हाल का coverage target के सबसे करीब है।
यह एक व्यावहारिक समस्या को हल करता है: एक बड़ा regime changes के लिए तेज़ी से adapt होता है लेकिन volatile interval widths उत्पन्न करता है; एक छोटा stable है लेकिन adapt करने में धीमा। DtACI इनका स्वचालित रूप से trade-off करता है।
यह ट्रेडिंग के लिए क्यों मायने रखता है
एक market-making strategy पर विचार करें जो return forecasting model का उपयोग करती है। शांत बाज़ारों के दौरान, conformal intervals tight होते हैं — model confident है, और आप बड़े positions ले सकते हैं। जब volatility spike होती है (earnings season, FOMC announcements, geopolitical shocks), ACI level adapt होता है और intervals कुछ time steps के भीतर widen हो जाते हैं। आपकी position sizing उसके जवाब में सिकुड़ती है, बिना किसी explicit volatility model या regime-detection logic के।
यह uncertainty quantification को एक first-class signal के रूप में मानना है, न कि बाद में सोचा गया कुछ।
Conformal Intervals के साथ Position Sizing

अब आइए conformal prediction को concrete position sizing से जोड़ें। मुख्य variable symmetric absolute-residual case के सापेक्ष prediction interval की half-width है। Symmetric interval के साथ, full width है। फ़ॉर्मूलों और code को consistent रखने के लिए, हम पूरे लेख में हर चीज़ को full width के मुकाबले मापते हैं।
Inverse-Width Sizing
सबसे सरल तरीका: size को interval width के व्युत्क्रमानुपाती रखें।
जहां आपके risk budget के अनुसार कैलिब्रेट किया गया एक scaling constant है। जब model confident है (narrow interval), आप एक बड़ी position लेते हैं। जब अनिश्चित है (wide interval), आप एक छोटी लेते हैं।
यह volatility targeting () के समान है, लेकिन एक महत्वपूर्ण अंतर के साथ: conformal interval width एक distribution-free uncertainty measure है, न कि एक parametric volatility estimate। यह coverage गारंटी के तहत predictive uncertainty को capture करता है, सिर्फ return variance को नहीं।
Edge-Ratio Sizing और No-Trade Filter
शुद्ध inverse-width sizing signal की खुद की strength को नज़रअंदाज़ करती है। एक स्वाभाविक refinement edge ratio से scale करता है — interval width के सापेक्ष point prediction:
यह एक signal-to-noise ratio का conformal analog है: expected return को एक distribution-free uncertainty measure से divide किया गया। हम इसे sizing और no-trade filter दोनों के लिए उपयोग करते हैं।
Filter सिद्धांतबद्ध है। यदि interval zero को straddle करता है,
तो prediction interval में positive और negative दोनों returns शामिल हैं — realized return में आपकी prediction के विपरीत sign हो सकता है। एक minimum edge threshold परिभाषित करें और केवल तभी trade करें जब । ध्यान दें कि " इतना बड़ा है कि interval अब zero को straddle नहीं करता" का geometric content ठीक है (क्योंकि interval तब zero को clear करता है जब )। को के वास्तविक scale पर backtesting के माध्यम से चुनें; daily-return residuals के लिए आमतौर पर से काफी नीचे होता है, इसलिए एक बहुत छोटा लगभग सभी trades को स्वीकार कर सकता है और एक बड़ा किसी को भी नहीं। इसे अपने डेटा पर calibrate करें।
"Conformal Kelly" के बारे में
Conformal intervals को Kelly fraction पर जोड़ना आकर्षक है। लेकिन पहले से ही एक जीतने की संभावना और एक payoff ratio से derived एक complete, bounded fraction है; इसे जैसे unbounded ratio से गुणा करने का कोई decision-theoretic justification नहीं है — यह से independently 1 से अधिक हो सकता है या sign flip कर सकता है, और यह उस edge को double-count करता है जिसे पहले से encode करता है। इसलिए हम "conformal Kelly" multiplier प्रस्तुत नहीं करते।
यदि आप interval से Kelly चलाना चाहते हैं, तो आपको वास्तव में इससे और derive करने होंगे, जिसके लिए interval के भीतर distribution के बारे में एक explicit assumption की आवश्यकता है (conformal intervals जानबूझकर इसके बारे में कुछ नहीं कहते — Limitations देखें)। उदाहरण के लिए, interval के भीतर मान ली गई एक shape के तहत आप और interval geometry से एक payoff ratio का अनुमान लगा सकते हैं — लेकिन उस assumption को ज़ोर से flag करें, क्योंकि यह ठीक उसी parametric commitment को फिर से introduce करता है जिससे conformal prediction बचना चाहता था।
ईमानदार, कम-assumption वाला विकल्प है edge ratio को एक fractional-Kelly shrinkage के रूप में उपयोग करना: जब expected return interval के सापेक्ष बड़ा हो तो size बढ़ाएं, जब छोटा हो तो घटाएं, और इसे एक hard cap के ऊपर लागू करें — स्पष्ट रूप से एक heuristic के रूप में, "Kelly fraction" के रूप में नहीं।
Python Implementation
यहां एक व्यावहारिक implementation है। हम split/prefit path और temporal (EnbPI) path दोनों दिखाते हैं, क्योंकि इस लेख का पूरा मुद्दा यही है कि वित्तीय डेटा non-exchangeable है।
Setup और Data Preparation
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()
MAPIE के साथ Split Conformal (Prefit)
Split/prefit conformal के लिए, cv="prefit" को method="base" (naive split-conformal estimator) की आवश्यकता होती है। method="plus" option CV+/jackknife+ estimator है और यह cv="prefit" के साथ incompatible है — इसे इसके बजाय एक cross-validation object चाहिए। हम यहां सही combination का उपयोग करते हैं, और sizing को full width पर standardize करते हैं।
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)
EnbPI के साथ Time-Series Conformal
चूंकि returns non-exchangeable हैं, ऊपर दिया गया random/prefit split केवल एक baseline है। MAPIE का MapieTimeSeriesRegressor, method="enbpi" (Xu & Xie, 2021) के साथ, temporal dependence के लिए designed block bootstrap और residual updating का उपयोग करता है। यह वह tool है जो इस लेख के अपने argument से मेल खाता है।
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)
Live trading के लिए हम true ACI को शुरू से implement करते हैं: miscoverage level बनाए रखें, इसे additively update करें, और हर step पर residual set से quantile को फिर से derive करें। दो finite-sample details महत्वपूर्ण हैं:
- Order statistic का उपयोग करें, interpolated quantile का नहीं।
np.quantileडिफ़ॉल्ट रूप से interpolate करता है, जो आवश्यक rank से थोड़ा नीचे गिर सकता है और undercover कर सकता है;method="higher"(equivalently"inverted_cdf") pass करें। - जब आवश्यक rank से अधिक हो जाए (छोटा , high target coverage), तो सही threshold है (interval = पूरी line), largest residual पर clamp नहीं। Clamping चुपचाप गारंटी को तोड़ देता है।
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))
सब कुछ मिलाकर: 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
परिणामों का मूल्यांकन
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}")
व्यावहारिक विचार
Nonconformity Score का चयन
Absolute residual डिफ़ॉल्ट है, लेकिन यह मानता है कि prediction interval को point prediction के चारों ओर symmetric होना चाहिए। वित्तीय returns के लिए, asymmetric intervals अक्सर अधिक मायने रखते हैं:
- Conformalized Quantile Regression (CQR): levels और पर quantile regressors फ़िट करें, फिर conformalize करें (Romano et al., 2019)। Intervals अपने आकार को local distribution के अनुसार adapt करते हैं — drawdowns के दौरान downside पर wider, rallies के दौरान upside पर wider। (CQR के साथ interval अब symmetric नहीं है, इसलिए असली upper-minus-lower width है — हर जगह को denominator के रूप में इस्तेमाल करना जारी रखें।)
- Normalized scores: , जहां एक local volatility estimate है। यह conditionally adaptive intervals produce करता है — low-volatility regimes के दौरान tighter, high-volatility regimes के दौरान wider — marginal coverage बनाए रखते हुए।
Calibration Set का आकार
Split conformal की कवरेज गारंटी किसी भी calibration set size के लिए लागू होती है, लेकिन बढ़ने पर interval width घटती है। बहुत छोटे के लिए आवश्यक order statistic मौजूद नहीं हो सकता, ऐसे में honest threshold है (uninformative लेकिन valid interval) — largest residual पर clamp करना चुपचाप गारंटी को void कर देता है। व्यावहारिक ट्रेडिंग के लिए:
- calibration points reasonably tight intervals देते हैं।
- स्थिर quantile estimation के लिए को प्राथमिकता दी जाती है।
- ACI के साथ, calibration set केवल initialization के लिए है; online level updates बाकी काम संभालते हैं।
Retraining Frequency
Base model stale हो सकता है। दो approaches:
- समय-समय पर retrain करें (जैसे, monthly) और conformal quantile को फिर से calibrate करें।
- ACI का उपयोग करें और adaptive level को model staleness की भरपाई करने दें — model के residuals बढ़ने पर intervals स्वचालित रूप से widen होते हैं।
Option 2 सरल और आश्चर्यजनक रूप से effective है। Conformal layer एक safety net की तरह काम करता है: भले ही model degrade हो जाए, long-run ACI coverage frequency फिर भी target की ओर converge करती है।
Transaction Costs
Conformal intervals transaction costs के साथ एक उपयोगी तरीके से interact करते हैं। जब intervals wide हों (high uncertainty), positions छोटे होते हैं, इसलिए turnover कम होता है। जब intervals narrow हों (model confident है), positions बढ़ते हैं — लेकिन model के सही होने की संभावना अधिक होती है, इसलिए turnover चुकाने लायक होता है।
आप transaction costs को सीधे no-trade filter में भी शामिल कर सकते हैं:
यह सुनिश्चित करता है कि आप तभी trade करें जब expected net edge conformal width द्वारा scaled एक threshold से अधिक हो — हर जगह जैसा वही denominator इस्तेमाल करते हुए।
पारंपरिक तरीकों से तुलना
| गुण | Gaussian CI | Bootstrap CI | Conformal PI |
|---|---|---|---|
| Distribution assumption | Normal errors | i.i.d. + asymptotic | कोई नहीं (exchangeability) |
| Finite-sample गारंटी | नहीं | नहीं | हाँ |
| किसी भी model के साथ काम करता है | नहीं | हाँ | हाँ |
| Heteroskedasticity के लिए adapt करता है | नहीं | आंशिक रूप से | CQR / normalized scores के साथ |
| Distribution shift को handle करता है | नहीं | नहीं | ACI / EnbPI variant |
| Computational cost | कम | मध्यम | Split: कम; jackknife+: refits; full: निषेधात्मक |
Bootstrap केवल अपनी गारंटी में "asymptotic" है; यह फिर भी i.i.d./exchangeable डेटा और smoothness मानता है, इसलिए इसका distribution-assumption cell "assumption-free" नहीं है। और एकल "conformal" column variants में बहुत अलग costs छिपाता है, जिसे cost row अब स्पष्ट कर देती है।
सीमाएं
Conformal prediction जादू नहीं है। ट्रेडिंग के लिए प्रमुख सीमाएं:
-
Marginal, conditional नहीं, कवरेज। गारंटी यह है कि marginally है — और दोनों की randomness पर averaged। एक विशिष्ट conditioning value के लिए, local coverage से ऊपर या नीचे हो सकता है। Conformalized quantile regression इसे आंशिक रूप से संबोधित करता है।
-
Exchangeability एक वास्तविक आवश्यकता है। Split conformal के लिए, calibration data और test point exchangeable होने चाहिए। वित्तीय डेटा नहीं है। ACI और EnbPI इसे एक long-run गारंटी तक ढीला करते हैं, लेकिन short-run coverage भटक सकता है।
-
Interval width एक probability density नहीं है। एक conformal interval आपको बताता है कि probability के साथ कहां गिरेगा, लेकिन यह interval के भीतर distribution के बारे में कुछ नहीं कहता। यह एक full predictive distribution का विकल्प नहीं है — जो ठीक इसीलिए है कि इससे Kelly और derive करने के लिए एक अतिरिक्त, explicit assumption की आवश्यकता है।
-
कचरा अंदर, बाहर ज़्यादा चौड़ा। एक ख़राब base model wide intervals produce करता है। Conformal prediction कवरेज की गारंटी देता है लेकिन उपयोगिता की नहीं। यदि आपके model में कोई predictive power नहीं है, तो intervals इतने wide होंगे कि position sizer कभी trade नहीं करेगा।
सारांश
Conformal prediction uncertainty quantification के लिए एक rigorous, distribution-free framework प्रदान करता है जो position sizing में स्वाभाविक रूप से फिट बैठता है:
- Split conformal static, offline calibration के लिए finite-sample coverage के साथ।
- Jackknife+ जब calibration data दुर्लभ हो और आप सभी observations का उपयोग करना चाहते हों ( refits की क़ीमत पर)।
- Adaptive conformal inference / EnbPI non-stationary markets के साथ online trading के लिए।
- Position sizing inverse interval width और एक edge-ratio no-trade filter के माध्यम से — और, यदि आप Kelly तक जाते हैं, तो केवल और को ईमानदारी से derive करने के बाद, किसी unjustified multiplier के ज़रिये नहीं।
पैरामीट्रिक विकल्पों पर मुख्य लाभ: आपको कभी भी किसी parametric distributional assumption को specify या verify करने की ज़रूरत नहीं है। Intervals construction द्वारा ईमानदार हैं (marginally, exchangeability के तहत)। एक systematic trader के लिए, इसका मतलब है model risk का एक स्रोत कम — और एक ऐसे business में जहां model risk अस्तित्वगत है, यह मायने रखता है।
संदर्भ:
- 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.