다목적 베이지안 최적화로 포트폴리오 파레토 전선 탐색하기
베이지안 최적화와 MOBO를 활용해 수익, CVaR, 거래비용, ESG처럼 상충하는 포트폴리오 목표를 파레토 전선에서 탐색하는 방법을 정리한다.
2026-08-14 · 최초 발행 2024-04-29
수익·위험·비용이 충돌하는 포트폴리오 탐색
포트폴리오 설계에서는 기대수익만 높인다고 해서 좋은 해가 되지 않는다. 변동성이나 위험(CVaR), 거래비용, 턴오버, ESG까지 함께 고려해야 하며, 이 목표들은 대개 서로 충돌한다. 다목적 베이지안 최적화(Multi-Objective Bayesian Optimization, MOBO)는 이런 조건에서 파레토 전선을 찾아가는 방법이다.
베이지안 최적화(Bayesian Optimization, BO)는 평가 비용이 높거나 내부 구조를 알기 어려운 목적함수를 다룰 때 사용한다. 확률적 대리모형(surrogate, 보통 Gaussian Process)으로 목적함수의 분포를 추정하고, 획득함수(acquisition)로 탐색과 활용의 균형을 정하면서 순차적으로 후보를 평가한다. 고비용 평가와 노이즈가 있는 환경에서도 탐색 효율을 확보하는 것이 핵심이다.
MOBO는 상충하는 2개 이상 목적을 동시에 최적화해 파레토 최적 해집(Pareto set)과 파레토 전선(Pareto front)을 근사한다. 스칼라화 기반의 ParEGO·Tchebycheff, 하이퍼볼륨 개선을 사용하는 EHVI/qEHVI, 엔트로피 기반 PESMO 등이 대표적인 획득함수 계열이다. 포트폴리오에서는 자산 가중치 벡터 w를 예산, 공매도, 섹터, 턴오버 제약 안에서 조정하며 기대효용을 높인다. 이때 수익↑, 위험↓(Var/CVaR/MaxDD), 비용↓, ESG↑가 동시에 목적이 될 수 있다.
대리모형부터 제약까지 함께 설계한다
대리모형은 목적과 제약의 불확실성을 추정하는 기반이다. GP/TFP/Deep Kernel 등을 사용할 수 있고, 이질적 노이즈와 비정상성에는 heteroscedastic GP 또는 변환을 적용한다. 포트폴리오 문제라면 표본 사이의 의존 구조인 공분산도 반영해야 한다. 因자 노출을 특징량으로 사용하고, 시계열 윈도우를 설정하며, 레짐 태깅을 병행하는 방식이 여기에 해당한다.
획득함수 선택은 탐색 비용과 운영 형태를 좌우한다. ParEGO는 무작위 스칼라화로 다목적 문제를 단일 목적 BO로 바꾸므로 계산이 단순하다. EHVI와 qEHVI는 하이퍼볼륨의 기대 개선을 최대화하며, qEHVI는 배치 병렬 최적화도 지원한다. PESMO와 Surrogate-based PES는 파레토 전선에 대한 정보 이득을 크게 만드는 데 초점을 두지만 계산량 증가라는 트레이드오프가 있다.
실현 가능성은 나중에 확인할 부가 조건이 아니다. 예산(Σw=1), 박스(0≤w≤u), 섹터·엑스포저, 턴오버(∥w−w_prev∥₁≤τ)는 하드 제약과 소프트 제약으로 섞일 수 있다. 제약 GP로 위반 확률을 낮추거나, 가중 페널티 및 수리(repair: 프로젝션-온-심플렉스)를 적용할 수 있다.
백테스트와 CVaR 추정은 비용이 크다. qEHVI로 후보를 배치 제안하고 비동기 업데이트로 자원을 활용하며, 짧은 윈도우에서 사전 스크리닝한 뒤 전체 기간을 정밀 평가하는 멀티-피델리티 방식으로 비용을 줄일 수 있다. 리스크는 CVaR/ES, Drawdown, TE를 목적 또는 제약으로 두고, 부트스트랩·블록부트스트랩으로 표본 불확실성을 반영한다. 분포 전이에는 분포 시나리오 집합과 와서스타인 볼을 사용하는 로버스트 BO도 적용 가능하다.
포트폴리오 문제에서 나타나는 선택지
자산배분에서는 기대수익↑, CVaR↓, 턴오버↓를 목표로 두고 Σw=1, 0≤w≤u, 섹터·因자 노출 제한을 적용할 수 있다. 레짐 분할, 서브샘플 부트스트랩, MOBO 탐색, 파레토 집합 선택, 워크포워드 검증의 흐름으로 구성한다.
인덱스 트래킹과 인핸스드 베타에서는 추적오차(TE)↓, 거래비용↓, ESG 스코어↑가 함께 목표가 된다. 이때 보유 종목수 L0/L1 제약을 병행한다. 대체자산 혼합 전략은 샤프↑, MaxDD↓, 상관도↓(분산화)를 추구하며, 비정상 시계열에는 로그수익·윈도우 변환과 헤지 제약을 함께 둔다.
획득함수의 성능과 운영성 비교
| 방법 | 성능(파레토 근사) | 확장성(차원/배치) | 일관성(잡음 대응) | 안정성(수렴) | 운영 편의(구현/튜닝) |
|---|---|---|---|---|---|
| ParEGO(스칼라화) | 중 | 높음 | 중 | 중 | 높음 |
| EHVI | 높음 | 중 | 중상 | 높음 | 중 |
| qEHVI(배치) | 높음 | 높음 | 중상 | 높음 | 중 |
| PESMO | 높음 | 중하 | 높음 | 중상 | 중하 |
고차원·잡음 환경에서는 qEHVI 또는 ParEGO에 차원축소(因자화, 그룹화)를 병행하는 방식을 권장한다.
평가 파이프라인에서 확인할 운영 조건
입력에는 자산별 일별/주별 수익률, 거래비용/슬리피지 모델, 제약 정의, 초기 포트폴리오가 들어간다. 처리 단계에서는 공분산을 난수 안정화(shrinkage)로 추정하고, CVaR은 α=0.95 및 블록부트스트랩으로 계산한다. 목적과 제약에 대한 GP를 학습할 때는 이질적 노이즈를 고려한다.
획득함수는 배치 평가에는 qEHVI, 간단한 구성에는 ParEGO를 선택할 수 있으며 비동기 평가로 운영한다. 제안된 후보에는 심플렉스 투영과 섹터·라벨 제약을 적용한다. 결과물은 파레토 해집, 하이퍼볼륨/TE/샤프 등의 메트릭, 선택된 포트폴리오와 기대 성과 범위다.
제약 위반이 자주 생기면 페널티 가중치를 올리거나 제약 GP를 추가한다. 데이터 누락 또는 편향으로 백테스트가 실패하면 윈도우를 재설정하고, 외도치 윈저라이징과 재추정을 수행한다. 수렴이 정체될 경우에는 탐색률(ξ, β)을 높이고, 멀티-피델리티의 초반 범위를 넓히거나 초기화를 재시작한다.
데이터 거버넌스에서는 리빌런싱 누출을 막기 위한 워크포워드와 이벤트 라벨 시점 정합성 검증이 필요하다. 시간 격자 CV, 레짐별 중복 검증, 파레토 해의 단순성(규제화) 선호는 과최적화를 줄이는 장치가 된다. 배포 시에는 하드 리스크 한도(Δw·VaR·TE), 승인 워크플로, 변경 추적을 둔다.
qEHVI로 수익과 CVaR을 탐색하는 예제
전제조건은 다음과 같다.
- Python 3.10+, torch 2.2+, botorch 0.10+, numpy, pandas
- 가정: 8개 자산, 과거 수익률로부터 평균/표본 시뮬레이션
설치는 아래 명령을 사용한다.
- pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
- pip install botorch numpy pandas
설계변수 x ∈ R^8는 소프트맥스를 거쳐 심플렉스의 가중치 w가 되며, 이때 Σw=1, w≥0이다. 목적1은 −E[Return] 최소화(= 수익 최대화), 목적2는 CVaR_95% 최소화다. 획득함수는 qEHVI이며 초기 시드는 라틴하이퍼큐브 샘플을 사용한다.
# Python 3.10, torch 2.2, botorch 0.10
import torch, numpy as np
from botorch.models import SingleTaskGP, ModelListGP
from botorch.fit import fit_gpytorch_mll
from botorch.acquisition.multi_objective.monte_carlo import qExpectedHypervolumeImprovement
from botorch.optim.optimize import optimize_acqf
from botorch.utils.transforms import normalize, unnormalize
from gpytorch.mlls.exact_marginal_log_likelihood import ExactMarginalLogLikelihood
torch.manual_seed(42)
device = torch.device("cpu")
dtype = torch.double
# 1) 가상 수익률 데이터 생성 (실무에서는 실제 수익률 로드)
T, N = 1000, 8 # 1000일, 8자산
rets = np.random.multivariate_normal(
mean=np.random.uniform(0.0002, 0.0008, size=N),
cov=(0.0005*np.eye(N) + 0.0003*np.ones((N,N))), # 약한 상관
size=T
)
rets = torch.tensor(rets, dtype=dtype, device=device)
def to_weights(x):
# x: (..., N) unconstrained -> simplex
w = torch.softmax(x, dim=-1)
return w
def eval_objectives(x):
# x: (n, N) unconstrained
w = to_weights(x) # (n, N)
# 포트폴리오 경로 수익
port_ret_t = (rets @ w.t()) # (T, n)
# 목적1: -평균수익
mean_ret = port_ret_t.mean(dim=0) # (n,)
obj1 = -mean_ret
# 목적2: CVaR_95%
alpha = 0.95
sorted_ret, _ = torch.sort(port_ret_t, dim=0)
q_idx = int(np.floor((1 - alpha) * T))
tail = sorted_ret[:q_idx, :] # 하위 5%
cvar = -tail.mean(dim=0) # 손실 관점(+)
obj2 = cvar
Y = torch.stack([obj1, obj2], dim=-1) # (n, 2)
return Y
# 2) 초기 데이터 (라틴하이퍼큐브 유사: 난수)
n_init = 16
X_init = torch.randn(n_init, N, dtype=dtype, device=device)
Y_init = eval_objectives(X_init)
# 3) GP 모델 적합
models = []
for i in range(2):
gp = SingleTaskGP(X_init, Y_init[..., i:i+1])
mll = ExactMarginalLogLikelihood(gp.likelihood, gp)
fit_gpytorch_mll(mll)
models.append(gp)
mobo = ModelListGP(*models)
# 4) 레퍼런스점(하이퍼볼륨 기준): 경험적 최악값 근사
ref_point = Y_init.quantile(q=0.9, dim=0).tolist()
# 5) qEHVI 획득함수 정의
q = 4 # 배치 후보
acq = qExpectedHypervolumeImprovement(model=mobo, ref_point=ref_point, partitioning=None)
# 6) 반복 최적화 루프
X = X_init.clone()
Y = Y_init.clone()
for it in range(5): # 5회 배치 업데이트
bounds = torch.tensor([[-3.0]*N, [3.0]*N], dtype=dtype, device=device)
cand, _ = optimize_acqf(
acq_function=acq,
bounds=bounds,
q=q,
num_restarts=8,
raw_samples=256,
)
Y_cand = eval_objectives(cand)
# 데이터 누적 및 재학습
X = torch.cat([X, cand], dim=0)
Y = torch.cat([Y, Y_cand], dim=0)
models = []
for i in range(2):
gp = SingleTaskGP(X, Y[..., i:i+1])
mll = ExactMarginalLogLikelihood(gp.likelihood, gp)
fit_gpytorch_mll(mll)
models.append(gp)
mobo = ModelListGP(*models)
acq = qExpectedHypervolumeImprovement(model=mobo, ref_point=ref_point, partitioning=None)
# 7) 파레토 근사 추출
# 비지배 해 선별
def is_nondominated(Y):
n = Y.shape[0]
mask = torch.ones(n, dtype=torch.bool)
for i in range(n):
if not mask[i]: continue
dominated = ( (Y <= Y[i]).all(dim=1) & (Y < Y[i]).any(dim=1) )
mask[dominated] = False
return mask
nd_mask = is_nondominated(Y)
X_star = X[nd_mask]
Y_star = Y[nd_mask]
W_star = to_weights(X_star)
print("Pareto candidates:", W_star.shape[0])
print("Example weight vector:", W_star[0].data.numpy().round(4))
print("Objectives [ -mean_ret , CVaR ]:", Y_star[0].data.numpy().round(6))
실제 운영에서는 거래비용·슬리피지·체결 제약을 반영해야 한다. 레짐 전환과 데이터 누수를 막고 워크포워드 검증을 수행하는 것도 필수다.
평가 예산과 운영 안정성에서 얻는 효과
동일 평가 예산에서 그리드·랜덤 탐색 대비 50~80% 평가 수 절감이 가능하다. 하이퍼볼륨 지표 기준으로 파레토 전선을 개선하고, 리스크 한도 안에서 목표 수익을 달성하는 비율을 높일 수 있다.
배치·비동기 평가는 자원 효율을 높이고, 멀티-피델리티는 계산비용 절감에 기여한다. 제약 GP는 제약 위반 리스크를 낮추는 수단이 된다. GP 기반 불확실성 추정과 qEHVI/ParEGO 등의 획득함수, 제약 모델링을 결합하면 상충 목표가 표준인 포트폴리오 설계에서 실현 가능한 해 공간의 파레토 전선을 효율적으로 탐색할 수 있다.