일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- 실전 금융 머신 러닝 완벽 분석
- 금융딥러닝
- AFML
- 아비트라지랩 #arbitragelab #아비트라지 #arbitrage #residual #reversion #residualreverstion #hudsonthames #허드슨
- >
- 테슬라 #tesla #ai #퀀트
- 틱데이터
- 금융머신러닝
- 틱
- Today
- Total
알파트로스
[ArbLab] Vectorized Backtest Methodology for Pairs Trading 본문
[ArbLab] Vectorized Backtest Methodology for Pairs Trading
알파트로스 2024. 7. 8. 22:46
Vectorized Backtesting and Pairs Trading
- 벡터화된 백테스트는 페어 트레이딩 투자 전략을 올바르게 백테스트하는 방법으로 설명되어 있다. 롱-온리 포트폴리오의 경우에는 간단하지만, 롱-숏 포트폴리오에서는 문제들이 생긴다.
- 연구를 백테스트에 의존하면 안된다 백테스트는 마지막 검증 단계 일뿐이다
Equity Curve
- Equity Curve란 전략의 성과를 한눈에 살펴 볼 수 있는 일종의 검증 도구로 VaR, 거래 비용, 시장 영향, 슬리피지 등 세부 항목들보다는 어느 정도 성과인지를 확인하는 빠른 백테스트라 보면 된다.
- Equity Curve 계산은 간단해 보이지만, 실제로는 정확한 방법론에 대해서는 논의가 부족해서 오류가 많다
예를 들어, 차익 거래 기회에서 수익률을 계산할때, 즉 두 주식X와 Y를 정확한 hedge ratio에 따라 생성(논의를 간단하게 하기 위해 X를 1단위 매수하고 Y를 정확히 1단위 매도했다고 가정하자)한 스프레드가 0달러일 때 진입하고, 스프레드가 1달러일 때 종료하는 알고리즘을 따랐을때 얻은 수익을 어떻게 계산해야 할까? - 단일 주식을 위한 equity curve 계산은 쉽지만, 포트폴리오로 변환되면 문제가 복잡해진다. 특히, 하나의 포트폴리오 내의 각 구성 요소에 대해 롱 포지션(long position)과 숏 포지션(short position)을 허용할 때 상황이 달라진다. 페어 트레이딩에서 롱-숏 포트폴리오(스프레드)는 self-financing 성격을 가진다, 즉 숏 포지션의 비용으로 롱 포지션의 비용을 (브로커에 따라)충당할 수 있기도 하다. 이 경우 투자 비용이 불분명해지며, 결과적으로 수익률 계산이 불가능해지고 수익률이 무한대로 보일 수 있다.
Valid Methods for Forming an Equity Curve
Returns: Single Stock
주식은 가격이 음수가 될 수 없기 때문에 일일 수익률을 산출하는 것으로 Equity Curve를 형성할 수 있다.
- 주식 가격 \( S \)를 이용해서 주식의 일일 수익률 시리즈를 만든다.
\[r(t) = \frac{S(t)}{S(t-1)} - 1\] - \( P(t) \)는 전략에따라 해당 일에 보유한 포지션이다. (look-ahead bias주의)
포지션 시리즈 \( P(t) \)는 실수의 time series로 가정한다. 이는 특정 전략에 따라 시간 \( t \)에 보유할 포트폴리오의 단위를 나타낸다.
일부 전략의 경우, \( \{0, 1, -1\} \) 값만을 가지며, 각각 포지션을 보유하지 않음, 100% 롱 포지션, 100% 숏 포지션을 나타낸다. - 전략에서 일일 수익률 \( r_s(t) \)을 계산한다. 이는 pointwise multiplication을 통해 이루어진다
\[ r_s(t) = r(t) P(t), \quad \text{for each t} \] - 일일 수익률 \( r_s(t) \)을 사용하여 포트폴리오의 수익 곡선 \( \mathcal{E}(t) \)을 구성한다
\[ \mathcal{E}(t) = \left( \prod_{\tau=0}^{t} [r_s(\tau) + 1] \right) - 1\]
# 1. Portfolio time series
prices = testing_data['GDX']
# 2. Portfolio's returns
rts = prices.pct_change()
# 3. Suggested positions by some strategy
# I am just making the positions up for ease of demonstration.
position_data = [0]*50 + [1]*50 + [0]*50 + [-1]*50 + [0]*53
positions = pd.Series(data=position_data, index=prices.index)
# 4. Returns from our strategy
rts_strat = rts * positions
# 5. (Normalized) P&L
equity_normalized = (1 + rts_strat).cumprod() - 1
Prep
Forming a Portfolio from a Pair of Stocks
두 주식으로 구성된 portfolio’s value time series 는 다음과 같이 정의된다
\[
\Pi(t) = w_A(t) S_A(t) + w_B(t) S_B(t),
\]
여기서 \(w_A\)와 \(w_B\)는 각각 \(S_A\)와 \(S_B\)에 대해 보유한 단위 수이다. 이는 실수일 수 있으며, 반드시 1로 합계되지 않아도 되고, 양수일 필요도 없다.
Long-Only Portfolio
롱-온리 포트폴리오의 경우, 보유한 단위가 양수라고 가정한다. 일반적으로, 단위가 상수인 경우 이를 [0, 1]의 분할로 설정하여 정규화하는 것이 일반적이며, 이들은 실질적으로 가중치가 된다. 즉,
\[
w_A + w_B = 1; \quad w_A, w_B \geq 0.
\]
Long-Short Portfolio
롱-숏 포트폴리오의 경우, 일반적으로 헤지 비율 \( h(t) \)가 보유한 단위 수에 사용된다.
\[
w_A = 1, \quad w_B(t) = -h(t).
\]
일반적으로 \( h \)는 시간의 함수여야 하지만, 때때로 거래 기간 동안 일정할 수 있다.
따라서 헤지 비율에서 형성된 long-short portfolio’s value time series는 다음과 같다:
\[
\Pi(t) = S_A(t) - h(t) S_B(t).
\]
Returns: Long-Only Portfolios
두 개의 strictly positive value time series(ex 주식, 펀드)에 대해 양수인 \( w_A(t) \)와 \( w_B(t) \) 만큼을 보유한다고 가정해보자. (단일 주식의 방법과 거의 동일하다)
1. 포트폴리오(가격 시리즈 \( \Pi \))를 구성한다
\[
\Pi(t) = w_A(t) S_A(t) + w_B(t) S_B(t)
\]
2. 포트폴리오의 일일 수익률 시리즈를 계산한다.
\[
r(t) = \frac{\Pi(t)}{\Pi(t-1)} - 1
\]
3. 해당 일에 보유한 포지션 \( P(t) \)를 전략에서 가져온다
4. 전략에서 일일 수익률 \( r_s(t) \)을 계산한다. 이는 pointwise multiplication을 통해 이루어진다
\[
r_s(t) = r(t) P(t), \quad \text{for each t}
\]
5. 일일 수익률 \( r_s(t) \)을 사용하여 포트폴리오의 수익 곡선 \( \mathcal{E}(t) \)을 계산한다.
\[
\mathcal{E}(t) = \left( \prod_{\tau=0}^{t} [r_s(\tau) + 1] \right) - 1
\]
# 1. Portfolio time series
# Weights are arbitrarily chosen as a partition of [0, 1] for demonstration
w1 = 0.8 # Positive weight
w2 = 0.2 # Positive weight
portfolio = w1 * testing_data['GDX'] + w2 * testing_data['GLD']
# 2. Portfolio's returns
rts = portfolio.pct_change()
# 3. Suggested positions by some strategy
# I am just making the positions up for ease of demonstration.
position_data = [0]*50 + [1]*50 + [0]*50 + [-1]*50 + [0]*53
positions = pd.Series(data=position_data, index=portfolio.index)
# 4. Returns from our strategy
rts_strat = rts * positions
# 5. (Normalized) P&L
equity_normalized = (1 + rts_strat).cumprod() - 1
Daily Profit and Loss (P&L): Long-Short Portfolios and Others ★
★★
일일 손익(P&L)은 모든 포트폴리오에 대해 적용할 수 있는 가장 일반적인 접근 방식이다. 특히, portfolio’s value time series가 양수, 음수, 또는 0을 기준으로 변동할 수 있는 롱-숏 pair trading portfolio의 경우, 이 방법이 가장 일반화된 올바른 계산방식이다.
1. 포트폴리오(가격 시리즈 \( \Pi \))를 헤지 비율로 구성한다.
\[
\Pi(t) = S_A(t) - h(t) S_B(t)
\]
2. 포트폴리오의 일일 수익(가격 차이, 한 단위에 대한 일일 P&L) 시리즈를 계산한다.
\[
R(t) = \Pi(t) - \Pi(t-1)
\]
3. 전략에서 포지션 \( P(t) \)를 가져온다.
4. 전략에서 일일 수익률 \( R_s(t) \)을 계산합니다. pointwise multiplication을 통해 이루어진다
\[
R_s(t) = R(t) P(t), \quad \text{for each t}
\]
5. 일일 P&L \( R_s(t) \)을 사용하여 포트폴리오의 손익 곡선 \( \mathcal{E}(t) \)을 계산한다.
\[
\mathcal{E}(t) = \sum_{\tau=0}^{t} R_s(\tau)
\]
이 방법은 주어진 전략에 따라 제안된 포지션을 가지고 포트폴리오의 한 단위를 보유할 때의 누적 P&L로 해석할 수 있다. 이 접근법은 포트폴리오의 한 단위를 거래하고 있으므로 직접적으로 수익률이라 볼 수는 없다.
하지만 아래와 같이 특정한 경우에,이 결과를 실제 수익률로 바꿀 수 있긴하다.
가정)
특정 전략에 따라 100,000를 투자할 계획으로, 롱 포지션에 60,000, 숏 포지션에 40,000를 투자한다.
기간 동안 발생한 총 이익(Profit)이 10,000입니다.
- 현실적으로, 다양한 유형의 리스크를 고려하고 투자 전략 하에서 얼마나 많은 단위를 투자할지 결정하기 때문에 위험에 노출된 달러 금액을 수익률을 계산하기 위한 분모로 사용할 수 있다.
순 노출 금액: 롱 포지션 금액 - 숏 포지션 금액 = 60,000 - 40,000 = 20,000
\[
\text{수익률} = \frac{\text{총 이익}}{\text{순 노출 금액}} = \frac{10,000}{20,000} = 0.5 \, (50\%)
\] - ROA
롱 포지션을 자본으로, 숏 포지션을 부채로 간주하여 ROA를 계산한다. 이는 페어 트레이딩 프레임워크에서 흔하지 않지만, 달러 중립 전략에서는 롱 포지션에 투자할 금액을 제한하는 것이 합리적이다.
\[
ROA = \frac{Profit}{Equity + Liability}
\]
롱 포지션을 자본(Equity)으로, 숏 포지션을 부채(Liability)로 간주한다.
롱 포지션: $60,000
숏 포지션: $40,000
\[
ROA = \frac{\text{총 이익}}{\text{자본} + \text{부채}} = \frac{10,000}{60,000 + 40,000} = \frac{10,000}{100,000} = 0.1 \, (10\%)
\]
그러나 이러한 접근법은 사례별로 다르기 때문에, 우리는 1단위에서 제공되는 P&L을 통해 사용자가 선택할 수 있도록 남겨둔다.
h_ols = 1.4 # Hedge ratio suggested by OLS on training data
# 1. Portfolio time series
portfolio = testing_data['GDX'] - h_ols * testing_data['GLD']
# 2. Portfolio's daily P&L for holding 1 unit
pnl = portfolio.diff()
# 3. Suggested positions by some strategy
# I am just making the positions up for ease of demonstration.
position_data = [0]*50 + [1]*50 + [0]*50 + [-1]*50 + [0]*53
positions = pd.Series(data=position_data, index=portfolio.index)
# 4. Daily P&L from our strategy
pnl_strat = pnl * positions
# 5. Equity curve
equity_pnl = (pnl_strat).cumsum()
Returns-ish: Dollar-Neutral
Dollar-Neutral 전략은 distance-based strategy에서 제안된 것처럼, 트레이더는 고평가 주식은 1달러를 숏(Short)하고 저평가 주식은 1달러를 롱(Long)한다.
이 경우 여전히 일일 손익(P&L)을 계산하고 수익 곡선을 형성할 수 있다. 단 이때의 P&L이 1달러 short과 1달러 long 포지션에 대해 계산된다. 이는 수익률과 유사하게 해석될 수 있지만 실제 투자된 원금이 없기 때문에(Dollar-Neutral), 엄밀한 의미의 수익률은 아니다.
# Use the MPI module in the copula approach library for calculating dollar neutral units
CS = csmpi.CopulaStrategyMPI()
# I am just making the positions up for ease of demonstration.
position_data = [0]*50 + [1]*50 + [0]*50 + [-1]*50 + [0]*53
positions = pd.Series(data=position_data, index=portfolio.index)
# The method used below splits 1 dollar into long and short positions equally, hence adjust multiplier = 2
units = CS.positions_to_units_dollar_neutral(prices_df=testing_data, positions=positions, multiplier=2)
# Get the portfolio's daily P&L, which is the number of units held multiplying with prices
portfolio_pnl = units['GDX'] * testing_data['GDX'] + units['GLD'] * testing_data['GLD']
# Cumulatively sum the daily P&L for the equity curve
equity = portfolio_pnl.cumsum()
ROCC and Fully-Invested Return
ROCC는 여러 페어의 수익률을 계산하는 데 있어 보수적인 접근 방식으로 간주된다. 거래에 사용된 페어 수로 각 포트폴리오의 수익을 나눠준다. 몇몇 페어가 거래 기간 동안 포지션을 열지 않았더라도 그러한 단위의 비용은 여전히 기회비용을 고려한 것으로 간주한다.
Fully-Invested Return은 거래 기간 동안 실제로 포지션을 연 페어의 총 수로 최종 이익을 나누어 계산된다. 따라서 이는 자본 배분에 있어 완벽한 유연성을 가정한 것이다.
- ROCC 예시 :
투자된 총 자본: 100,000
페어별 이익 : {페어1 : 2000, 페어2: 1500, 페어3 : 1800, 페어4 : 2200, 페어5: 2500}
\[
ROCC = \frac{\text{총 이익}}{\text{투자된 총 자본}} = \frac{10,000}{100,000} = 0.1 \, (10\%)
\]
따라서, ROCC는 10%. - Fully Invested Return 예시:
투자된 총 자본: 100,000
페어별 이익 : {페어1 : 2000, 페어2: 1500, 페어3 : 1800}
\[
\text{Fully-Invested Return} = \frac{\text{총 이익}}{\text{실제로 포지션을 연 페어의 총 수}} = \frac{5,300}{3} = 1,766.67
\]
따라서, Fully-Invested Return은 페어당 1,766.67입니다.
Common Mistakes
Linearly Combining Returns
포트폴리오 가격을 자체적으로 계산할 때 수익률 \( r(t) \)을 계산하는 일반적인 실수는 다음과 같다:
\[
r(t) = \frac{\Pi(t)}{\Pi(t-1)} - 1
\]
이를 자세히 풀어보면,
\[
r(t) = \frac{w_A(t) S_A(t) + w_B(t) S_B(t)}{w_A(t-1) S_A(t-1) + w_B(t-1) S_B(t-1)} - 1 \neq w_A(t) r_A(t) + w_B(t) r_B(t)
\]
즉, 각 구성 요소의 수익률을 선형적으로 결합하여 포트폴리오의 수익률을 계산할 수 없다는 것을 의미한다. 만약 가중치가 잘 선택된다면, 잘못된 수익 곡선이 올바른 수익 곡선에 근접할 수는 있지만, 이는 완전히 옳은 방법은 아니다.
\( r(t) \)을 계산하는 올바른 방법은 위 방정식의 왼쪽 부분을 사용하는 것이다. 즉, 각 구성 요소의 value series를 사용하여 portfolio의 value series를 형성한 다음 초과 수익을 계산하는 것이다. 이때 가중치는 양수여야 한다.
Using the Long-Only Method on Other Portfolios
롱-온리 포트폴리오를 계산하는 방법은 롱-온리 포트폴리오에만 적용해야 한다. 만약 이 방법을 롱-숏 포트폴리오나 구성 요소의 value time series가 양수로 유지되지 않는 포트폴리오에 사용하면, 제대로 작동하지 않는다
아래는 롱-온리 포트폴리오에 의도된 방법을 사용하여 음수 시간 시리즈에 적용한 잘못된 구현 사례로 우리가 원하는 것과 반대의 결과를 얻게 된다. 포트폴리오의 수익률을 계산할 때 \(\Pi(t)\)가 strict positive가 아니기 때문에 수익률 계산이 유효하지 않게 된다.
https://hudsonthames.org/correct-backtest-methodology-pairs-trading/
Equity Curve Convention — arbitragelab 1.0.0 documentation
Why We Write This For every strategy, in the end, we wish to see its performance on portfolios comprised of real-world data. However, there are very little well-organized resources that are openly and readily available talking about how the performance sho
hudson-and-thames-arbitragelab.readthedocs-hosted.com
'HudsonThames > ArbitrageLab' 카테고리의 다른 글
[ArbLab] 2. OU Process (0) | 2024.07.14 |
---|---|
[ArbLab] 2. OU Process (1) Caveats in Calibrating the OU Process (0) | 2024.07.14 |
[ArbLab] Hedge Ratio (1) | 2024.07.08 |
[ArbLab] 1. Cointegration (5)Multivariate Cointegration Strategy (0) | 2024.07.08 |
[ArbLab] 1. Cointegration (4) Minimum Profit Strategy (0) | 2024.07.04 |