피처 엔지니어링
import FinanceDataReader as fdr
%matplotlib inline
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
pd.options.display.float_format = '{:,.3f}'.format
피처 엔지니어링#
가설 검정에서 만들었던 모든 피쳐(변수)를 정리해 보겠습니다. 이제 예측 모델링을 위한 데이터가 준비되었습니다. 예측모델링에 활용한 데이터의 기간은 2021년 1월 5일부터 2022년 3월 24일까지입니다.
mdl_data = pd.read_pickle('mdl_data.pkl') # 수익률 결과값이 있는 데이터
mdl_data.head()
print(mdl_data.index.min(), mdl_data.index.max())
2021-01-05 2022-03-24
가설검정에서 만들었던 모든 피쳐를 정리합니다. 단, “5일 이동평균선이 종가보다 위에 있다” 는 유의미하지 않았으므로 제외입니다. 결과를 feature_all 이라는 데이터프레임에 저장합니다.
kosdaq_list = pd.read_pickle('kosdaq_list.pkl')
feature_all = pd.DataFrame()
for code, sector in zip(kosdaq_list['code'], kosdaq_list['sector']):
data = mdl_data[mdl_data['code']==code].sort_index().copy()
# 가격변동성이 크고, 거래량이 몰린 종목이 주가가 상승한다
data['price_mean'] = data['close'].rolling(20).mean()
data['price_std'] = data['close'].rolling(20).std(ddof=0)
data['price_z'] = (data['close'] - data['price_mean'])/data['price_std']
data['volume_mean'] = data['volume'].rolling(20).mean()
data['volume_std'] = data['volume'].rolling(20).std(ddof=0)
data['volume_z'] = (data['volume'] - data['volume_mean'])/data['volume_std']
# 위꼬리가 긴 양봉이 자주 발생한다.
data['positive_candle'] = (data['close'] > data['open']).astype(int) # 양봉
data['high/close'] = (data['positive_candle']==1)*(data['high']/data['close'] > 1.1).astype(int) # 양봉이면서 고가가 종가보다 높게 위치
data['num_high/close'] = data['high/close'].rolling(20).sum()
data['long_candle'] = (data['positive_candle']==1)*(data['high']==data['close'])*\
(data['low']==data['open'])*(data['close']/data['open'] > 1.2).astype(int) # 장대 양봉을 데이터로 표현
data['num_long'] = data['long_candle'].rolling(60).sum() # 지난 20 일 동안 장대양봉의 갯 수
# 거래량이 종좀 터지며 매집의 흔적을 보인다
data['volume_mean'] = data['volume'].rolling(60).mean()
data['volume_std'] = data['volume'].rolling(60).std()
data['volume_z'] = (data['volume'] - data['volume_mean'])/data['volume_std'] # 거래량은 종목과 주가에 따라 다르기 떄문에 표준화한 값이 필요함
data['z>1.96'] = (data['close'] > data['open'])*(data['volume_z'] > 1.65).astype(int) # 양봉이면서 거래량이 90%신뢰구간을 벗어난 날
data['num_z>1.96'] = data['z>1.96'].rolling(60).sum() # 양봉이면서 거래량이 90%신뢰구간을 벗어난 날을 카운트
# 주가지수보다 더 좋은 수익율을 보여준다
data['num_win_market'] = data['win_market'].rolling(60).sum() # 주가지수 수익율이 1 보다 작을 때, 종목 수익율이 1 보다 큰 날 수
data['pct_win_market'] = (data['return']/data['kosdaq_return']).rolling(60).mean() # 주가지수 수익율 대비 종목 수익율
# 동종업체 수익률보다 더 좋은 수익율을 보여준다.
data['return_mean'] = data['return'].rolling(60).mean() # 종목별 최근 60 일 수익율의 평균
data['sector'] = sector
data['max_close'] = data[['close_r1','close_r2','close_r3','close_r4','close_r5']].max(axis=1) # 5 영업일 종가 수익율 중 최고 값
data['mean_close'] = data[['close_r1','close_r2','close_r3','close_r4','close_r5']].mean(axis=1) # 5 영업일 종가 수익율 중 최고 값
data['min_close'] = data[['close_r1','close_r2','close_r3','close_r4','close_r5']].min(axis=1) # 5 영업일 종가 수익율 중 최저 값
data = data[(data['price_std']!=0) & (data['volume_std']!=0)]
feature_all = pd.concat([data, feature_all], axis=0)
feature_all['sector_return'] = feature_all.groupby(['sector', feature_all.index])['return'].transform(lambda x: x.mean()) # 섹터의 평균 수익율 계산
feature_all['return over sector'] = (feature_all['return']/feature_all['sector_return']) # 섹터 평균 수익률 대비 종목 수익률 계산
feature_all.dropna(inplace=True) # Missing 값 있는 행 모두 제거
# 최종 피처 및 수익률 데이터만으로 구성
feature_all = feature_all[['code', 'sector','return','kosdaq_return','price_z','volume_z','num_high/close','num_long','num_z>1.96','num_win_market','pct_win_market','return over sector','max_close','mean_close','min_close']]
feature_all.to_pickle('feature_all.pkl')
이제 모델링을 위한 데이터 준비가 끝났습니다. 간단한 프로파일을 뽑아봅니다. 평균과 표준편차 값을 보고, 피처들이 제대로 생성되었는 지 확인합니다. 그리고 price_z 와 volum_z 는 같이 분석했을 때 유의미했다는 사실을 기억하면 좋겠습니다.
feature_all = pd.read_pickle('feature_all.pkl')
feature_all.describe(percentiles=[0.05, 0.1, 0.9, 0.95]).style.set_table_attributes('style="font-size: 12px"').format(precision=3)
return | kosdaq_return | price_z | volume_z | num_high/close | num_long | num_z>1.96 | num_win_market | pct_win_market | return over sector | max_close | mean_close | min_close | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | 329307.000 | 329307.000 | 329307.000 | 329307.000 | 329307.000 | 329307.000 | 329307.000 | 329307.000 | 329307.000 | 329307.000 | 329307.000 | 329307.000 | 329307.000 |
mean | 1.000 | 1.000 | -0.106 | -0.058 | 0.126 | 0.017 | 1.942 | 7.559 | 1.001 | 1.000 | 1.033 | 1.001 | 0.970 |
std | 0.035 | 0.013 | 1.316 | 1.063 | 0.387 | 0.131 | 2.015 | 2.840 | 0.004 | 0.030 | 0.071 | 0.053 | 0.050 |
min | 0.326 | 0.963 | -4.359 | -2.032 | 0.000 | 0.000 | 0.000 | 0.000 | 0.976 | 0.379 | 0.700 | 0.509 | 0.416 |
5% | 0.955 | 0.978 | -2.083 | -0.868 | 0.000 | 0.000 | 0.000 | 3.000 | 0.995 | 0.963 | 0.969 | 0.931 | 0.884 |
10% | 0.967 | 0.983 | -1.729 | -0.722 | 0.000 | 0.000 | 0.000 | 4.000 | 0.996 | 0.974 | 0.981 | 0.949 | 0.911 |
50% | 1.000 | 1.001 | -0.226 | -0.311 | 0.000 | 0.000 | 1.000 | 7.000 | 1.000 | 0.998 | 1.017 | 0.998 | 0.977 |
90% | 1.033 | 1.015 | 1.672 | 0.687 | 1.000 | 0.000 | 5.000 | 11.000 | 1.005 | 1.026 | 1.096 | 1.053 | 1.016 |
95% | 1.051 | 1.021 | 2.127 | 1.681 | 1.000 | 0.000 | 6.000 | 12.000 | 1.008 | 1.042 | 1.143 | 1.080 | 1.032 |
max | 1.300 | 1.046 | 4.359 | 7.617 | 5.000 | 2.000 | 15.000 | 22.000 | 1.043 | 1.399 | 3.703 | 2.346 | 1.300 |