パンダとPythonでS&P500の予測戦略をバックテスト

作者: リン・ハーン優しさ, 作成日:2019-03-29 14:24:50, 更新日:

熟成した Python ライブラリである matplotlib, pandas, scikit-learn も,ボイラープレートコードを書く必要性を軽減し,よく知られたアルゴリズムの独自の実装を提案します.

予測 戦略

予測戦略そのものは,二次分別解析器として知られる機械学習技術に基づいており,線形分別解析器と密接に関連している.この2つのモデルは,財務時間系列の予測に関する記事で詳細に説明されている.

予報者は,前回の2日間のリターンを要因の集合として利用し,今日の株式市場の方向性を予測する.当日のアップの確率が50%を超えると,戦略はSPY ETFの500株を購入し,日の終わりに売却する.ダウン日の確率が50%を超えると,戦略はSPY ETFの500株を売却し,閉店時に買い戻します.したがって,これは私たちの最初のイントラデイ取引戦略の例です.

これは特に現実的な取引戦略ではないことに注意してください! 過剰なオープニング波動性,ブローカージによるオーダールーティング,オープン/クローズの周りの潜在的な流動性問題などの多くの要因により,開店または閉店価格を達成することは不可能です. さらに,取引コストは含まれていません. これらは毎日往復取引が行われるため,収益のかなりの割合を占める可能性があります. したがって,私たちの予報者は,日々の収益を予測するのに比較的正確でなければなりません.そうでなければ,取引コストは,私たちの取引収益をすべて食べます.

実施

Python/pandaに関する他のチュートリアルと同様に,私は以下のライブラリを使用しました:

  • パイソン - 2.7.3
  • NumPy - 1.8.0
  • パンダ 0.12.0
  • マットプロットリブ - 1.1.0
  • スキット・ラーニング - 0.14.1

下の snp_forecast.py の実装にはbacktest.pyこのトチュリアルからforecast.pyこの先のチュートリアルから作成されます. 最初のステップは必要なモジュールとオブジェクトをインポートすることです:

# 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株式指数への四角差分分析を,将来の価値を予測するための手段として適合するように設計されています.モデルのフィットメントは,下記のfit_model方法で行われ,実際のシグナルは,genere_signals方法から生成されます.これは,戦略クラスのインターフェースと一致します.

平方差分解析器の動作の詳細,および Python の実装については,財務時間列の予測に関する前の記事で詳細に説明されています. 下のソースコードのコメントでは,プログラムが何をしているのかを詳しく説明しています:

# 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ティッカーによって提供されます.その後,初期資本10万米ドル (以前のチュートリアルと同様に) でMarketIntradayPortfolioが生成されます.最後に,リターンが計算され,株式曲線がプロットされます.

この段階では,すべての重い計算が戦略およびポートフォリオサブクラスで行われ,新しい取引戦略を作成し,戦略パイプラインで使用するために迅速にテストすることを非常に簡単にするため,コードがどれほど少ないか注意してください.

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%を返した.このバックテストシステムには取引費用 (佣金などの手数料) が加算されていないことに注意する.戦略は1日1回往復取引を行うため,これらの手数料は収益を大幅に削減する可能性が高い.

2005年1月1日から2006年12月31日まで S&P500 予測戦略の業績

次の記事では,現実的な取引コストを追加し,追加の予測エンジンを利用し,パフォーマンス指標を決定し,ポートフォリオ最適化ツールを提供します.


もっと