← Kembali ke artikel
June 12, 2026
5 menit baca

Conformal Prediction untuk Position Sizing yang Sadar Risiko

Conformal Prediction untuk Position Sizing yang Sadar Risiko
#ketidakpastian
#conformal-prediction
#risiko
#position-sizing
#statistika
#algorithmic-trading
🧠
Part 4 of 4 · Collection
Deep Learning for Markets

Setiap formula position sizing membutuhkan estimasi ketidakpastian. Kriteria Kelly membutuhkan probabilitas menang dan rasio payoff (lihat Kriteria Kelly untuk strategi). Optimasi mean-variance membutuhkan matriks kovarians. VaR membutuhkan distribusi return. Semua ini memerlukan asumsi tentang proses pembangkit data — asumsi yang secara rutin dilanggar oleh pasar keuangan.

Conformal prediction menawarkan sesuatu yang berbeda: interval prediksi dengan jaminan cakupan finite-sample, tanpa asumsi distribusi parametrik apa pun. Jika Anda meminta cakupan 90%, Anda mendapatkan setidaknya cakupan 90% — terlepas dari apakah return bersifat Gaussian, fat-tailed, skewed, atau heteroskedastik. Satu-satunya syarat adalah exchangeability (atau kondisi yang lebih lemah, seperti yang akan kita lihat).

Tulisan ini membahas teorinya, varian-varian utamanya, dan implementasi praktis untuk position sizing dalam Python.

Ide Inti: Skor Nonconformity

Distribusi skor nonconformity (residual) dengan ambang kuantil yang ditandai — inti dari kalibrasi conformal

Conformal prediction bekerja dengan mengukur seberapa "aneh" suatu observasi baru relatif terhadap data masa lalu. Keanehan ini dikuantifikasi oleh skor nonconformity — fungsi apa pun yang mengukur seberapa buruk sebuah titik data sesuai dengan pola yang terlihat pada sisa data.

Untuk regresi (memprediksi nilai kontinu seperti return), skor nonconformity paling sederhana adalah residual absolut:

Ri=Yiμ^(Xi)R_i = |Y_i - \hat{\mu}(X_i)|

di mana μ^\hat{\mu} adalah prediktor titik apa pun (regresi linier, random forest, neural network — tidak masalah) dan (Xi,Yi)(X_i, Y_i) adalah sebuah titik data.

Wawasan kuncinya: jika titik-titik data (X1,Y1),,(Xn,Yn),(Xn+1,Yn+1)(X_1, Y_1), \ldots, (X_n, Y_n), (X_{n+1}, Y_{n+1}) bersifat exchangeable, maka peringkat dari Rn+1R_{n+1} di antara R1,,Rn,Rn+1R_1, \ldots, R_n, R_{n+1} terdistribusi seragam atas {1,,n+1}\{1, \ldots, n+1\}. Ini adalah fakta yang murni kombinatorial — tidak memerlukan asumsi apa pun tentang distribusi XX atau YY.

Dari keseragaman peringkat ini, kita dapat menyusun interval prediksi dengan cakupan finite-sample.

Split Conformal Prediction

Split conformal prediction: data dibagi menjadi fold training dan kalibrasi, residual kalibrasi menghasilkan interval prediksi

Split conformal prediction (Papadopoulos et al., 2002; Lei et al., 2018) adalah varian yang paling praktis. Algoritmanya sederhana:

Langkah 1. Bagi data menjadi training set Dtrain\mathcal{D}_{\text{train}} dan calibration set Dcal={(X1,Y1),,(Xn,Yn)}\mathcal{D}_{\text{cal}} = \{(X_1, Y_1), \ldots, (X_n, Y_n)\}.

Langkah 2. Fit model apa pun μ^\hat{\mu} pada Dtrain\mathcal{D}_{\text{train}}.

Langkah 3. Hitung skor nonconformity pada calibration set:

Ri=Yiμ^(Xi),i=1,,nR_i = |Y_i - \hat{\mu}(X_i)|, \quad i = 1, \ldots, n

Langkah 4. Untuk tingkat miscoverage yang diinginkan α(0,1)\alpha \in (0, 1), ambil q^\hat{q} sebagai kuantil empiris (1α)(n+1)n\frac{\lceil (1 - \alpha)(n + 1) \rceil}{n} dari R1,,RnR_1, \ldots, R_n. Secara konkret, ini adalah residual terkecil ke-(1α)(n+1)\lceil (1 - \alpha)(n + 1) \rceil (dan q^=+\hat{q} = +\infty kapan pun (1α)(n+1)>n\lceil (1 - \alpha)(n + 1) \rceil > n, yaitu untuk nn yang sangat kecil).

Langkah 5. Interval prediksi untuk titik baru Xn+1X_{n+1} adalah:

C(Xn+1)=[μ^(Xn+1)q^,  μ^(Xn+1)+q^]C(X_{n+1}) = \left[\hat{\mu}(X_{n+1}) - \hat{q}, \; \hat{\mu}(X_{n+1}) + \hat{q}\right]

Jaminan Cakupan

Di bawah exchangeability data kalibrasi dan titik tes baru:

P(Yn+1C(Xn+1))1α\mathbb{P}\left(Y_{n+1} \in C(X_{n+1})\right) \geq 1 - \alpha

Ini adalah jaminan finite-sample — bukan aproksimasi asimptotik. Jaminan ini berlaku untuk model μ^\hat{\mu} apa pun, distribusi data apa pun, dan ukuran sampel nn berapa pun. Jika μ^\hat{\mu} adalah prediktor yang buruk, intervalnya akan sekadar menjadi lebih lebar. Jaminan cakupan tetap berlaku.

Ada juga batas atas ketika skor tidak memiliki ikatan (ties): P(Yn+1C(Xn+1))1α+1n+1\mathbb{P}(Y_{n+1} \in C(X_{n+1})) \leq 1 - \alpha + \frac{1}{n+1}, sehingga cakupannya tidak konservatif secara berlebihan.

Mengapa Ini Penting untuk Trading

Interval prediksi tradisional dari, katakanlah, regresi linier mengasumsikan galat Gaussian. Sebuah interval Gaussian yang dikalibrasi pada bagian utama data dapat sangat salah menilai ekor (tail) ketika residual sebenarnya bersifat heavy-tailed (misalnya Student-tt dengan derajat kebebasan yang sedikit): massa pusatnya lebih tipis daripada Gaussian, sehingga sebuah interval Gaussian yang dicocokkan dengan variansnya akan over-cover di dekat pusat namun under-cover di ekor, dan yang dicocokkan dengan ekor melakukan sebaliknya. Intinya bukanlah sebuah angka ajaib tunggal — intinya adalah bahwa cakupan yang terealisasi dari sebuah interval parametrik bergantung pada asumsi distribusi yang belum Anda verifikasi.

Interval conformal prediction menghindari masalah ini. Interval-interval tersebut secara otomatis melebar ketika model tidak yakin, dan mempertahankan cakupan marginal terlepas dari distribusi galat sebenarnya. Bagi seorang trader, ini berarti:

  • Jika Anda menentukan ukuran posisi secara berbanding terbalik dengan lebar interval, Anda secara otomatis mengurangi eksposur ketika model tidak yakin.
  • Jaminan cakupan berarti estimasi risiko Anda bersifat jujur — jika Anda mengatakan "90% dari return yang terealisasi akan berada dalam interval ini," pernyataan itu valid secara statistik (secara marginal, di bawah exchangeability).

Full Conformal dan Jackknife+

Split conformal sederhana tetapi memboroskan data: calibration set tidak dapat digunakan untuk training. Dua alternatif menangani hal ini.

Full Conformal Prediction

Full conformal prediction (Vovk et al., 2005) menggunakan semua data baik untuk training maupun kalibrasi. Untuk setiap nilai kandidat yy dari Yn+1Y_{n+1}:

  1. Tambahkan dataset dengan (Xn+1,y)(X_{n+1}, y).
  2. Fit ulang model pada dataset yang telah ditambah tersebut.
  3. Hitung semua skor nonconformity.
  4. Sertakan yy dalam himpunan prediksi jika skor untuk (Xn+1,y)(X_{n+1}, y) tidak terlalu ekstrem.

Himpunan prediksinya adalah:

C(Xn+1)={y:{i:RiyRn+1y}n+1>α}C(X_{n+1}) = \left\{y : \frac{|\{i : R_i^y \geq R_{n+1}^y\}|}{n+1} > \alpha \right\}

di mana RiyR_i^y adalah skor nonconformity yang dihitung dengan dataset yang telah ditambah.

Full conformal memberikan interval yang paling ketat tetapi secara komputasi terlampau mahal untuk sebagian besar model — Anda harus fit ulang model untuk setiap kandidat yy pada sebuah grid. Untuk prediksi return, ini bisa berarti ribuan kali fit ulang per prediksi.

Jackknife+ (Barber et al., 2021)

Jackknife+ mencapai keseimbangan. Metode ini menggunakan residual leave-one-out (LOO) tetapi memperhitungkan variabilitas model yang di-fit lintas fold LOO.

Misalkan μ^i\hat{\mu}_{-i} menyatakan model yang dilatih pada semua data kecuali titik ii. Definisikan skor nonconformity LOO dengan residual absolut tunggal:

Ri=Yiμ^i(Xi)R_i = |Y_i - \hat{\mu}_{-i}(X_i)|

Interval prediksi jackknife+ kemudian dibangun dari prediksi LOO pada titik tes, dilebarkan oleh residual-residual ini:

C(Xn+1)=[qα ⁣{μ^i(Xn+1)Ri},    q1α+ ⁣{μ^i(Xn+1)+Ri}]C(X_{n+1}) = \left[\, q_{\alpha}^{-}\!\left\{\hat{\mu}_{-i}(X_{n+1}) - R_i\right\}, \;\; q_{1-\alpha}^{+}\!\left\{\hat{\mu}_{-i}(X_{n+1}) + R_i\right\} \right]

Di sini q1α+{vi}q_{1-\alpha}^{+}\{v_i\} menyatakan nilai terkecil ke-(1α)(n+1)\lceil (1-\alpha)(n+1)\rceil dari himpunan {vi}i=1n\{v_i\}_{i=1}^n, dan qα{vi}q_{\alpha}^{-}\{v_i\} adalah nilai terkecil ke-α(n+1)\lfloor \alpha(n+1)\rfloor. Batas bawah mengurangkan residual dari setiap prediksi LOO; batas atas menambahkannya. Asimetri itulah inti dari semuanya — meruntuhkan kedua batas menjadi μ^i+Ri\hat{\mu}_{-i} + R_i akan menempatkan batas bawah di atas prediksi, yang mana itu salah.

Jackknife+ memberikan jaminan cakupan setidaknya 12α1 - 2\alpha (sedikit lebih lemah daripada 1α1-\alpha milik split conformal), tetapi menggunakan semua data baik untuk training maupun kalibrasi. Dalam praktiknya cakupannya biasanya mendekati 1α1-\alpha.

Untuk model trading yang dilatih pada data terbatas (misalnya, model spesifik-regime dengan hanya beberapa ratus observasi), jackknife+ seringkali merupakan pilihan terbaik — metode ini tidak mengorbankan data yang langka untuk kalibrasi. Biayanya adalah nn kali fit ulang model.

Masalah dengan Deret Waktu Keuangan: Non-Exchangeability

Non-exchangeability dalam deret waktu keuangan: sebuah deret non-stasioner dengan pergeseran regime, cakupan retak saat distribusi bergeser

Jaminan conformal standar memerlukan exchangeability: distribusi gabungan dari (Z1,,Zn+1)(Z_1, \ldots, Z_{n+1}) bersifat invarian di bawah permutasi. Untuk data i.i.d., ini berlaku secara trivial.

Deret waktu keuangan tidak bersifat exchangeable. Return menunjukkan:

  • Volatility clustering: Periode volatilitas tinggi mengikuti periode volatilitas tinggi (efek GARCH).
  • Momentum dan mean reversion: Autokorelasi pada return atau return kuadrat.
  • Perubahan regime: Distribusi bergeser seiring waktu (pasar bull vs. bear).

Jika Anda secara naif menerapkan split conformal pada deret waktu — menggunakan pemisahan kalibrasi acak — Anda melanggar struktur temporalnya. Skor kalibrasi dari tahun 2017 yang tenang tidak akan mencerminkan ketidakpastian tahun 2020 yang bergejolak. Jaminan cakupan Anda rusak.

Adaptive Conformal Inference (ACI)

Adaptive conformal inference: sebuah interval prediksi yang melebar dan menyempit melalui loop umpan balik yang melacak cakupan terealisasi menuju target

Gibbs dan Candes (2021, NeurIPS) memperkenalkan Adaptive Conformal Inference (ACI) untuk menangani pergeseran distribusi dan data non-exchangeable. Idenya elegan: alih-alih menggunakan tingkat cakupan tetap, sesuaikan tingkat miscoverage target secara online berdasarkan apakah interval-interval terkini mencakup hasil sebenarnya, dan turunkan ulang kuantil dari distribusi skor pada setiap langkah.

Algoritma ACI

ACI tidak menggeser lebar interval secara langsung. Algoritma ini mempertahankan parameter miscoverage adaptif αt\alpha_t dan menghitung ulang kuantil conformal darinya. Pada setiap langkah waktu tt:

  1. Hitung ambang conformal sebagai kuantil empiris (1αt)(1 - \alpha_t) dari himpunan residual saat ini (skor kalibrasi, ditambah skor terealisasi sejauh ini): q^t=Quantile^1αt({Rj})\hat{q}_t = \widehat{\text{Quantile}}_{1-\alpha_t}(\{R_j\}).
  2. Amati fitur XtX_t, hasilkan interval Ct(Xt)=[μ^(Xt)q^t,  μ^(Xt)+q^t]C_t(X_t) = [\hat{\mu}(X_t) - \hat{q}_t, \; \hat{\mu}(X_t) + \hat{q}_t].
  3. Amati nilai sebenarnya YtY_t dan hitung indikator galat errt=1{YtCt(Xt)}\text{err}_t = \mathbf{1}\{Y_t \notin C_t(X_t)\}.
  4. Perbarui tingkat (bukan lebarnya):

αt+1=clip ⁣(αt+γ(αerrt),  0,  1)\alpha_{t+1} = \text{clip}\!\left(\alpha_t + \gamma\,(\alpha - \text{err}_t),\; 0,\; 1\right)

di mana γ>0\gamma > 0 adalah ukuran langkah (step size) dan α\alpha adalah miscoverage target. Jika sebuah interval meleset (errt=1\text{err}_t = 1), αt\alpha_t menyusut, yang mendorong kuantil berikutnya lebih tinggi dan melebarkan interval; jika interval mencakup, αt\alpha_t tumbuh dan interval mengetat. Yang krusial, γ\gamma di sini berada dalam satuan probabilitas — ia menggeser sebuah tingkat dalam [0,1][0,1], bukan ambang dalam satuan return mentah — sehingga γ\gamma yang sama berperilaku masuk akal entah residualnya berada pada orde 10310^{-3} atau tidak.

Jaminan Cakupan untuk ACI

ACI memberikan jaminan cakupan jangka panjang yang tidak bergantung pada model distribusi:

1Tt=1TerrtααT+1α1γT\left|\frac{1}{T}\sum_{t=1}^{T} \text{err}_t - \alpha\right| \leq \frac{|\alpha_{T+1} - \alpha_1|}{\gamma T}

Karena αt\alpha_t di-clip ke [0,1][0,1], pembilangnya dibatasi oleh 1/γ1/\gamma dikali sebuah konstanta, sehingga ruas kanannya adalah O(1/T)O(1/T) dan frekuensi miscoverage empiris konvergen ke α\alpha. Pernyataan presisinya: ACI menjamin frekuensi miscoverage empiris jangka panjang konvergen ke α\alpha untuk barisan sembarang (termasuk yang adversarial), asalkan tingkat yang diadaptasi tetap terbatas — yang ditegakkan oleh clip. Ini adalah jaminan atas frekuensi cakupan, bukan atas informativeness interval: di bawah barisan yang benar-benar adversarial, interval dapat tumbuh menjadi sangat lebar tanpa informasi sembari tetap mencapai target cakupan.

Dynamically-Tuned ACI (DtACI)

Gibbs dan Candes (2024, JMLR) menyempurnakan ACI dengan dynamic tuning dari ukuran langkah γ\gamma. Alih-alih menetapkan γ\gamma, mereka mempertahankan himpunan kandidat Γ={γ1,,γK}\Gamma = \{\gamma_1, \ldots, \gamma_K\} dan menggabungkannya melalui aturan agregasi-pakar (expert-aggregation), mengunggulkan γ\gamma yang cakupan terkininya paling dekat dengan target.

Ini menangani masalah praktis: γ\gamma yang besar beradaptasi cepat terhadap perubahan regime tetapi menghasilkan lebar interval yang bergejolak; γ\gamma yang kecil stabil tetapi lambat beradaptasi. DtACI menyeimbangkan keduanya secara otomatis.

Mengapa Ini Penting untuk Trading

Pertimbangkan sebuah strategi market-making yang menggunakan model peramalan return. Selama pasar tenang, interval conformal ketat — model percaya diri, dan Anda dapat mengambil posisi lebih besar. Ketika volatilitas melonjak (musim laporan keuangan, pengumuman FOMC, guncangan geopolitik), tingkat ACI beradaptasi dan interval melebar dalam beberapa langkah waktu. Position sizing Anda menyusut sebagai respons, tanpa model volatilitas eksplisit atau logika deteksi regime apa pun.

Inilah kuantifikasi ketidakpastian sebagai sinyal kelas-utama, bukan sebagai renungan belakangan.

Position Sizing dengan Interval Conformal

Memetakan interval ketidakpastian yang terkalibrasi ke ukuran posisi: interval ketat mendorong posisi besar, interval lebar mendorong posisi kecil

Sekarang mari kita hubungkan conformal prediction dengan position sizing konkret. Variabel kuncinya adalah setengah-lebar interval prediksi relatif terhadap kasus residual-absolut yang simetris. Dengan interval simetris [μ^(Xt)q^t,  μ^(Xt)+q^t][\hat{\mu}(X_t) - \hat{q}_t, \; \hat{\mu}(X_t) + \hat{q}_t], lebar penuhnya adalah wt=2q^tw_t = 2\hat{q}_t. Untuk menjaga konsistensi formula dan kode, kita mengukur segalanya terhadap lebar penuh wtw_t di sepanjang tulisan ini.

Inverse-Width Sizing

Pendekatan paling sederhana: tentukan ukuran secara berbanding terbalik dengan lebar interval.

position_sizet=kwt\text{position\_size}_t = \frac{k}{w_t}

di mana kk adalah konstanta penskalaan yang dikalibrasi terhadap anggaran risiko Anda. Ketika model percaya diri (interval sempit), Anda mengambil posisi lebih besar. Ketika tidak yakin (interval lebar), Anda mengambil yang lebih kecil.

Ini analog dengan volatility targeting (size1/σ\text{size} \propto 1/\sigma), tetapi dengan perbedaan krusial: lebar interval conformal adalah ukuran ketidakpastian bebas distribusi, bukan estimasi volatilitas parametrik. Ia menangkap ketidakpastian prediktif di bawah jaminan cakupan, bukan sekadar varians return.

Edge-Ratio Sizing dan Filter No-Trade

Inverse-width sizing murni mengabaikan kekuatan sinyal itu sendiri. Penyempurnaan yang alami adalah menskalakan dengan edge ratio — prediksi titik relatif terhadap lebar interval:

et=μ^(Xt)wte_t = \frac{|\hat{\mu}(X_t)|}{w_t}

Ini adalah analog conformal dari rasio sinyal-terhadap-derau (signal-to-noise ratio): return harapan dibagi ukuran ketidakpastian bebas distribusi. Kami menggunakannya baik untuk sizing maupun untuk filter no-trade.

Filter ini berprinsip. Jika interval mengangkangi nol,

lowert<0<uppert,\text{lower}_t < 0 < \text{upper}_t,

maka interval prediksi (1α)(1-\alpha) mencakup return positif maupun negatif — return terealisasi secara masuk akal dapat memiliki tanda yang berlawanan dari prediksi Anda. Definisikan ambang edge minimum θ\theta dan trade hanya ketika et>θe_t > \theta. Perhatikan bahwa muatan geometris dari "ete_t cukup besar sehingga interval tidak lagi mengangkangi nol" tepatnya adalah et>1/2e_t > 1/2 (karena interval melewati nol ketika μ^>q^t=wt/2|\hat{\mu}| > \hat{q}_t = w_t/2). Pilih θ\theta pada skala aktual dari et=μ^/wte_t = |\hat{\mu}|/w_t melalui backtesting; untuk residual return harian, ete_t biasanya jauh di bawah 1/21/2, sehingga θ\theta yang sangat kecil mungkin mengizinkan hampir semua trade dan yang besar mungkin tidak mengizinkan satu pun. Kalibrasikan terhadap data Anda.

Tentang "Conformal Kelly"

Menggiurkan untuk membaut interval conformal ke fraksi Kelly f=pb(1p)bf^* = \frac{pb - (1-p)}{b}. Tetapi ff^* sudah merupakan fraksi yang lengkap dan terbatas yang diturunkan dari probabilitas menang pp dan rasio payoff bb; mengalikannya dengan rasio tak terbatas seperti μ^/q^\hat{\mu}/\hat{q} tidak memiliki justifikasi teori-keputusan — hasilnya dapat melebihi 1 atau membalik tanda secara independen dari ff^*, dan ini menghitung ganda edge yang sudah dikodekan oleh ff^*. Jadi kami tidak menyajikan pengali "conformal Kelly".

Jika Anda ingin menggerakkan Kelly dari interval, Anda harus benar-benar menurunkan pp dan bb darinya, yang memerlukan asumsi eksplisit tentang distribusi di dalam interval (interval conformal secara sengaja tidak mengatakan apa pun tentang itu — lihat Keterbatasan). Sebagai contoh, di bawah asumsi bentuk dalam-interval Anda dapat mengaproksimasi pP(return>0)p \approx \mathbb{P}(\text{return} > 0) dan rasio payoff dari geometri interval — tetapi tandai asumsi itu dengan lantang, karena ia memperkenalkan kembali tepatnya komitmen parametrik yang seharusnya dihindari oleh conformal prediction.

Alternatif yang jujur dan ringan-asumsi adalah menggunakan edge ratio et=μ^/wte_t = |\hat{\mu}|/w_t sebagai penyusutan fractional-Kelly: perbesar ukuran ketika return harapan besar relatif terhadap interval, perkecil ukuran ketika kecil, dan terapkan ini di atas batas keras (hard cap) — secara eksplisit sebagai heuristik, bukan sebagai "fraksi Kelly".

Implementasi Python

Berikut adalah implementasi praktis. Kami menunjukkan baik jalur split/prefit maupun jalur temporal (EnbPI), karena seluruh inti tulisan ini adalah bahwa data keuangan bersifat non-exchangeable.

Setup dan Persiapan Data

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 dengan MAPIE (Prefit)

Untuk conformal split/prefit, cv="prefit" memerlukan method="base" (estimator split-conformal naif). Opsi method="plus" adalah estimator CV+/jackknife+ dan tidak kompatibel dengan cv="prefit" — ia memerlukan objek cross-validation sebagai gantinya. Kami menggunakan kombinasi yang benar di sini, dan menstandarkan sizing pada lebar penuh.

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)

Conformal Deret Waktu dengan EnbPI

Karena return bersifat non-exchangeable, pemisahan acak/prefit di atas hanyalah sebuah baseline. MapieTimeSeriesRegressor dari MAPIE dengan method="enbpi" (Xu & Xie, 2021) menggunakan block bootstrap dan pembaruan residual yang dirancang untuk ketergantungan temporal. Inilah alat yang cocok dengan argumen tulisan ini sendiri.

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)

Untuk live trading kami mengimplementasikan ACI sejati dari nol: pertahankan tingkat miscoverage αt\alpha_t, perbarui secara aditif, dan turunkan ulang kuantil dari himpunan residual setiap langkah. Dua detail finite-sample penting:

  • Gunakan statistik urutan (order statistic), bukan kuantil yang diinterpolasi. np.quantile melakukan interpolasi secara default, yang dapat jatuh tepat di bawah peringkat yang diperlukan dan under-cover; lewatkan method="higher" (setara dengan "inverted_cdf").
  • Ketika peringkat yang diperlukan melebihi nn (nn kecil, cakupan target tinggi), ambang yang benar adalah ++\infty (interval = seluruh garis), bukan clamp ke residual terbesar. Clamping diam-diam merusak jaminan 1αt\ge 1-\alpha_t.
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))

Menyatukan Semuanya: Loop Backtest

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

Mengevaluasi Hasilnya

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}")

Pertimbangan Praktis

Memilih Skor Nonconformity

Residual absolut Yμ^(X)|Y - \hat{\mu}(X)| adalah default, tetapi ia mengasumsikan interval prediksi seharusnya simetris di sekitar prediksi titik. Untuk return keuangan, interval asimetris seringkali lebih masuk akal:

  • Conformalized Quantile Regression (CQR): Fit quantile regressor pada tingkat α/2\alpha/2 dan 1α/21 - \alpha/2, lalu conformalize (Romano et al., 2019). Interval-interval tersebut menyesuaikan bentuknya terhadap distribusi lokal — lebih lebar di sisi bawah selama drawdown, lebih lebar di sisi atas selama rally. (Dengan CQR interval tidak lagi simetris, sehingga wtw_t adalah lebar atas-dikurangi-bawah yang sebenarnya — tetap gunakan wtw_t sebagai penyebut di mana pun.)
  • Skor ternormalisasi: Ri=Yiμ^(Xi)/σ^(Xi)R_i = |Y_i - \hat{\mu}(X_i)| / \hat{\sigma}(X_i), di mana σ^\hat{\sigma} adalah estimasi volatilitas lokal. Ini menghasilkan interval yang adaptif secara kondisional — lebih ketat selama regime volatilitas rendah, lebih lebar selama yang tinggi — sembari mempertahankan cakupan marginal.

Ukuran Calibration Set

Jaminan cakupan dari split conformal berlaku untuk ukuran calibration set nn berapa pun, tetapi lebar interval mengecil seiring bertambahnya nn. Untuk nn yang sangat kecil, statistik urutan yang diperlukan mungkin tidak ada, dalam hal ini ambang yang jujur adalah ++\infty (interval yang tidak informatif tetapi valid) — clamping ke residual terbesar diam-diam membatalkan jaminan. Untuk trading praktis:

  • n100n \geq 100 titik kalibrasi memberikan interval yang cukup ketat.
  • n500n \geq 500 lebih disukai untuk estimasi kuantil yang stabil.
  • Dengan ACI, calibration set hanya untuk inisialisasi; pembaruan tingkat online menangani sisanya.

Frekuensi Pelatihan Ulang

Model dasar μ^\hat{\mu} dapat menjadi usang. Dua pendekatan:

  1. Latih ulang secara periodik (misalnya, bulanan) dan kalibrasi ulang kuantil conformal.
  2. Gunakan ACI dan biarkan tingkat adaptif mengompensasi keusangan model — interval melebar secara otomatis seiring bertambahnya residual model.

Opsi 2 lebih sederhana dan secara mengejutkan efektif. Lapisan conformal bertindak sebagai jaring pengaman: bahkan jika model menurun kualitasnya, frekuensi cakupan ACI jangka panjang tetap konvergen ke target.

Biaya Transaksi

Interval conformal berinteraksi dengan biaya transaksi dengan cara yang berguna. Ketika interval lebar (ketidakpastian tinggi), posisi kecil, sehingga turnover rendah. Ketika interval menyempit (model percaya diri), posisi tumbuh — tetapi model lebih mungkin benar, sehingga turnover-nya layak dibayar.

Anda juga dapat memasukkan biaya transaksi langsung ke dalam filter no-trade:

trade hanya jika μ^(Xt)cost>θwt\text{trade hanya jika } |\hat{\mu}(X_t)| - \text{cost} > \theta \cdot w_t

Ini memastikan Anda hanya trade ketika edge bersih yang diharapkan melebihi sebuah ambang yang diskalakan oleh lebar conformal — menggunakan penyebut wtw_t yang sama seperti di mana pun.

Perbandingan dengan Metode Tradisional

Properti Gaussian CI Bootstrap CI Conformal PI
Asumsi distribusi Galat normal i.i.d. + asimptotik Tidak ada (exchangeability)
Jaminan finite-sample Tidak Tidak Ya
Bekerja dengan model apa pun Tidak Ya Ya
Beradaptasi terhadap heteroskedastisitas Tidak Sebagian Dengan CQR / skor ternormalisasi
Menangani pergeseran distribusi Tidak Tidak Varian ACI / EnbPI
Biaya komputasi Rendah Sedang Split: rendah; jackknife+: O(n)O(n) fit ulang; full: terlampau mahal

Bootstrap bersifat "asimptotik" hanya dalam jaminannya; ia tetap mengasumsikan data i.i.d./exchangeable dan kemulusan, sehingga sel asumsi-distribusinya bukanlah "bebas-asumsi". Dan kolom tunggal "conformal" menyembunyikan biaya yang sangat berbeda antar varian, yang sekarang dijabarkan oleh baris biaya.

Keterbatasan

Conformal prediction bukanlah sihir. Keterbatasan utama untuk trading:

  1. Cakupan marginal, bukan kondisional. Jaminannya adalah bahwa P(Yn+1C(Xn+1))1α\mathbb{P}(Y_{n+1} \in C(X_{n+1})) \geq 1 - \alpha secara marginal — dirata-ratakan atas keacakan baik dalam XX maupun YY. Untuk nilai pengkondisian spesifik X=xX = x, cakupan lokal bisa di atas atau di bawah 1α1 - \alpha. Conformalized quantile regression sebagian menangani hal ini.

  2. Exchangeability adalah syarat nyata. Untuk split conformal, data kalibrasi dan titik tes harus exchangeable. Data keuangan tidak. ACI dan EnbPI melonggarkan ini menjadi jaminan jangka panjang, tetapi cakupan jangka pendek dapat menyimpang.

  3. Lebar interval bukanlah densitas probabilitas. Sebuah interval conformal memberi tahu Anda di mana YY akan jatuh dengan probabilitas 1α1-\alpha, tetapi tidak mengatakan apa pun tentang distribusi di dalam interval. Ia bukan pengganti distribusi prediktif penuh — yang justru menjadi alasan mengapa menurunkan pp dan bb Kelly darinya memerlukan asumsi eksplisit tambahan.

  4. Sampah masuk, lebih lebar keluar. Model dasar yang buruk menghasilkan interval yang lebar. Conformal prediction menjamin cakupan tetapi bukan kegunaan. Jika model Anda tidak memiliki daya prediktif, intervalnya akan begitu lebar sehingga position sizer tidak pernah trade.

Ringkasan

Conformal prediction menyediakan kerangka kerja yang ketat dan bebas distribusi untuk kuantifikasi ketidakpastian yang menyatu secara alami ke dalam position sizing:

  • Split conformal untuk kalibrasi statis dan offline dengan cakupan finite-sample.
  • Jackknife+ ketika data kalibrasi langka dan Anda ingin menggunakan semua observasi (dengan biaya nn kali fit ulang).
  • Adaptive conformal inference / EnbPI untuk trading online dengan pasar non-stasioner.
  • Position sizing melalui inverse lebar interval dan filter no-trade berbasis edge-ratio — dan, jika Anda beralih ke Kelly, hanya setelah menurunkan pp dan bb secara jujur, bukan melalui pengali yang tidak terjustifikasi.

Keunggulan kunci atas alternatif parametrik: Anda tidak pernah perlu menentukan atau memvalidasi asumsi distribusi parametrik. Interval-interval tersebut jujur secara konstruksi (secara marginal, di bawah exchangeability). Bagi seorang trader sistematis, ini berarti satu sumber risiko model yang lebih sedikit — dan dalam bisnis di mana risiko model bersifat eksistensial, itu penting.


Referensi:

  • 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.
Penafian: Informasi yang disediakan dalam artikel ini hanya untuk tujuan edukasi dan informasi serta tidak merupakan nasihat keuangan, investasi, atau trading. Trading mata uang kripto mengandung risiko kerugian yang signifikan.

Penulis

Eugen Soloviov
Eugen Soloviov

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.

Newsletter

Selangkah Lebih Maju dari Pasar

Berlangganan newsletter kami untuk wawasan AI trading eksklusif, analisis pasar, dan pembaruan platform.

Kami menghormati privasi Anda. Berhenti berlangganan kapan saja.