[Python] ロジスティック回帰での株価予測

ロジスティック回帰で、翌日の株価の上昇/下降を予測します。

ロジスティック回帰

ロジスティック回帰(ロジスティックかいき、: Logistic regression)は、ベルヌーイ分布に従う変数の統計的回帰モデルの一種である。

出典: フリー百科事典『ウィキペディア(Wikipedia)』

2つに分類する問題を解くときに使う回帰の手法で、確率が出力されます。

どういったものかは、以下が分かりやすいです。

Lecture 19 – Logistic Regression and Classification

Python では、sklearn を使うことで簡単にロジスティック回帰を行うことができます。

sklearn.linear_model.LogisticRegression

GEを対象に、過去40日間の前日比の株価と取引量の変化率を使い、当日の株価が上がるか下がるかを予測してみます。

2000年から2019年を学習データ、2019年から1年間をテストデータとして使います。

import numpy as np
import pandas as pd
import datetime

import numpy as np
import pandas as pd
import sklearn
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix

from pandas_datareader import data as pdr
import yfinance as yf
import requests_cache
# override pandas_datareader
yf.pdr_override()
# casche for downloaded data
expire_after = datetime.timedelta(days=3)
session = requests_cache.CachedSession(cache_name='cache', backend='sqlite', expire_after=expire_after)


def calc_daily_pct_change(data):
    pct_change = data.pct_change()
    return pct_change

def create_dataset(stock, start, ende, lags=40):
    df =  pdr.get_data_yahoo(stock, start, end)

    df_lag = pd.DataFrame(index=df.index)
    df_lag['today'] = df['Adj Close']
    df_lag['Volume'] = df['Volume']

    for i in range(1, lags+1):
        df_lag[f'lag_close_{i}'] = df['Adj Close'].shift(i)
        df_lag[f'lag_vol_{i}'] = df['Volume'].shift(i)

    df_ret = pd.DataFrame(index=df_lag.index)
    df_ret['today'] = df_lag['today'].pct_change()

    for i in range(1, lags+1):
        df_ret[f'lag_close_{i}'] = df_lag[f'lag_close_{i}'].pct_change()
        df_ret[f'lag_vol_{i}'] = df_lag[f'lag_vol_{i}'].pct_change()

    df_ret.dropna(inplace=True)
    df_ret['up_or_down'] = np.sign(df_ret['today'])
    # drop rows where col 'up_or_down'==0 to redress the balance
    df_ret = df_ret[df_ret['up_or_down'] != 0]

    return df_ret

if __name__ == '__main__':
    start = datetime.datetime(2000, 1, 1)
    end = datetime.datetime(2020, 1, 1)

    data = create_dataset('GE', start, end)
    # print(data.describe())
    # print(data['up_or_down'].value_counts())

    X = data.drop(['today', 'up_or_down'], axis=1)
    y = data['up_or_down']

    start_test = datetime.datetime(2019, 1, 1)

    X_train = X[X.index < start_test]
    X_test = X[X.index >= start_test]
    y_train = y[y.index < start_test]
    y_test = y[y.index >= start_test]

    # standardize    
    scaler = StandardScaler()
    X_train_scaled = scaler.fit_transform(X_train)
    X_test_scaled = scaler.transform(X_test)

    model = LogisticRegression(solver='lbfgs', multi_class='auto')
    model.fit(X_train_scaled, y_train)
    pred = model.predict(X_test_scaled)
    pred_prob = model.predict_proba(X_test_scaled)
    # [-1.  1.]
    # print(model.classes_)
    
    # Accuracy 0.560 
    print(f'Accuracy {model.score(X_test_scaled, y_test):.3f}')
    # [[80 66]
    #  [43 59]]
    print(confusion_matrix(pred, y_test))
    # 上手くいっていない


    # 確率が0.6より大きい場合のみに限定して混同行列を見てみる。
    df_test = pd.DataFrame(y_test)
    df_test['prob_down'] = pred_prob.transpose()[0]
    bound = 0.60
    test_over_bound = df_test[(df_test['prob_down'] > bound) | (df_test['prob_down'] < (1-bound))].copy()
    test_over_bound['pred'] = test_over_bound['prob_down'].map(lambda x: -1. if x>bound else 1.)
    # confusion matrix when prob>0.6
    # [[20 24]
    #  [11 14]]
    print(f'confusion matrix when prob>{bound}')
    print(confusion_matrix(test_over_bound['pred'], test_over_bound['up_or_down']))
    # 計算するとこの場合で accuracy は 0.568
    # 上手くいっていない。
    

上手くいきませんでした。