GIL's LAB

실험 1. 이전 종가를 가지고 미래 종가 예측하기 (2) 실험 코드 본문

퀀트 투자/실험 일지

실험 1. 이전 종가를 가지고 미래 종가 예측하기 (2) 실험 코드

GIL~ 2021. 9. 4. 17:27

이번 포스팅에서는 이전 포스팅에서 설명한 이전 종가를 가지고 미래 종가를 예측하는 파이썬 코드를 설명한다.

실험 결과 및 백테스팅 결과는 다음 포스팅에서 정리하도록 한다.

 

먼저 실험에 필요한 모듈을 다음과 같이 임포트한다.

import os
import pandas as pd
import numpy as np
from sklearn.metrics import mean_absolute_percentage_error as MAPE
from sklearn.tree import DecisionTreeRegressor as DTR
from sklearn.linear_model import LinearRegression as LR
from sklearn.ensemble import RandomForestRegressor as RFR
from sklearn.neural_network import MLPRegressor as MLP
from sklearn.neighbors import KNeighborsRegressor as KNN
import warnings

참고로 sklearn.metrics에 있는 MAPE 함수는 sklearn 최신 버전에서만 사용이 가능하다.

만약 sklearn 최신 버전을 사용하고 있지 않다면, MAPE 함수를 아래와 같이 직접 만들어서 사용해도 무방하다.

 

def MAPE(y_true, y_pred): 
    y_true, y_pred = np.array(y_true), np.array(y_pred)
    return np.mean(np.abs((y_true - y_pred) / y_true)) * 100

 

그리고 데이터가 있는 경로를 설정하고, 불필요한 경고가 뜨는 것을 다음과 같이 막는다.

# 주가 데이터가 있는 곳으로 경로 설정
os.chdir("../GILLAB/QUANT_DATA/일별주가")
warnings.filterwarnings("ignore")

 

이제 핵심이 되는 함수 두 가지를 작성한다.

첫 번째 함수는 한 종목의 일별 주가 데이터 (data)와 lag (L)이 입력되었을 때, 시점 t의 종가를 라벨로, 시점 t-1, t-2, ..., t-L의 종가를 특징으로 하는 데이터프레임인 train_data를 반환하는 함수인 make_train_data이다. 

def make_train_data(data, L):
    train_data = df.copy()[['종가']] # df를 copy한 뒤 종가 컬럼만 가져옴 (데이터프레임 형태로 가져오기 위해 괄호를 두개사용)

    # L 이전의 종가 데이터를 부착 후, shift하면서 발생하는 결측 제거 
    for l in range(1, L+1):
        train_data["{}_이전종가".format(l)] = train_data['종가'].shift(l)
    train_data.dropna(inplace = True)
    return train_data

 

두 번째 함수는 train_data와 모델을 생성하는 함수가 입력되었을 때, train_data로 학습한 모델의 평균 MAPE를 반환해주는 scoring_model 함수이다. 

핵심 로직은 다음과 같다. 가장 먼저, numpy의 linspace 함수를 이용하여 학습 데이터의 크기를 11등분하는 점들의 목록인 train_fold_idx를 정의한다. 가령, 학습 데이터의 크기가 100이라면, [0, 10, 20, ..., 100]이 되도록 한다. 소수점이 나올 수 있으므로 astype(int)를 사용하여 모두 인트형으로 바꿔준다. 이제 train_fold_idx에 있는 구분점을 기준으로 앞쪽에 있는 데이터를 학습 데이터로, 뒤 쪽에 있는 데이터를 평가 데이터로 사용한다. 이 방법은 실험 설계에서도 소개한 바 있다. 데이터를 나눈 뒤 입력된 모델을 학습하여 MAPE를 계산하는 과정은 일반적인 학습 과정과 다를 바가 없다. 

def scoring_model(train_data, model_func):
    total_score = 0 # 점수 초기화

    # train data를 10등분하는데 사용 (맨 앞 인덱스는 0이므로 무시)
    train_fold_idx = np.linspace(0, len(train_data), 11).astype(int)

    for end_idx in train_fold_idx[1:-1]:
        # 데이터 분할
        train_df = train_data.iloc[:end_idx]
        test_df = train_data.iloc[end_idx:]
        
        train_X = train_df.drop('종가', axis = 1)
        train_Y = train_df['종가']
        test_X = test_df.drop('종가', axis = 1)
        test_Y = test_df['종가']
        
        # 모델 학습 및 평가
        model = model_func().fit(train_X, train_Y)
        pred_Y = model.predict(test_X)
        score = MAPE(test_Y, pred_Y)
        total_score += score
    
    return total_score / 9

이제 위 함수를 이용하여, 종목별 최적의 L과 회귀 모델을 찾는다.

참고로 KOSPI 폴더에 코스피 종목별 일별 주가 데이터가 csv 파일로 저장되어 있다. 

즉, 이 폴더에 있는 각 파일을 df로 불러온 뒤에, L과 model_func을 순회하면서 가장 MAPE를 낮게 하는 L과 model_func을 저장하는 로직이다. 이 로직은 일반적인 파라미터 튜닝 로직인데, 워낙 자주 사용되니 조만간 한 번 정리해야겠다. 

KOSPI_result = []

for data_name in os.listdir("KOSPI"):
    print(data_name)
    df = pd.read_csv("KOSPI/{}".format(data_name), encoding = "cp949").sort_values("날짜")
    if len(df) < 1000: # 길이가 1000 이상일때만 계속 수행
        continue
    best_score = 9999999 # 최고 점수 초기화
    for L in range(1, 11):
        train_data = make_train_data(df, L)
        for model_func in [DTR, LR, RFR, MLP, KNN]:
            model_score = scoring_model(train_data, model_func)
            if model_score < best_score:
                best_score = model_score
                best_model_func = model_func.__name__
                best_L = L
    
    KOSPI_result.append([data_name.split('.csv')[0], best_score, best_model_func, best_L])    

KOSPI_result = pd.DataFrame(KOSPI_result, columns = ["데이터", "평균MAPE", "모델", "lag"])
KOSPI_result.to_csv("실험1_이전종가를가지고미래종가예측하기_KOSPI결과.csv", index = False, encoding = "cp949")

완성된 코드는 아래와 같다.

실험 1. 이전 종가를 가지고 미래 종가 예측하기.ipynb
0.04MB

 

결과가 나오면 이쁘게 정리해서 포스팅해야겠다. 

 

 

 

수집하고 싶은 금융 데이터나 실험하고 싶은 퀀트 관련 아이디어가 있으면 댓글로 남겨주세요! 
관련 포스팅을 준비하도록 하겠습니다!

 

Comments