슬리피지 모델링
우리가 거래할 때는 현재 시장 가격으로 거래하는 것이 아니라, 시장이 우리 주문을 체결해 주는 가격으로 거래합니다. 우리가 보는 가격과 실제로 받는 가격 사이에는 차이가 있습니다. 이 차이를 슬리피지라고 합니다. 슬리피지를 사전에 예측하는 것은 경험적인 문제이지만, 이를 모델링하면 단순히 추측하거나 모델에서 완전히 배제하는 것보다 더 나은 결과를 얻을 수 있습니다.
단순 슬리피지 모델
다른 주문의 규모에 비해 지나치게 큰 주문을 시장에 내면, 우리 주문은 우리가 보는 가격과 다른 가격에 체결됩니다. 그러나 자산의 최근 가격 이력을 바탕으로 예상 슬리피지를 해석적으로 모델링한 다음, 슬리피지의 영향을 최소화하도록 주문 규모를 정할 수 있습니다. 이는 세 가지 구성 요소의 함수입니다.
자산의 변동성은 슬리피지의 주된 요인입니다. 자산의 변동성이 클수록 더 큰 슬리피지가 예상됩니다. 최근 가격 이력으로부터 자산의 변동성을 추정한 다음, 이를 이용해 슬리피지를 추정하는 방식으로 이를 모델링할 수 있습니다. 변동성은 단순 이동 윈도 평균을 사용하여 계산할 수 있습니다.
슬리피지의 또 다른 구성 요소는 거래량 영향 계수입니다. 이는 평균 일일 거래량에 비해 우리 주문의 규모가 얼마나 큰지에 따라 가격이 움직이는 정도입니다. 최근 가격 이력으로부터 자산의 평균 일일 거래량을 추정한 다음, 이를 이용해 슬리피지를 추정하는 방식으로 이를 모델링할 수 있습니다.
슬리피지의 세 번째 구성 요소는 스프레드입니다. 이는 매수 호가와 매도 호가의 차이입니다. 자산의 최근 가격 이력으로부터 스프레드를 추정하는 방식으로 이를 모델링할 수 있습니다. 전체 슬리피지는 이러한 구성 요소의 합으로 모델링할 수 있습니다:
여기서:
여기서:
- 는 현재 가격입니다
- 는 거래량 영향 계수입니다
- 는 스프레드 계수입니다
- 는 주문 규모입니다
- 는 평균 일일 거래량입니다
- 는 변동성입니다
- 는 매수-매도 스프레드입니다
- 은 베이시스 포인트 단위의 최소 슬리피지입니다
import numpy as np
import pandas as pd
from typing import Union, Optional
class SlippageModel:
def __init__(
self,
volatility_window: int = 20,
volume_impact_factor: float = 0.1,
spread_factor: float = 0.5,
min_slippage_bps: float = 1.0
):
self.volatility_window = volatility_window
self.volume_impact_factor = volume_impact_factor
self.spread_factor = spread_factor
self.min_slippage_bps = min_slippage_bps
def estimate_slippage(
self,
price: float,
order_size: float,
avg_daily_volume: float,
volatility: Optional[float] = None,
bid_ask_spread: Optional[float] = None
) -> float:
# Base slippage on minimum bps
base_slippage = price * (self.min_slippage_bps / 10000)
# Volume impact component
volume_ratio = order_size / avg_daily_volume
volume_impact = price * self.volume_impact_factor * volume_ratio
# Volatility component (if provided)
volatility_impact = 0
if volatility is not None:
volatility_impact = price * volatility * self.volume_impact_factor
# Spread component
spread_impact = 0
if bid_ask_spread is not None:
spread_impact = bid_ask_spread * self.spread_factor
else:
# Estimate spread as 0.1% of price if not provided
spread_impact = price * 0.001 * self.spread_factor
# Combine all components
total_slippage = base_slippage + volume_impact + volatility_impact + spread_impact
return total_slippage
def calculate_volatility(self, prices: pd.Series) -> float:
returns = prices.pct_change().dropna()
volatility = returns.rolling(window=self.volatility_window).std()
return volatility.iloc[-1]
def estimate_trade_price(
self,
current_price: float,
order_size: float,
avg_daily_volume: float,
is_buy: bool,
volatility: Optional[float] = None,
bid_ask_spread: Optional[float] = None
) -> float:
slippage = self.estimate_slippage(
current_price,
order_size,
avg_daily_volume,
volatility,
bid_ask_spread
)
# Add slippage for buys, subtract for sells
execution_price = current_price + (slippage if is_buy else -slippage)
return execution_price
이제 이 모델을 사용하려면 자산의 변동성과 매수-매도 스프레드를 추정해야 합니다. 이 예제에서는 주식과 유사한 자산을 사용하겠습니다.
# Initialize model
model = SlippageModel(
volatility_window=20,
volume_impact_factor=0.1,
spread_factor=0.5,
min_slippage_bps=1.0
)
# Example trade
current_price = 100.0
order_size = 1000
avg_daily_volume = 1000000
volatility = 0.02 # 2% daily volatility
bid_ask_spread = 0.05
# Estimate slippage for a buy order
slippage = model.estimate_slippage(
current_price,
order_size,
avg_daily_volume,
volatility,
bid_ask_spread
)
execution_price = model.estimate_trade_price(
current_price,
order_size,
avg_daily_volume,
is_buy=True,
volatility=volatility,
bid_ask_spread=bid_ask_spread
)
print(f"Current Price: ${current_price:.2f}")
print(f"Estimated Slippage: ${slippage:.4f}")
print(f"Execution Price: ${execution_price:.2f}")
print(f"Slippage Impact (bps): {(slippage/current_price)*10000:.2f}")
Abdi와 Ranaldo 모델
Abdi와 Ranaldo(2017) 모델은 금융 시장에서 매수-매도 스프레드를 추정하기 위한 포괄적인 프레임워크를 제공합니다. 이 모델은 자산 가격의 근본적인 변동성을 반영하는 효율적 가격 분산이라는 개념에 기반을 두고 있습니다. 이 모델은 고가, 저가, 종가를 분석하여 매수-매도 스프레드를 더 정확하게 추정하는 것을 목표로 합니다. 이 모델의 핵심(Core)은 각 거래 기간의 평균 가격을 계산하는 것이며, 이는 다음과 같이 표기합니다:
이는 각 거래 기간에 대해 계산됩니다. 이 평균 가격은 가격 움직임을 평가하기 위한 기준점 역할을 합니다. 그다음 모델은 시간에 따른 가격 변동의 동역학을 포착하기 위해 로 표현되는 이 평균 가격 변화량의 분산을 살펴봅니다. 효율적 가격 분산을 추정하기 위해, 모델은 평균 가격 변화량의 분산을 상수 계수로 조정하여 사용합니다. 효율적 가격 분산은 다음과 같이 계산됩니다:
여기서 은 모델의 이론적 기초에서 도출된 상수입니다(유도 과정은 원 논문을 참고하십시오). 이 조정은 시장 미시구조가 가격 형성에 미치는 영향을 반영합니다. 마지막으로, 모델은 종가의 분산과 효율적 가격 분산 사이의 관계를 이용하여 매수-매도 스프레드를 도출합니다. 스프레드()는 다음과 같이 추정됩니다:
이 식은 스프레드가 관측된 가격 움직임과 근본적인 시장 상황 모두에 의해 어떻게 영향을 받는지 보여 주며, 트레이더에게 슬리피지 추정을 위한 견고한 도구를 제공합니다. 자세한 내용은 원 논문을 여기에서 참고하십시오.
def abdi_ranaldo_spread_estimator(
highs: pd.Series,
lows: pd.Series,
closes: pd.Series
) -> tuple[float, float]:
k1 = 4 * np.log(2) # Constant used in the model
# Calculate η_t = (high + low)/2 for each day
eta = (highs + lows) / 2
# Calculate variance of η changes
eta_changes = eta.diff().dropna()
var_eta_changes = eta_changes.var()
# Estimate efficient price variance
efficient_variance = var_eta_changes / (2 - k1/2)
# Calculate variance of close vs average etas
eta_avg = (eta + eta.shift(-1)) / 2 # (η_t + η_{t+1})/2
close_vs_eta = closes - eta_avg
var_close_eta = close_vs_eta.dropna().var()
# Solve for spread
spread = np.sqrt(4 * (var_close_eta - (0.5 + k1/8) * efficient_variance))
return spread, efficient_variance
이 함수를 사용하면 매수-매도 스프레드를 추정한 뒤, 이를 이용해 이전과 동일한 클래스 인터페이스로 거래의 슬리피지를 추정하는 모델을 만들 수 있습니다.
class AbdiRanaldoModel(SlippageModel):
def __init__(
self,
volatility_window: int = 20,
volume_impact_factor: float = 0.1,
spread_factor: float = 0.5,
min_slippage_bps: float = 1.0
):
super().__init__(volatility_window, volume_impact_factor, spread_factor, min_slippage_bps)
def estimate_market_spread(
self,
highs: pd.Series,
lows: pd.Series,
closes: pd.Series
) -> float:
"""
Estimates the bid-ask spread using the Abdi-Ranaldo method.
"""
spread, _ = abdi_ranaldo_spread_estimator(highs, lows, closes)
return spread
이제 이 모델을 사용하여 개별 거래의 최근 가격 이력으로부터 해당 거래의 슬리피지를 추정할 수 있습니다.
model = AbdiRanaldoModel()
# Estimate the market spread
estimated_spread = model.estimate_market_spread(
data['High'],
data['Low'],
data['Close']
)
# Use the estimated spread in slippage calculation
slippage = model.estimate_slippage(
price=data['Close'].iloc[-1],
order_size=1000,
avg_daily_volume=data['Volume'].iloc[-1],
bid_ask_spread=estimated_spread
)
# Estimate the execution price
execution_price = model.estimate_trade_price(
current_price=data['Close'].iloc[-1],
order_size=1000,
avg_daily_volume=data['Volume'].iloc[-1],
is_buy=True,
bid_ask_spread=estimated_spread
)