TCA(Transaction Cost Analysis)란?
자동매매 시스템을 운영하다 보면 백테스트 수익률과 실제 수익률 사이에 괴리가 생깁니다. 이 차이의 핵심 원인이 바로 거래비용(Transaction Cost)입니다. TCA는 주문 체결 과정에서 발생하는 모든 비용을 정량적으로 분석하는 기법으로, 기관 투자자들이 브로커 성과를 평가할 때 널리 사용합니다.
개인 퀀트 트레이더에게도 TCA는 필수입니다. 전략의 실제 알파가 거래비용을 초과하는지 판단하지 못하면, 수익을 내는 것처럼 보이는 전략이 실은 손실을 만들 수 있기 때문입니다.
거래비용의 구성 요소
거래비용은 크게 명시적 비용과 암묵적 비용으로 나뉩니다.
| 구분 | 항목 | 설명 |
|---|---|---|
| 명시적 비용 | 수수료(Commission) | 거래소·브로커에 지불하는 고정 수수료 |
| 명시적 비용 | 세금(Tax) | 거래세, 양도소득세 등 |
| 암묵적 비용 | 스프레드(Spread) | 매수·매도 호가 차이 |
| 암묵적 비용 | 슬리피지(Slippage) | 의도 가격과 실제 체결 가격 차이 |
| 암묵적 비용 | 시장 충격(Market Impact) | 대량 주문이 가격을 밀어내는 효과 |
| 암묵적 비용 | 기회비용(Opportunity Cost) | 미체결로 놓친 수익 |
TCA 핵심 지표: Implementation Shortfall
Implementation Shortfall(IS)은 TCA에서 가장 널리 쓰이는 벤치마크입니다. 의사결정 시점의 가격과 실제 체결 가격 사이의 차이를 측정합니다.
IS = (실제 포트폴리오 수익) - (이론적 포트폴리오 수익)
# 매수 주문 기준
IS_bps = ((체결평균가격 - 의사결정가격) / 의사결정가격) × 10000
IS가 양수이면 비용이 발생한 것이고, 음수이면 유리하게 체결된 것입니다. 일반적으로 10~50 bps 범위가 소매 트레이더의 평균적인 IS입니다.
파이썬으로 TCA 구현하기
실제 거래 로그를 분석하는 TCA 클래스를 파이썬으로 구현해 보겠습니다. 아래 코드는 거래소 API에서 받은 체결 데이터를 기반으로 핵심 비용 지표를 계산합니다.
import pandas as pd
import numpy as np
from dataclasses import dataclass
from typing import List
@dataclass
class Fill:
"""개별 체결 정보"""
timestamp: str
side: str # 'buy' or 'sell'
price: float
quantity: float
fee: float
decision_price: float # 신호 발생 시점 가격
arrival_price: float # 주문 제출 시점 가격
class TransactionCostAnalyzer:
def __init__(self, fills: List[Fill]):
self.df = pd.DataFrame([vars(f) for f in fills])
self.df['timestamp'] = pd.to_datetime(self.df['timestamp'])
self.df['notional'] = self.df['price'] * self.df['quantity']
def implementation_shortfall(self) -> pd.DataFrame:
"""Implementation Shortfall 계산 (bps 단위)"""
df = self.df.copy()
sign = np.where(df['side'] == 'buy', 1, -1)
df['is_bps'] = sign * (
(df['price'] - df['decision_price'])
/ df['decision_price']
) * 10000
df['is_bps'] += (df['fee'] / df['notional']) * 10000
return df[['timestamp', 'side', 'is_bps', 'notional']]
def spread_cost(self, orderbook_snapshots: pd.DataFrame) -> float:
"""평균 스프레드 비용 (bps)"""
spreads = (
(orderbook_snapshots['best_ask']
- orderbook_snapshots['best_bid'])
/ orderbook_snapshots['mid_price']
) * 10000
return spreads.mean()
def market_impact(self, window_minutes: int = 5) -> pd.DataFrame:
"""시장 충격 측정: 체결 전후 가격 변화"""
df = self.df.copy()
sign = np.where(df['side'] == 'buy', 1, -1)
df['impact_bps'] = sign * (
(df['price'] - df['arrival_price'])
/ df['arrival_price']
) * 10000
return df[['timestamp', 'side', 'impact_bps', 'quantity']]
def summary(self) -> dict:
"""TCA 요약 리포트"""
is_df = self.implementation_shortfall()
impact_df = self.market_impact()
total_notional = self.df['notional'].sum()
total_fees = self.df['fee'].sum()
return {
'total_trades': len(self.df),
'total_notional': round(total_notional, 2),
'total_fees': round(total_fees, 2),
'fee_rate_bps': round(
(total_fees / total_notional) * 10000, 2
),
'avg_is_bps': round(is_df['is_bps'].mean(), 2),
'median_is_bps': round(is_df['is_bps'].median(), 2),
'avg_impact_bps': round(
impact_df['impact_bps'].mean(), 2
),
'worst_is_bps': round(is_df['is_bps'].max(), 2),
}
실전 데이터로 TCA 실행하기
바이낸스 선물 거래 로그를 활용한 실제 분석 예제입니다.
fills = [
Fill('2026-03-13 09:00:01', 'buy', 84250.5, 0.01,
0.84, 84200.0, 84220.0),
Fill('2026-03-13 09:15:03', 'sell', 84500.0, 0.01,
0.85, 84550.0, 84530.0),
Fill('2026-03-13 10:30:12', 'buy', 84100.2, 0.02,
1.68, 84050.0, 84070.0),
Fill('2026-03-13 11:45:07', 'sell', 84350.8, 0.02,
1.69, 84400.0, 84380.0),
]
analyzer = TransactionCostAnalyzer(fills)
report = analyzer.summary()
for key, value in report.items():
print(f"{key}: {value}")
# 출력 예시:
# total_trades: 4
# total_notional: 3372.03
# total_fees: 5.06
# fee_rate_bps: 15.01
# avg_is_bps: 7.23
# median_is_bps: 6.85
# avg_impact_bps: 4.12
# worst_is_bps: 11.90
VWAP·TWAP 벤치마크 비교 분석
IS 외에도 VWAP(Volume Weighted Average Price)과 TWAP(Time Weighted Average Price) 벤치마크를 함께 비교하면 더 입체적인 분석이 가능합니다.
def vwap_benchmark(trades_df: pd.DataFrame,
market_df: pd.DataFrame) -> float:
"""VWAP 대비 체결 성과 (bps)"""
market_vwap = (
(market_df['price'] * market_df['volume']).sum()
/ market_df['volume'].sum()
)
exec_vwap = (
(trades_df['price'] * trades_df['quantity']).sum()
/ trades_df['quantity'].sum()
)
return ((exec_vwap - market_vwap) / market_vwap) * 10000
def twap_benchmark(trades_df: pd.DataFrame,
market_df: pd.DataFrame) -> float:
"""TWAP 대비 체결 성과 (bps)"""
market_twap = market_df['price'].mean()
exec_avg = (
(trades_df['price'] * trades_df['quantity']).sum()
/ trades_df['quantity'].sum()
)
return ((exec_avg - market_twap) / market_twap) * 10000
매수 주문에서 VWAP 벤치마크가 음수라면 시장 평균보다 저렴하게 매수한 것이므로 양호한 체결입니다. 반대로 양수라면 개선이 필요합니다.
시간대별·수량별 비용 패턴 분석
TCA의 진정한 가치는 비용의 패턴을 발견하는 데 있습니다. 어떤 시간대에 비용이 높은지, 주문 크기와 비용의 관계는 어떤지 분석하면 전략을 최적화할 수 있습니다.
def analyze_cost_patterns(analyzer: TransactionCostAnalyzer):
"""시간대·수량별 비용 패턴"""
is_df = analyzer.implementation_shortfall()
is_df['hour'] = is_df['timestamp'].dt.hour
# 시간대별 평균 IS
hourly = is_df.groupby('hour')['is_bps'].agg(
['mean', 'std', 'count']
)
print("=== 시간대별 IS (bps) ===")
print(hourly.round(2))
# 수량 구간별 시장 충격
impact_df = analyzer.market_impact()
impact_df['size_bucket'] = pd.qcut(
impact_df['quantity'], q=4,
labels=['소량', '중소', '중대', '대량']
)
by_size = impact_df.groupby('size_bucket')[
'impact_bps'
].mean()
print("n=== 수량별 시장 충격 (bps) ===")
print(by_size.round(2))
일반적으로 유동성이 낮은 시간대(아시아 새벽, 주말)에 비용이 높고, 주문 수량이 클수록 시장 충격이 커집니다. 이 패턴을 파악하면 주문 시점과 분할 전략을 최적화할 수 있습니다.
TCA 결과를 전략에 반영하는 방법
분석 결과를 실제 전략 개선에 활용하는 핵심 포인트를 정리합니다.
- 백테스트 비용 모델 보정: TCA로 측정한 실제 IS를 백테스트의 슬리피지 파라미터로 사용하면 현실적인 시뮬레이션이 가능합니다.
- 주문 분할 최적화: 시장 충격이 큰 종목은 TWAP·VWAP 알고리즘으로 분할 체결하여 비용을 줄입니다.
- 거래 빈도 조절: IS가 전략 알파보다 크다면 거래 빈도를 낮추거나 진입 기준을 강화해야 합니다.
- 거래소 비교: 동일 종목을 여러 거래소에서 체결한 데이터를 비교하면 최적의 체결 경로를 찾을 수 있습니다.
- 실시간 모니터링: IS가 임계값을 넘으면 알림을 보내도록 설정하여 비정상 체결을 즉시 감지합니다.
수익성 판단 프레임워크
TCA 결과를 기반으로 전략의 순수익성을 판단하는 공식입니다.
def is_strategy_profitable(
gross_alpha_bps: float,
avg_is_bps: float,
fee_rate_bps: float,
safety_margin: float = 1.5
) -> dict:
"""전략 순수익성 판단"""
total_cost = avg_is_bps + fee_rate_bps
net_alpha = gross_alpha_bps - total_cost
cost_ratio = total_cost / gross_alpha_bps if gross_alpha_bps else float('inf')
return {
'gross_alpha_bps': gross_alpha_bps,
'total_cost_bps': round(total_cost, 2),
'net_alpha_bps': round(net_alpha, 2),
'cost_ratio': round(cost_ratio, 4),
'profitable': net_alpha > 0,
'robust': total_cost * safety_margin < gross_alpha_bps,
'verdict': (
'✅ 강건한 수익성'
if total_cost * safety_margin < gross_alpha_bps
else '⚠️ 수익성 취약' if net_alpha > 0
else '❌ 비용 초과'
)
}
# 사용 예시
result = is_strategy_profitable(
gross_alpha_bps=25, # 백테스트 기대 알파
avg_is_bps=7.2, # TCA 측정 IS
fee_rate_bps=15.0 # 수수료율
)
# verdict: '❌ 비용 초과' → 전략 개선 필요
cost_ratio가 0.5 이하이면 안정적, 0.5~0.8이면 주의, 0.8 이상이면 전략 재검토가 필요합니다. 기관에서는 보통 1.5배의 안전 마진을 적용합니다.
마무리
TCA는 퀀트 자동매매에서 백테스트와 실전의 간극을 메우는 핵심 도구입니다. 거래비용을 정확히 측정하지 않으면 수익을 내는 것처럼 보이는 전략이 실은 돈을 잃고 있을 수 있습니다. 오늘 소개한 Implementation Shortfall, VWAP·TWAP 벤치마크, 패턴 분석을 조합하면 자신만의 체결 성과 리포트를 만들 수 있습니다.
관련 글도 함께 확인해 보세요: