판다와 함께 파이썬에서 S&P500에 대한 예측 전략을 백테스트합니다.

저자:선함, 창작: 2019-03-29 14:24:50, 업데이트:

matplotlib, pandas, scikit-learn 같은 성숙한 파이썬 라이브러리들은 또한 보일러플라트 코드를 작성하거나 잘 알려진 알고리즘의 자체 구현을 할 필요성을 줄여줍니다.

예측 전략

예측 전략 자체는 쿼드라틱 디스크리맨트 분석기 (quadratic discriminant analyzer) 로 알려진 기계 학습 기술을 기반으로 하며, 이는 선형 디스크리맨트 분석기와 밀접한 관련이 있다. 이 두 모델은 금융 시간 시리즈 예측에 관한 기사에서 상세히 설명되어 있다.

예측자는 이전 두 일일 수익을 요인 집합으로 사용하여 주식 시장의 오늘날의 방향을 예측합니다. 하루가 상승할 확률이 50%를 초과하면 전략은 SPY ETF의 500주식을 구입하고 하루 말에 판매합니다. 낮의 확률이 50%를 초과하면 전략은 SPY ETF의 500주식을 판매하고 닫을 때 다시 구매합니다. 따라서 이것은 내일 거래 전략의 첫 번째 예입니다.

참고로 이것은 특히 현실적인 거래 전략이 아닙니다! 우리는 과도한 오픈 변동성, 중개업에 의한 오더 라우팅 및 오픈/클로즈 주변의 잠재적 유동성 문제와 같은 많은 요소로 인해 개막 또는 폐쇄 가격을 달성하지 못할 것입니다. 또한 우리는 거래 비용을 포함하지 않았습니다. 이것은 매일 진행되는 왕복 거래가 있기 때문에 수익의 상당한 비율이 될 것입니다. 따라서 우리의 예측자는 매일 수익을 예측하는 데 상대적으로 정확해야합니다. 그렇지 않으면 거래 비용은 우리의 모든 거래 수익을 먹게됩니다.

시행

다른 파이썬/판다 관련 튜토리얼과 마찬가지로 다음 라이브러리를 사용했습니다:

  • 파이썬 - 2.7.3
  • 수 - 1.8.0
  • 판다 - 0.12.0
  • matplotlib - 1.1.0
  • 스키티-러닝 - 0.14.1

아래의 snp_forecast.py를 구현하려면backtest.py이 이전 튜토리얼에서forecast.py(주로 함수 create_lagged_series를 포함) 이 이전 튜토리얼에서 만들어집니다. 첫 번째 단계는 필요한 모듈과 객체를 수입하는 것입니다:

# snp_forecast.py

import datetime
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import sklearn

from pandas.io.data import DataReader
from sklearn.qda import QDA

from backtest import Strategy, Portfolio
from forecast import create_lagged_series

모든 관련 라이브러리 및 모듈이 포함되면 이전 튜토리얼에서 수행한 것처럼 전략 추상 기본 클래스를 하위 클래스로 분류 할 때입니다. SNPForecastingStrategy는 S&P500 주식 지수를 예측하는 수단으로 사각형 차별 분석기를 S&P500 주식 지수에 맞게 설계되었습니다. 모델의 적합성은 아래의 fit_model 방법으로 수행되며 실제 신호는 generate_signals 방법에서 생성됩니다. 이것은 전략 클래스의 인터페이스와 일치합니다.

제곱분리분리 분석기가 작동하는 방법의 세부 사항과 아래의 파이썬 구현은 금융 시간 계열 예측에 관한 이전 기사에서 자세히 설명되어 있습니다. 아래 소스 코드의 댓글은 프로그램이하는 일을 광범위하게 논의합니다.

# snp_forecast.py

class SNPForecastingStrategy(Strategy):
    """    
    Requires:
    symbol - A stock symbol on which to form a strategy on.
    bars - A DataFrame of bars for the above symbol."""

    def __init__(self, symbol, bars):
        self.symbol = symbol
        self.bars = bars
        self.create_periods()
        self.fit_model()

    def create_periods(self):
        """Create training/test periods."""
        self.start_train = datetime.datetime(2001,1,10)
        self.start_test = datetime.datetime(2005,1,1)
        self.end_period = datetime.datetime(2005,12,31)

    def fit_model(self):
        """Fits a Quadratic Discriminant Analyser to the
        US stock market index (^GPSC in Yahoo)."""
        # Create a lagged series of the S&P500 US stock market index
        snpret = create_lagged_series(self.symbol, self.start_train, 
                                      self.end_period, lags=5) 

        # Use the prior two days of returns as 
        # predictor values, with direction as the response
        X = snpret[["Lag1","Lag2"]]
        y = snpret["Direction"]

        # Create training and test sets
        X_train = X[X.index < self.start_test]
        y_train = y[y.index < self.start_test]

        # Create the predicting factors for use 
        # in direction forecasting
        self.predictors = X[X.index >= self.start_test]

        # Create the Quadratic Discriminant Analysis model
        # and the forecasting strategy
        self.model = QDA()
        self.model.fit(X_train, y_train)

    def generate_signals(self):
        """Returns the DataFrame of symbols containing the signals
        to go long, short or hold (1, -1 or 0)."""
        signals = pd.DataFrame(index=self.bars.index)
        signals['signal'] = 0.0       

        # Predict the subsequent period with the QDA model
        signals['signal'] = self.model.predict(self.predictors)

        # Remove the first five signal entries to eliminate
        # NaN issues with the signals DataFrame
        signals['signal'][0:5] = 0.0
        signals['positions'] = signals['signal'].diff() 

        return signals

이제 예측 엔진이 신호를 생성 한 후, 우리는 MarketIntradayPortfolio를 만들 수 있습니다. 이 포트폴리오 객체는 하루 내 거래를 수행하기 때문에 이동 평균 크로스오버 백테스트 기사에서 제공 된 예와 다릅니다.

포트폴리오는 시그널이 상승세를 나타낼 것이라고 표시하면 개시 가격에 SPY 500주식을 (구입) 하다가 폐쇄 가격에 매각하도록 설계되어 있습니다. 반대로 포트폴리오는 시그널이 하락세를 나타낼 것이라고 표시되면 SPY 500주식을 (구매) 하다가 종료 가격에 매각하도록 설계되어 있습니다.

이를 달성하기 위해 시장 개방 및 시장 폐쇄 가격 사이의 가격 차이는 매일 결정되며, 500 개의 주식 구매 또는 판매에 대한 일일 이윤을 계산합니다. 이것은 각 날의 이익 / 손실을 누적 요약함으로써 자연스럽게 주식 곡선으로 이어집니다. 또한 매일의 이익 / 손실 통계를 계산 할 수 있다는 장점이 있습니다.

다음은 MarketIntraday 포트폴리오의 목록입니다:

# snp_forecast.py

class MarketIntradayPortfolio(Portfolio):
    """Buys or sells 500 shares of an asset at the opening price of
    every bar, depending upon the direction of the forecast, closing 
    out the trade at the close of the bar.

    Requires:
    symbol - A stock symbol which forms the basis of the portfolio.
    bars - A DataFrame of bars for a symbol set.
    signals - A pandas DataFrame of signals (1, 0, -1) for each symbol.
    initial_capital - The amount in cash at the start of the portfolio."""

    def __init__(self, symbol, bars, signals, initial_capital=100000.0):
        self.symbol = symbol        
        self.bars = bars
        self.signals = signals
        self.initial_capital = float(initial_capital)
        self.positions = self.generate_positions()
        
    def generate_positions(self):
        """Generate the positions DataFrame, based on the signals
        provided by the 'signals' DataFrame."""
        positions = pd.DataFrame(index=self.signals.index).fillna(0.0)

        # Long or short 500 shares of SPY based on 
        # directional signal every day
        positions[self.symbol] = 500*self.signals['signal']
        return positions
                    
    def backtest_portfolio(self):
        """Backtest the portfolio and return a DataFrame containing
        the equity curve and the percentage returns."""

        # Set the portfolio object to have the same time period
        # as the positions DataFrame
        portfolio = pd.DataFrame(index=self.positions.index)
        pos_diff = self.positions.diff()

        # Work out the intraday profit of the difference
        # in open and closing prices and then determine
        # the daily profit by longing if an up day is predicted
        # and shorting if a down day is predicted        
        portfolio['price_diff'] = self.bars['Close']-self.bars['Open']
        portfolio['price_diff'][0:5] = 0.0
        portfolio['profit'] = self.positions[self.symbol] * portfolio['price_diff']

        # Generate the equity curve and percentage returns
        portfolio['total'] = self.initial_capital + portfolio['profit'].cumsum()
        portfolio['returns'] = portfolio['total'].pct_change()
        return portfolio

마지막 단계는 전략과 포트폴리오의주요이 함수는 SPY 인스트루먼트에 대한 데이터를 얻고 S&P500 인덱스 자체에 대한 신호 생성 전략을 만듭니다. 이것은 ^GSPC 틱러에 의해 제공됩니다. 그 다음 초기 자본 100,000 USD (전과 같이) 로 마켓 인트라데이 포트폴리오가 생성됩니다. 마지막으로 수익을 계산하고 주식 곡선이 그려집니다.

이 단계에 얼마나 적은 코드가 필요한지 주목하십시오. 왜냐하면 모든 무거운 계산은 전략 및 포트폴리오 하위 클래스에서 수행되기 때문입니다. 이것은 새로운 거래 전략을 만들고 전략 파이프라인에서 사용하기 위해 신속하게 테스트하는 것을 매우 간단하게 만듭니다.

if __name__ == "__main__":
    start_test = datetime.datetime(2005,1,1)
    end_period = datetime.datetime(2005,12,31)

    # Obtain the bars for SPY ETF which tracks the S&P500 index    
    bars = DataReader("SPY", "yahoo", start_test, end_period)
    
    # Create the S&P500 forecasting strategy
    snpf = SNPForecastingStrategy("^GSPC", bars)
    signals = snpf.generate_signals()

    # Create the portfolio based on the forecaster
    portfolio = MarketIntradayPortfolio("SPY", bars, signals,              
                                        initial_capital=100000.0)
    returns = portfolio.backtest_portfolio()

    # Plot results
    fig = plt.figure()
    fig.patch.set_facecolor('white')

    # Plot the price of the SPY ETF
    ax1 = fig.add_subplot(211,  ylabel='SPY ETF price in $')
    bars['Close'].plot(ax=ax1, color='r', lw=2.)

    # Plot the equity curve
    ax2 = fig.add_subplot(212, ylabel='Portfolio value in $')
    returns['total'].plot(ax=ax2, lw=2.)

    fig.show()

이 프로그램의 출력은 아래와 같습니다. 이 기간 동안 주식 시장은 4%를 반환했습니다. 알고리즘 자체는 또한 4%를 반환했습니다. 이 백테스팅 시스템에 거래 비용 (위원회 수수료와 같은 것) 이 추가되지 않았다는 점에 유의하십시오. 전략은 하루에 한 번 왕복 거래를 수행하기 때문에 이러한 수수료는 수익을 크게 줄일 가능성이 있습니다.

2005-01-01부터 2006-12-31까지 S&P500 예측 전략 성과

다음 기사에서는 현실적인 거래 비용을 추가하고, 추가 예측 엔진을 활용하고, 성과 측정표를 결정하고 포트폴리오 최적화 도구를 제공합니다.


더 많은