SPY와 IWM 사이의 평균 회귀를 사용하는 내일 거래 전략

저자:선함, 2019-07-01 11:47:08에 작성, 2023-10-26 20:07:32에 업데이트

img

이 문서에서는 일일 거래 전략을 작성할 것입니다. 그것은 고전적인 거래 개념을 사용 하 여, 즉, 동위 회귀 거래 쌍을 사용 하 여. 이 예에서, 우리는 뉴욕 증권 거래소 (NYSE) 에서 거래 하는 두 가지 거래형 오픈 인덱스 펀드 (ETF), SPY 및 IWM을 사용 하 여, 미국 주식 지표인 S&P 500 및 러셀 2000을 대표하려고 시도 합니다.

이 전략은 더 많은 ETF를 만들고 다른 ETF를 공허하게 함으로써 수익률 격차를 생성한다. 다중공격 비율은 통계적 합동 시간 계열을 이용하는 방법과 같은 여러 가지 방법으로 정의될 수 있다. 이 시나리오에서 우리는 회전형 선형 회귀를 통해 SPY와 IWM 사이의 헤드리지 비율을 계산할 것이다. 이것은 우리가 SPY와 IWM 사이에 수익률 격차를 생성하도록 허용할 것이며, 이는 z-score로 표준화된다. z-score가 어느 한 임계치를 초과하면 거래 신호가 생성되며, 왜냐하면 우리는 이 수익률 격차가 평균으로 회복될 것이라고 믿기 때문이다.

이 전략의 기본 원리는 SPY와 IWM가 거의 동일한 시장 상황을, 즉 미국 대기업과 소규모 기업들의 주식 가격 성능을 나타낸다는 것이다. 가정하자면, 가격에 대한 평형이 회귀 이론을 받아들인다면, 그것은 항상 회귀할 때이다. 왜냐하면 사건의 은 매우 짧은 시간에 각각 S&P500와 Russell 2000에 영향을 미칠 수 있기 때문에, 그러나 그들 사이의 이윤 격차는 항상 정상적인 평균으로 돌아갑니다. 그리고 두 가지의 장기적인 가격 순서는 항상 통합됩니다.

전략

이 전략은 다음과 같은 단계로 실행됩니다.

데이터 - 2007년 4월부터 2014년 2월까지 각각 SPY와 IWM의 1분 k선 그래프를 얻었다.

처리 - 데이터를 올바르게 정렬하고 서로가 빠진 k줄을 삭제합니다.

차이점 - 두 ETF 사이의 헤딩 비율은 회전형 선형 회귀 계산을 통해 계산된다. 이 회귀 창을 사용하는 β 회귀 계수가 정의되며, 이 회귀 창은 1개의 k 라인을 앞으로 이동시키고 회귀 계수를 재 계산한다. 따라서, 헤딩 비율 βi, big root K 라인은 bi-1-k에서 bi-1로 경사점을 계산하여 k 라인을 회귀하는 데 사용됩니다.

Z-Score - 표준 이윤의 값은 일반적인 방법으로 계산됩니다. 이것은 이윤의 평균값을 (사본) 빼고 이윤의 표준 이윤을 (사본) 빼는 것을 의미합니다. 그렇게 하는 이유는 Z-Score가 차원없는 양이기 때문에 절정값 매개 변수를 더 이해하기 쉽기 때문입니다. 나는 얼마나 미묘할 수 있는지 보여주기 위해 계산에 선향편 편차를 의도적으로 도입했습니다. 한번 보세요!

트레이딩 - 부정적 z-스코어 값이 예정된 (또는 후 최적화) 임계치보다 낮아지면 더 많은 신호가 생성되고, 임계치 신호는 반대로 생성됩니다. z-스코어의 절대 값이 추가 임계치 아래로 떨어지면 평형 신호가 생성됩니다. 이 전략에 대해 나는 (미미 무작위로) ?? = 2를 오픈 임계치로, 그리고 ?? = 1을 평형 임계치로 선택했습니다.

아마도 정책을 깊이 이해하는 가장 좋은 방법은 실제로 구현하는 것입니다. 다음 섹션에서는 이 평행값 회귀 전략을 구현하는 완전한 파이썬 코드가 (단독 파일) 자세히 설명됩니다. 더 나은 이해를 돕기 위해 자세한 코드 설명서를 추가했습니다.

파이썬 구현

모든 파이썬/판다스 튜토리얼과 마찬가지로, 이 튜토리얼에서 설명한 파이썬 환경에 따라 설정되어야 한다. 설정이 완료되면 첫 번째 작업은 필요한 파이썬 라이브러리를 수입하는 것이다. 이것은 matplotlib과 판다스를 사용하는 데 필수적이다.

제가 사용하는 특정 라이브러리 버전은 다음과 같습니다.

파이썬 - 2.7.3 수 - 1.8.0 판다 - 0.12.0 matplotlib - 1.1.0

이 자료를 가져오면서 계속해 봅시다.

# mr_spy_iwm.py

import matplotlib.pyplot as plt
import numpy as np
import os, os.path
import pandas as pd

다음 함수 create_pairs_dataframe는 두 개의 기호를 포함하는 내장 k줄의 CSV 파일을 가져옵니다. 우리 예제에서는 SPY와 IWM이 될 것입니다. 그러면 개별 데이터 쌍을 생성합니다. 이 데이터 쌍은 두 원본 파일의 인덱스를 사용할 것입니다. 놓친 트랜잭션과 오류로 인해 시간이 다를 수 있습니다. 이것은 pandas와 같은 데이터 분석기를 사용하는 주요 장점 중 하나입니다. 우리는 매우 효율적으로 샘플 코드를 처리합니다.

# mr_spy_iwm.py
def create_pairs_dataframe(datadir, symbols):
    """Creates a pandas DataFrame containing the closing price
    of a pair of symbols based on CSV files containing a datetime
    stamp and OHLCV data."""
 
    # Open the individual CSV files and read into pandas DataFrames
    print "Importing CSV data..."
    sym1 = pd.io.parsers.read_csv(os.path.join(datadir, '%s.csv' % symbols[0]),
                                  header=0, index_col=0, 
                                  names=['datetime','open','high','low','close','volume','na'])
    sym2 = pd.io.parsers.read_csv(os.path.join(datadir, '%s.csv' % symbols[1]),
                                  header=0, index_col=0, 
                                  names=['datetime','open','high','low','close','volume','na'])
 
    # Create a pandas DataFrame with the close prices of each symbol
    # correctly aligned and dropping missing entries
    print "Constructing dual matrix for %s and %s..." % symbols    
    pairs = pd.DataFrame(index=sym1.index)
    pairs['%s_close' % symbols[0].lower()] = sym1['close']
    pairs['%s_close' % symbols[1].lower()] = sym2['close']
    pairs = pairs.dropna()
    return pairs

다음 단계는 SPY와 IWM 사이에 선형 회귀를 수행하는 것입니다. 이 시나리오에서 IWM는 예측자 (x), SPY는 응답자 (y) 입니다. 100개의 k줄의 기본 회귀 창을 설정했습니다. 위와 같이, 이것은 정책의 매개 변수입니다. 정책을 견고하게 간주하기 위해, 우리는 이상적으로 회귀 보고서가 회귀기간에 함수 상태 ( function status) 또는 다른 성능 메트릭의 성능을 보이는 것을보고 싶습니다. 따라서, 코드의 후기에 우리는 변화 범위 내의 회귀기를 통해 민감성 분석을 수행합니다.

SPY-IWM의 선형 회귀 모형에서 롤러 베타 계수를 계산한 후, 데이터프레임 쌍에 추가하고 빈줄을 제거한다. 이것은 첫 번째 K 라인 그룹을 구성하고, 이는 회귀 길이의 절단 측정에 해당한다. 그리고 우리는 SPY의 단위와 IWM의 -βi 단위인 두 개의 ETF의 이윤차를 만듭니다. 분명히, 이것은 현실적인 일이 아닙니다. 왜냐하면 우리는 IWM의 적은 양을 사용하고 있기 때문에 실제 구현에서는 불가능합니다.

마지막으로, 우리는 이윤차이의 z-스코어를 만들고, 이윤차의 평균값을 빼고 표준화 된 이윤차의 표준차를 통해 계산한다. 여기서 상당히 섬세한 선구적인 미래 지각차가 존재한다는 점에 유의해야 한다. 나는 의도적으로 그것을 코드 안에 남겨두었기 때문에 연구에서 그러한 실수를 하는 것이 얼마나 쉬운지를 강조하고 싶다. 이윤차의 전체 시간 계열의 평균값과 표준차를 계산한다. 만약 이것이 진정한 역사적 정확성을 반영하기 위한 것이라면, 이 정보는 얻을 수 없을 것이다. 왜냐하면 그것은 암시적으로 미래 정보를 이용하기 때문이다. 따라서 우리는 롤러 평균값과 devst를 사용하여 z-스코어를 계산해야 한다.

# mr_spy_iwm.py
 
def calculate_spread_zscore(pairs, symbols, lookback=100):
    """Creates a hedge ratio between the two symbols by calculating
    a rolling linear regression with a defined lookback period. This
    is then used to create a z-score of the 'spread' between the two
    symbols based on a linear combination of the two."""
     
    # Use the pandas Ordinary Least Squares method to fit a rolling
    # linear regression between the two closing price time series
    print "Fitting the rolling Linear Regression..."
    model = pd.ols(y=pairs['%s_close' % symbols[0].lower()], 
                   x=pairs['%s_close' % symbols[1].lower()],
                   window=lookback)
 
    # Construct the hedge ratio and eliminate the first 
    # lookback-length empty/NaN period
    pairs['hedge_ratio'] = model.beta['x']
    pairs = pairs.dropna()
 
    # Create the spread and then a z-score of the spread
    print "Creating the spread/zscore columns..."
    pairs['spread'] = pairs['spy_close'] - pairs['hedge_ratio']*pairs['iwm_close']
    pairs['zscore'] = (pairs['spread'] - np.mean(pairs['spread']))/np.std(pairs['spread'])
    return pairs

create_long_short_market_signals에서 거래 신호를 생성한다. 이들은 z-score의 값이 제약값을 초과하는 것으로 계산된다. z-score의 절대값이 다른 (작은 규모의) 제약값보다 작거나 같을 때 평형 신호가 주어진다.

이러한 상황을 실현하기 위해, k줄마다 한仓库 또는 한仓库의 거래 전략을 설정하는 것이 필요했다. long_market과 short_market은 다중 및 빈장 포지션을 추적하는 데 사용되는 두 가지 정의 변수이다. 불행히도, 이더리얼 방식으로 프로그래밍하는 것이 벡터화 방식에 비해 더 간단하기 때문에 계산이 느렸다. 1분짜리 k줄 그래프는 CSV 파일에 약 700,000개의 데이터 포인트를 필요로 하지만, 내 오래된 데스크톱에서 계산하는 것은 여전히 비교적 빠르다!

pandas DataFrame를 반복하기 위해서는 (이것이 의심할 여지없이 흔하지 않은 작업) iterrows 방법을 사용해야 하며, 이는 반복 생성자를 제공합니다:

# mr_spy_iwm.py
 
def create_long_short_market_signals(pairs, symbols, 
                                     z_entry_threshold=2.0, 
                                     z_exit_threshold=1.0):
    """Create the entry/exit signals based on the exceeding of 
    z_enter_threshold for entering a position and falling below
    z_exit_threshold for exiting a position."""
 
    # Calculate when to be long, short and when to exit
    pairs['longs'] = (pairs['zscore'] <= -z_entry_threshold)*1.0
    pairs['shorts'] = (pairs['zscore'] >= z_entry_threshold)*1.0
    pairs['exits'] = (np.abs(pairs['zscore']) <= z_exit_threshold)*1.0
 
    # These signals are needed because we need to propagate a
    # position forward, i.e. we need to stay long if the zscore
    # threshold is less than z_entry_threshold by still greater
    # than z_exit_threshold, and vice versa for shorts.
    pairs['long_market'] = 0.0
    pairs['short_market'] = 0.0
 
    # These variables track whether to be long or short while
    # iterating through the bars
    long_market = 0
    short_market = 0
 
    # Calculates when to actually be "in" the market, i.e. to have a
    # long or short position, as well as when not to be.
    # Since this is using iterrows to loop over a dataframe, it will
    # be significantly less efficient than a vectorised operation,
    # i.e. slow!
    print "Calculating when to be in the market (long and short)..."
    for i, b in enumerate(pairs.iterrows()):
        # Calculate longs
        if b[1]['longs'] == 1.0:
            long_market = 1            
        # Calculate shorts
        if b[1]['shorts'] == 1.0:
            short_market = 1
        # Calculate exists
        if b[1]['exits'] == 1.0:
            long_market = 0
            short_market = 0
        # This directly assigns a 1 or 0 to the long_market/short_market
        # columns, such that the strategy knows when to actually stay in!
        pairs.ix[i]['long_market'] = long_market
        pairs.ix[i]['short_market'] = short_market
    return pairs

이 단계에서 우리는 실제로 많은, 빈 신호를 포함하도록 쌍을 업데이트하여 우리가 입장이 필요한지 여부를 결정할 수 있습니다. 이제 우리는 포트폴리오를 만들어 포지션의 시장 가치를 추적해야 합니다. 첫 번째 작업은 복수 신호와 빈 신호를 결합한 위치 열을 만드는 것입니다. 이것은 "1", "0,-1"에서 1이 복수 포지션을 나타내는 1에서 1이 복수 포지션을 나타내는 0은 아무 포지션을 의미하며, -1은 빈 포지션을 나타냅니다. sym1과 sym2는 각 k 라인의 끝에서 SPY와 IWM 위치의 시장 값을 나타냅니다.

일단 ETF의 시장가치가 만들어지면, 우리는 그것들을 서로 쪼개서 각 k줄의 끝에서 총 시장가치를 생성한다. 그리고 그 객체의 pct_change 방법을 통해 반환값으로 변환한다. 다음 코드 라인은 잘못된 항목 (NaN와 inf 요소) 을 제거하고, 마지막으로 완전한 이해곡선을 계산한다.

# mr_spy_iwm.py
 
def create_portfolio_returns(pairs, symbols):
    """Creates a portfolio pandas DataFrame which keeps track of
    the account equity and ultimately generates an equity curve.
    This can be used to generate drawdown and risk/reward ratios."""
     
    # Convenience variables for symbols
    sym1 = symbols[0].lower()
    sym2 = symbols[1].lower()
 
    # Construct the portfolio object with positions information
    # Note that minuses to keep track of shorts!
    print "Constructing a portfolio..."
    portfolio = pd.DataFrame(index=pairs.index)
    portfolio['positions'] = pairs['long_market'] - pairs['short_market']
    portfolio[sym1] = -1.0 * pairs['%s_close' % sym1] * portfolio['positions']
    portfolio[sym2] = pairs['%s_close' % sym2] * portfolio['positions']
    portfolio['total'] = portfolio[sym1] + portfolio[sym2]
 
    # Construct a percentage returns stream and eliminate all 
    # of the NaN and -inf/+inf cells
    print "Constructing the equity curve..."
    portfolio['returns'] = portfolio['total'].pct_change()
    portfolio['returns'].fillna(0.0, inplace=True)
    portfolio['returns'].replace([np.inf, -np.inf], 0.0, inplace=True)
    portfolio['returns'].replace(-1.0, 0.0, inplace=True)
 
    # Calculate the full equity curve
    portfolio['returns'] = (portfolio['returns'] + 1.0).cumprod()
    return portfolio

메인 함수는 그것들을 결합합니다.. CSV 파일이 datadir 경로에 있습니다..

룩백 주기에 대한 전략의 민감도를 결정하기 위해 룩백의 성능 지표를 계산하는 것이 필요합니다. 저는 포트폴리오의 최종 총 수익률 비율을 성능 지표로 선택하고 룩백 범위[50,200]에서 인프라를 10으로 합니다. 아래 코드에서 볼 수 있듯이 이전 함수는 이 범위 내의 for 루프에 포함되며 다른 문턱은 변하지 않습니다. 마지막 작업은 matplotlib을 사용하여 룩백 대 룩백의 리턴의 도형 그래프를 만드는 것입니다:

# mr_spy_iwm.py
 
if __name__ == "__main__":
    datadir = '/your/path/to/data/'  # Change this to reflect your data path!
    symbols = ('SPY', 'IWM')
 
    lookbacks = range(50, 210, 10)
    returns = []
 
    # Adjust lookback period from 50 to 200 in increments
    # of 10 in order to produce sensitivities
    for lb in lookbacks: 
        print "Calculating lookback=%s..." % lb
        pairs = create_pairs_dataframe(datadir, symbols)
        pairs = calculate_spread_zscore(pairs, symbols, lookback=lb)
        pairs = create_long_short_market_signals(pairs, symbols, 
                                                z_entry_threshold=2.0, 
                                                z_exit_threshold=1.0)
 
        portfolio = create_portfolio_returns(pairs, symbols)
        returns.append(portfolio.ix[-1]['returns'])
 
    print "Plot the lookback-performance scatterchart..."
    plt.plot(lookbacks, returns, '-o')
    plt.show()

img

이제 lookbacks와 returns의 그래프를 볼 수 있습니다. 참고로, lookback는 전체 의 최대값을 가지고 있으며, 110개의 k줄과 같습니다.

SPY-IWM 선형 회귀 헤딩 대 룩백 기간 민감성 분석

위쪽으로 기울인 이윤 곡선이 없다면, 어떤 리모델링 기사는 불완전하다! 따라서, 만약 당신이 누적 이윤 수익률과 시간의 곡선을 그리기를 원한다면, 다음 코드를 사용할 수 있다. 그것은 리모델링 파라미터 연구로부터 생성된 최종 투자 포트폴리오를 그리게 된다. 따라서, 당신이 시각화하고자 하는 그래프에 따라 리모델링을 선택해야 한다. 이 그래프는 또한 비교를 돕기 위해 같은 기간 SPY의 수익률을 그리기도 한다:

# mr_spy_iwm.py
 
    # This is still within the main function
    print "Plotting the performance charts..."
    fig = plt.figure()
    fig.patch.set_facecolor('white')
 
    ax1 = fig.add_subplot(211,  ylabel='%s growth (%%)' % symbols[0])
    (pairs['%s_close' % symbols[0].lower()].pct_change()+1.0).cumprod().plot(ax=ax1, color='r', lw=2.)
 
    ax2 = fig.add_subplot(212, ylabel='Portfolio value growth (%%)')
    portfolio['returns'].plot(ax=ax2, lw=2.)
 
    fig.show()

다음 권익곡선 그래프의 룩백 기간은 100일입니다.

img

SPY-IWM 선형 회귀 헤딩 대 룩백 기간 민감성 분석

참고로 2009년 SPY는 금융위기 기간 동안 크게 축소되었다. 이 전략은 이 시기에도 불안한 시기에 있었다. 또한 SPY가 이 기간 동안 강한 경향성을 보였으며 스탠다드 500 지수를 반영했기 때문에 작년 실적이 악화되었다.

참고로 z-score의 이자율을 계산할 때, 우리는 여전히 전향편차를 고려해야 한다. 또한, 이 모든 계산은 거래 비용이 없는 상태에서 이루어졌다. 이러한 요소들을 고려하면, 이 전략은 확실히 좋지 않을 것이다. 절차 수수료와 슬라이드 포인트는 현재 확정되지 않았다. 또한, 이 전략은 ETF의 소수 단위로 거래하는 것이 매우 비현실적이었다.

후속 기사에서 우리는 더 복잡한 이벤트 드라이브 백테스터를 만들 것이며, 이러한 요소들을 고려하여 자본 곡선과 성능 지표에 대한 더 많은 신뢰를 보여줄 것입니다.


관련

더 많은