Conformal Prediction cho việc định cỡ vị thế nhận biết rủi ro
Mọi công thức định cỡ vị thế đều cần một ước lượng về độ bất định. Tiêu chí Kelly cần một xác suất thắng và một tỷ lệ chi trả (xem Tiêu chí Kelly cho các chiến lược). Tối ưu hóa mean-variance cần một ma trận hiệp phương sai. VaR cần một phân phối lợi suất. Tất cả những thứ này đều đòi hỏi các giả định về quá trình sinh dữ liệu — những giả định mà thị trường tài chính thường xuyên vi phạm.
Conformal prediction mang lại một thứ khác: khoảng dự báo với bảo đảm mức bao phủ trên mẫu hữu hạn, mà không cần bất kỳ giả định phân phối tham số nào. Nếu bạn yêu cầu mức bao phủ 90%, bạn nhận được ít nhất 90% mức bao phủ — bất kể lợi suất là Gauss, đuôi nặng, lệch, hay phương sai thay đổi. Yêu cầu duy nhất là tính hoán đổi được (exchangeability) (hoặc các điều kiện yếu hơn, như chúng ta sẽ thấy).
Bài viết này trình bày lý thuyết, các biến thể quan trọng, và một cài đặt thực tế để định cỡ vị thế bằng Python.
Ý tưởng cốt lõi: điểm bất phù hợp (nonconformity score)

Conformal prediction hoạt động bằng cách đo lường một quan sát mới "lạ lùng" đến mức nào so với dữ liệu quá khứ. Sự lạ lùng được định lượng bằng một điểm bất phù hợp (nonconformity score) — bất kỳ hàm nào đo lường mức độ một điểm dữ liệu không phù hợp với khuôn mẫu thấy được ở phần còn lại của dữ liệu.
Với bài toán hồi quy (dự báo một giá trị liên tục như lợi suất), điểm bất phù hợp đơn giản nhất là phần dư tuyệt đối:
trong đó là một bộ dự báo điểm bất kỳ (hồi quy tuyến tính, random forest, mạng nơ-ron — đều không quan trọng) và là một điểm dữ liệu.
Nhận xét then chốt: nếu các điểm dữ liệu là hoán đổi được, thì hạng (rank) của trong số phân phối đều trên . Đây là một sự kiện thuần túy tổ hợp — nó không đòi hỏi bất kỳ giả định nào về phân phối của hay .
Từ tính đều của hạng này, chúng ta có thể xây dựng các khoảng dự báo với mức bao phủ trên mẫu hữu hạn.
Split Conformal Prediction

Split conformal prediction (Papadopoulos et al., 2002; Lei et al., 2018) là biến thể thực tế nhất. Thuật toán đơn giản:
Bước 1. Chia dữ liệu thành một tập huấn luyện và một tập hiệu chỉnh .
Bước 2. Khớp một mô hình bất kỳ trên .
Bước 3. Tính các điểm bất phù hợp trên tập hiệu chỉnh:
Bước 4. Với một mức không bao phủ mong muốn , lấy là phân vị thực nghiệm thứ của . Cụ thể, đây là phần dư nhỏ thứ (và mỗi khi , tức là với rất nhỏ).
Bước 5. Khoảng dự báo cho một điểm mới là:
Bảo đảm mức bao phủ
Dưới giả định hoán đổi được của dữ liệu hiệu chỉnh và điểm kiểm tra mới:
Đây là một bảo đảm trên mẫu hữu hạn — không phải một xấp xỉ tiệm cận. Nó đúng với mô hình bất kỳ, phân phối dữ liệu bất kỳ, và cỡ mẫu bất kỳ. Nếu là một bộ dự báo tồi tệ, các khoảng sẽ đơn giản là rộng hơn. Bảo đảm mức bao phủ vẫn đúng.
Cũng có một cận trên khi các điểm số không có giá trị trùng nhau (ties): , nên mức bao phủ không bảo thủ một cách lãng phí.
Vì sao điều này quan trọng với giao dịch
Các khoảng dự báo truyền thống từ, chẳng hạn, một hồi quy tuyến tính giả định sai số Gauss. Một khoảng Gauss được hiệu chỉnh trên phần lớn dữ liệu có thể đánh giá sai nghiêm trọng các đuôi khi phần dư thực có đuôi nặng (ví dụ Student- với vài bậc tự do): khối trung tâm mỏng hơn so với Gauss, nên một khoảng Gauss khớp phương sai sẽ bao phủ thừa gần trung tâm nhưng bao phủ thiếu ở đuôi, còn một khoảng khớp theo đuôi thì làm ngược lại. Điểm mấu chốt không phải là một con số kỳ diệu duy nhất — mà là mức bao phủ thực hiện được của một khoảng tham số phụ thuộc vào một giả định phân phối mà bạn chưa kiểm chứng.
Các khoảng conformal prediction né được điều này. Chúng tự động nới rộng khi mô hình không chắc chắn, và chúng duy trì mức bao phủ biên (marginal coverage) bất kể phân phối sai số thực là gì. Với một nhà giao dịch, điều này có nghĩa là:
- Nếu bạn định cỡ vị thế tỷ lệ nghịch với độ rộng khoảng, bạn tự động giảm phơi nhiễm khi mô hình không chắc chắn.
- Bảo đảm mức bao phủ có nghĩa là các ước lượng rủi ro của bạn trung thực — nếu bạn nói "90% lợi suất thực hiện sẽ rơi vào trong khoảng này," thì phát biểu đó hợp lệ về mặt thống kê (theo nghĩa biên, dưới giả định hoán đổi được).
Full Conformal và Jackknife+
Split conformal đơn giản nhưng lãng phí dữ liệu: tập hiệu chỉnh không thể được dùng để huấn luyện. Hai lựa chọn thay thế giải quyết điều này.
Full Conformal Prediction
Full conformal prediction (Vovk et al., 2005) dùng toàn bộ dữ liệu cho cả huấn luyện lẫn hiệu chỉnh. Với mỗi giá trị ứng viên của :
- Bổ sung tập dữ liệu với .
- Khớp lại mô hình trên tập dữ liệu đã bổ sung.
- Tính tất cả các điểm bất phù hợp.
- Đưa vào tập dự báo nếu điểm số cho không quá cực đoan.
Tập dự báo là:
trong đó là các điểm bất phù hợp được tính với tập dữ liệu đã bổ sung.
Full conformal cho ra các khoảng chặt nhất nhưng tốn kém tính toán đến mức không khả thi với hầu hết các mô hình — bạn phải khớp lại mô hình cho từng giá trị ứng viên trên một lưới. Với một dự báo lợi suất, điều này có thể có nghĩa là hàng nghìn lần khớp lại cho mỗi lần dự báo.
Jackknife+ (Barber et al., 2021)
Jackknife+ tạo ra một sự cân bằng. Nó dùng các phần dư bỏ-một-ra (leave-one-out, LOO) nhưng tính đến tính biến thiên trong mô hình khớp được qua các fold LOO.
Gọi là mô hình được huấn luyện trên toàn bộ dữ liệu trừ điểm . Định nghĩa điểm bất phù hợp LOO với phần dư tuyệt đối đơn:
Khoảng dự báo jackknife+ khi đó được xây dựng từ các dự báo LOO tại điểm kiểm tra, được nới rộng bởi các phần dư này:
Ở đây ký hiệu giá trị nhỏ thứ của tập , và là giá trị nhỏ thứ . Cận dưới trừ phần dư khỏi mỗi dự báo LOO; cận trên cộng nó vào. Sự bất đối xứng đó chính là toàn bộ điểm mấu chốt — gộp cả hai cận thành sẽ đẩy cận dưới lên trên dự báo, điều đó là sai.
Jackknife+ cung cấp một bảo đảm mức bao phủ ít nhất (yếu hơn một chút so với của split conformal), nhưng nó dùng toàn bộ dữ liệu cho cả huấn luyện lẫn hiệu chỉnh. Trong thực tế mức bao phủ thường gần với .
Với các mô hình giao dịch được huấn luyện trên dữ liệu hạn chế (ví dụ, các mô hình theo từng chế độ thị trường (regime) chỉ với vài trăm quan sát), jackknife+ thường là lựa chọn tốt nhất — nó không hy sinh dữ liệu khan hiếm cho việc hiệu chỉnh. Cái giá phải trả là lần khớp lại mô hình.
Vấn đề với chuỗi thời gian tài chính: tính không hoán đổi được

Bảo đảm conformal chuẩn đòi hỏi tính hoán đổi được: phân phối đồng thời của bất biến dưới các phép hoán vị. Với dữ liệu i.i.d., điều này đúng một cách hiển nhiên.
Chuỗi thời gian tài chính không hoán đổi được. Lợi suất thể hiện:
- Phân cụm biến động (volatility clustering): Các giai đoạn biến động cao theo sau các giai đoạn biến động cao (hiệu ứng GARCH).
- Động lượng và hồi quy về trung bình: Tự tương quan trong lợi suất hoặc lợi suất bình phương.
- Thay đổi chế độ (regime changes): Phân phối dịch chuyển theo thời gian (thị trường tăng giá so với giảm giá).
Nếu bạn áp dụng split conformal một cách ngây thơ lên một chuỗi thời gian — dùng một phép chia hiệu chỉnh ngẫu nhiên — bạn vi phạm cấu trúc thời gian. Các điểm số hiệu chỉnh từ một năm 2017 yên ả sẽ không phản ánh độ bất định của một năm 2020 đầy biến động. Bảo đảm mức bao phủ của bạn sụp đổ.
Adaptive Conformal Inference (ACI)

Gibbs và Candes (2021, NeurIPS) giới thiệu Adaptive Conformal Inference (ACI) để xử lý dịch chuyển phân phối và dữ liệu không hoán đổi được. Ý tưởng tinh tế: thay vì dùng một mức bao phủ cố định, hãy điều chỉnh mức không bao phủ mục tiêu một cách trực tuyến dựa trên việc các khoảng gần đây có bao phủ kết cục thực hay không, và suy ra lại phân vị từ phân phối điểm số ở mỗi bước.
Thuật toán ACI
ACI không nhúc nhích độ rộng khoảng một cách trực tiếp. Nó duy trì một tham số không bao phủ thích nghi và tính lại phân vị conformal từ đó. Tại mỗi bước thời gian :
- Tính ngưỡng conformal là phân vị thực nghiệm của tập phần dư hiện tại (các điểm số hiệu chỉnh, cộng với bất kỳ điểm số thực hiện được nào cho đến nay): .
- Quan sát các đặc trưng , tạo ra khoảng .
- Quan sát giá trị thực và tính chỉ báo lỗi .
- Cập nhật mức (không phải độ rộng):
trong đó là một bước nhảy và là mức không bao phủ mục tiêu. Nếu một khoảng trượt (), co lại, điều này đẩy phân vị kế tiếp cao hơn và nới rộng khoảng; nếu nó bao phủ, tăng và các khoảng thắt chặt lại. Quan trọng là, ở đây tính theo đơn vị xác suất — nó nhúc nhích một mức trong , không phải ngưỡng theo đơn vị lợi suất thô — nên cùng một ứng xử hợp lý dù phần dư ở cỡ hay không.
Bảo đảm mức bao phủ cho ACI
ACI cung cấp một bảo đảm mức bao phủ trong dài hạn không phụ thuộc vào một mô hình phân phối:
Vì bị kẹp trong , tử số bị chặn bởi nhân một hằng số, nên vế phải là và tần suất không bao phủ thực nghiệm hội tụ về . Phát biểu chính xác: ACI bảo đảm tần suất không bao phủ thực nghiệm dài hạn hội tụ về với các chuỗi tùy ý (kể cả chuỗi đối kháng), miễn là các mức được điều chỉnh giữ trong vùng bị chặn — điều mà phép kẹp ép buộc. Đây là một bảo đảm về tần suất bao phủ, không phải về tính thông tin của khoảng: dưới một chuỗi thực sự đối kháng, các khoảng có thể nới rộng đến mức vô bổ trong khi vẫn đạt được mục tiêu bao phủ.
ACI tinh chỉnh động (DtACI)
Gibbs và Candes (2024, JMLR) cải tiến ACI bằng tinh chỉnh động bước nhảy . Thay vì cố định , họ duy trì một tập ứng viên và kết hợp chúng qua một quy tắc tổng hợp chuyên gia (expert-aggregation), ưu tiên có mức bao phủ gần đây gần với mục tiêu nhất.
Điều này giải quyết một vấn đề thực tiễn: một lớn thích nghi nhanh với các thay đổi chế độ nhưng tạo ra độ rộng khoảng dao động mạnh; một nhỏ thì ổn định nhưng chậm thích nghi. DtACI đánh đổi giữa hai thứ này một cách tự động.
Vì sao điều này quan trọng với giao dịch
Hãy xét một chiến lược tạo lập thị trường (market-making) dùng một mô hình dự báo lợi suất. Trong những thị trường yên ả, các khoảng conformal chặt — mô hình tự tin, và bạn có thể nắm các vị thế lớn hơn. Khi biến động vọt lên (mùa báo cáo lợi nhuận, công bố của FOMC, các cú sốc địa chính trị), mức ACI thích nghi và các khoảng nới rộng trong vòng vài bước thời gian. Việc định cỡ vị thế của bạn co lại để phản ứng, mà không cần bất kỳ mô hình biến động tường minh hay logic phát hiện chế độ nào.
Đây là việc định lượng độ bất định như một tín hiệu hạng nhất, không phải một thứ nghĩ thêm sau cùng.
Định cỡ vị thế với các khoảng conformal

Bây giờ hãy kết nối conformal prediction với việc định cỡ vị thế cụ thể. Biến then chốt là nửa độ rộng khoảng dự báo so với trường hợp phần dư tuyệt đối đối xứng. Với khoảng đối xứng , độ rộng đầy đủ là . Để giữ cho các công thức và mã nhất quán, chúng ta đo mọi thứ theo độ rộng đầy đủ xuyên suốt.
Định cỡ nghịch đảo độ rộng
Cách tiếp cận đơn giản nhất: định cỡ tỷ lệ nghịch với độ rộng khoảng.
trong đó là một hằng số tỷ lệ được hiệu chỉnh theo ngân sách rủi ro của bạn. Khi mô hình tự tin (khoảng hẹp), bạn nắm một vị thế lớn hơn. Khi không chắc chắn (khoảng rộng), bạn nắm một vị thế nhỏ hơn.
Điều này tương tự với nhắm mục tiêu biến động (volatility targeting) (), nhưng với một khác biệt then chốt: độ rộng khoảng conformal là một thước đo độ bất định không phụ thuộc phân phối, không phải một ước lượng biến động tham số. Nó nắm bắt độ bất định dự báo dưới bảo đảm mức bao phủ, không chỉ phương sai lợi suất.
Định cỡ theo tỷ số biên lợi (edge-ratio) và bộ lọc không-giao-dịch
Định cỡ nghịch đảo độ rộng thuần túy bỏ qua chính sức mạnh của tín hiệu. Một cải tiến tự nhiên là tỷ lệ theo tỷ số biên lợi (edge ratio) — dự báo điểm so với độ rộng khoảng:
Đây là một phép tương tự conformal của tỷ số tín hiệu trên nhiễu: lợi suất kỳ vọng chia cho một thước đo độ bất định không phụ thuộc phân phối. Chúng ta dùng nó cho cả việc định cỡ lẫn một bộ lọc không-giao-dịch.
Bộ lọc này có nguyên tắc. Nếu khoảng nằm vắt qua số không,
thì khoảng dự báo bao gồm cả lợi suất dương lẫn âm — lợi suất thực hiện được có thể hợp lý có dấu ngược với dự báo của bạn. Định nghĩa một ngưỡng biên lợi tối thiểu và chỉ giao dịch khi . Lưu ý rằng nội dung hình học của " đủ lớn để khoảng không còn vắt qua số không" chính xác là (vì khoảng vượt khỏi số không khi ). Hãy chọn trên thang đo thực tế của thông qua backtesting; với phần dư lợi suất theo ngày, thường thấp hơn nhiều so với , nên một rất nhỏ có thể chấp nhận gần như mọi giao dịch còn một lớn có thể không chấp nhận giao dịch nào. Hãy hiệu chỉnh nó theo dữ liệu của bạn.
Về "Conformal Kelly"
Thật cám dỗ khi gắn các khoảng conformal vào phần Kelly . Nhưng đã là một phần đầy đủ, bị chặn, được suy ra từ một xác suất thắng và một tỷ lệ chi trả ; nhân nó với một tỷ số không bị chặn như không có biện minh nào về mặt lý thuyết quyết định — nó có thể vượt quá 1 hoặc đổi dấu một cách độc lập với , và nó đếm trùng biên lợi mà đã mã hóa. Vì vậy chúng ta không trình bày một bộ nhân "conformal Kelly".
Nếu bạn muốn dẫn dắt Kelly từ khoảng, bạn phải thực sự suy ra và từ nó, điều này đòi hỏi một giả định tường minh về phân phối bên trong khoảng (các khoảng conformal cố ý không nói gì về điều đó — xem mục Hạn chế). Ví dụ, dưới một dạng giả định bên-trong-khoảng, bạn có thể xấp xỉ và một tỷ lệ chi trả từ hình học của khoảng — nhưng hãy nêu rõ giả định đó thật to, bởi vì nó tái đưa vào đúng cái cam kết tham số mà conformal prediction vốn nhằm tránh.
Lựa chọn thay thế trung thực, nhẹ-giả-định là dùng tỷ số biên lợi như một co rút Kelly phân số (fractional-Kelly shrinkage): tăng cỡ khi lợi suất kỳ vọng lớn so với khoảng, giảm cỡ khi nó nhỏ, và áp dụng điều này lên trên một mức trần cứng — một cách tường minh như một heuristic, không phải như "phần Kelly".
Cài đặt bằng Python
Đây là một cài đặt thực tế. Chúng tôi trình bày cả lối split/prefit lẫn lối thời gian (EnbPI), bởi vì toàn bộ điểm mấu chốt của bài viết này là dữ liệu tài chính không hoán đổi được.
Thiết lập và chuẩn bị dữ liệu
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 với MAPIE (Prefit)
Với conformal split/prefit, cv="prefit" đòi hỏi method="base" (bộ ước lượng split-conformal ngây thơ). Tùy chọn method="plus" là bộ ước lượng CV+/jackknife+ và không tương thích với cv="prefit" — nó cần một đối tượng cross-validation thay thế. Chúng tôi dùng tổ hợp đúng ở đây, và chuẩn hóa việc định cỡ theo độ rộng đầy đủ.
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 chuỗi thời gian với EnbPI
Bởi vì lợi suất không hoán đổi được, phép chia ngẫu nhiên/prefit ở trên chỉ là một mốc cơ sở. MapieTimeSeriesRegressor của MAPIE với method="enbpi" (Xu & Xie, 2021) dùng block bootstrap và cập nhật phần dư được thiết kế cho sự phụ thuộc thời gian. Đây là công cụ khớp với chính lập luận của bài viết.
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 (Trực tuyến)
Cho giao dịch trực tiếp, chúng tôi cài đặt ACI thực sự từ đầu: duy trì mức không bao phủ , cập nhật nó theo phép cộng, và suy ra lại phân vị từ tập phần dư ở mỗi bước. Hai chi tiết về mẫu hữu hạn quan trọng:
- Dùng thống kê thứ tự (order statistic), không phải một phân vị nội suy.
np.quantilenội suy theo mặc định, điều này có thể rơi xuống ngay dưới hạng cần thiết và gây bao phủ thiếu; hãy truyềnmethod="higher"(tương đương"inverted_cdf"). - Khi hạng cần thiết vượt quá ( nhỏ, mức bao phủ mục tiêu cao), ngưỡng đúng là (khoảng = toàn bộ đường thẳng), không phải kẹp về phần dư lớn nhất. Việc kẹp âm thầm phá vỡ bảo đảm .
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))
Ghép lại với nhau: vòng lặp 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
Đánh giá kết quả
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}")
Những cân nhắc thực tiễn
Chọn điểm bất phù hợp
Phần dư tuyệt đối là mặc định, nhưng nó giả định rằng khoảng dự báo nên đối xứng quanh dự báo điểm. Với lợi suất tài chính, các khoảng bất đối xứng thường hợp lý hơn:
- Conformalized Quantile Regression (CQR): Khớp các bộ hồi quy phân vị ở các mức và , rồi conformalize (Romano et al., 2019). Các khoảng thích nghi hình dạng của chúng theo phân phối cục bộ — rộng hơn ở phía giảm trong các đợt sụt giảm (drawdown), rộng hơn ở phía tăng trong các đợt tăng giá (rally). (Với CQR khoảng không còn đối xứng nữa, nên là độ rộng thực sự bằng cận-trên trừ cận-dưới — hãy tiếp tục dùng làm mẫu số ở mọi nơi.)
- Điểm số chuẩn hóa: , trong đó là một ước lượng biến động cục bộ. Điều này tạo ra các khoảng thích nghi theo điều kiện — chặt hơn trong các chế độ biến động thấp, rộng hơn trong các chế độ biến động cao — trong khi vẫn duy trì mức bao phủ biên.
Cỡ tập hiệu chỉnh
Bảo đảm mức bao phủ của split conformal đúng với cỡ tập hiệu chỉnh bất kỳ, nhưng độ rộng khoảng giảm khi tăng. Với rất nhỏ, thống kê thứ tự cần thiết có thể không tồn tại, trong trường hợp đó ngưỡng trung thực là (một khoảng vô bổ nhưng hợp lệ) — kẹp về phần dư lớn nhất sẽ âm thầm làm vô hiệu bảo đảm. Với giao dịch thực tế:
- điểm hiệu chỉnh cho các khoảng chặt một cách hợp lý.
- được ưu tiên để ước lượng phân vị ổn định.
- Với ACI, tập hiệu chỉnh chỉ dùng để khởi tạo; các cập nhật mức trực tuyến lo phần còn lại.
Tần suất huấn luyện lại
Mô hình cơ sở có thể trở nên lỗi thời. Hai cách tiếp cận:
- Huấn luyện lại định kỳ (ví dụ, hàng tháng) và hiệu chỉnh lại phân vị conformal.
- Dùng ACI và để mức thích nghi bù đắp cho sự lỗi thời của mô hình — các khoảng tự động nới rộng khi phần dư của mô hình tăng lên.
Cách 2 đơn giản hơn và hiệu quả đến mức bất ngờ. Lớp conformal hoạt động như một lưới an toàn: ngay cả khi mô hình suy thoái, tần suất bao phủ ACI trong dài hạn vẫn hội tụ về mục tiêu.
Chi phí giao dịch
Các khoảng conformal tương tác với chi phí giao dịch theo một cách hữu ích. Khi các khoảng rộng (độ bất định cao), các vị thế nhỏ, nên vòng quay giao dịch (turnover) thấp. Khi các khoảng thu hẹp (mô hình tự tin), các vị thế lớn lên — nhưng mô hình nhiều khả năng đúng hơn, nên vòng quay đáng để trả chi phí.
Bạn cũng có thể đưa chi phí giao dịch trực tiếp vào bộ lọc không-giao-dịch:
Điều này đảm bảo bạn chỉ giao dịch khi biên lợi ròng kỳ vọng vượt một ngưỡng được tỷ lệ theo độ rộng conformal — dùng cùng mẫu số như ở mọi nơi khác.
So sánh với các phương pháp truyền thống
| Tính chất | Khoảng tin cậy Gauss | Khoảng tin cậy Bootstrap | Khoảng dự báo Conformal |
|---|---|---|---|
| Giả định phân phối | Sai số chuẩn (normal) | i.i.d. + tiệm cận | Không (tính hoán đổi được) |
| Bảo đảm trên mẫu hữu hạn | Không | Không | Có |
| Hoạt động với mọi mô hình | Không | Có | Có |
| Thích nghi với phương sai thay đổi | Không | Một phần | Với CQR / điểm số chuẩn hóa |
| Xử lý dịch chuyển phân phối | Không | Không | Biến thể ACI / EnbPI |
| Chi phí tính toán | Thấp | Trung bình | Split: thấp; jackknife+: lần khớp lại; full: không khả thi |
Bootstrap "tiệm cận" chỉ ở bảo đảm của nó; nó vẫn giả định dữ liệu i.i.d./hoán đổi được và tính trơn, nên ô giả-định-phân-phối của nó không phải là "không-giả-định." Và cột "conformal" đơn lẻ che giấu các chi phí rất khác nhau giữa các biến thể, điều mà hàng chi phí giờ đã nêu rõ.
Hạn chế
Conformal prediction không phải phép màu. Những hạn chế chính với giao dịch:
-
Bao phủ biên, không phải bao phủ có điều kiện. Bảo đảm là theo nghĩa biên — lấy trung bình trên tính ngẫu nhiên của cả lẫn . Với một giá trị điều kiện cụ thể , mức bao phủ cục bộ có thể cao hơn hoặc thấp hơn . Conformalized quantile regression giải quyết điều này một phần.
-
Tính hoán đổi được là một yêu cầu có thật. Với split conformal, dữ liệu hiệu chỉnh và điểm kiểm tra phải hoán đổi được. Dữ liệu tài chính thì không. ACI và EnbPI nới lỏng điều này thành một bảo đảm dài hạn, nhưng mức bao phủ ngắn hạn có thể lệch đi.
-
Độ rộng khoảng không phải một mật độ xác suất. Một khoảng conformal cho bạn biết sẽ rơi vào đâu với xác suất , nhưng nó không nói gì về phân phối bên trong khoảng. Nó không phải vật thay thế cho một phân phối dự báo đầy đủ — đó chính xác là lý do vì sao suy ra một và của Kelly từ nó đòi hỏi một giả định bổ sung, tường minh.
-
Rác vào, rộng ra. Một mô hình cơ sở tồi tạo ra các khoảng rộng. Conformal prediction bảo đảm mức bao phủ chứ không bảo đảm tính hữu dụng. Nếu mô hình của bạn không có sức mạnh dự báo, các khoảng sẽ rộng đến mức bộ định cỡ vị thế không bao giờ giao dịch.
Tóm tắt
Conformal prediction cung cấp một khung chặt chẽ, không phụ thuộc phân phối để định lượng độ bất định, một khung tự nhiên ăn khớp với việc định cỡ vị thế:
- Split conformal cho việc hiệu chỉnh tĩnh, ngoại tuyến với mức bao phủ trên mẫu hữu hạn.
- Jackknife+ khi dữ liệu hiệu chỉnh khan hiếm và bạn muốn dùng toàn bộ quan sát (với cái giá là lần khớp lại).
- Adaptive conformal inference / EnbPI cho giao dịch trực tuyến với các thị trường không dừng.
- Định cỡ vị thế qua nghịch đảo độ rộng khoảng và một bộ lọc không-giao-dịch theo tỷ số biên lợi — và, nếu bạn đi tới Kelly, chỉ sau khi suy ra và một cách trung thực, không qua một bộ nhân vô căn cứ.
Lợi thế then chốt so với các lựa chọn tham số: bạn không bao giờ cần đặc tả hay kiểm chứng một giả định phân phối tham số. Các khoảng trung thực theo cấu trúc (theo nghĩa biên, dưới giả định hoán đổi được). Với một nhà giao dịch hệ thống, điều này có nghĩa là bớt đi một nguồn rủi ro mô hình — và trong một ngành mà rủi ro mô hình mang tính sống còn, điều đó quan trọng.
Tài liệu tham khảo:
- 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.
Tác Giả
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.