작성자: admin 작성일시: 2016-07-08 11:01:57 조회수: 473 다운로드: 33
카테고리: 금융 공학 태그목록:

주가 지수의 회귀 분석

  • 이 내용은 "파이썬을 활용한 금융 분석" 6장 199-209 페이지의 자료를 수정하여 정리한 내용입니다.
  • 우분투 리눅스를 기반으로 한 datascienceschool/rpython 도커 이미지에서 실행하는 것을 기준으로 하였습니다.
  • Python 2를 사용하지만 import __future__ 기능으로 Python 3의 print 문법을 사용합니다.

주가 지수 데이터 수집

  • EURO STOXX 50
  • VSTOXX
In [1]:
from __future__ import print_function
import matplotlib.pylab as plt
In [2]:
import pandas as pd
from urllib import urlretrieve

es_url = 'https://www.stoxx.com/document/Indices/Current/HistoricalData/hbrbcpe.txt'
vs_url = 'https://www.stoxx.com/document/Indices/Current/HistoricalData/h_vstoxx.txt'

%mkdir -p data   # 데이터 저장용 디렉토리가 없는 경우에는 만들어야 한다.

urlretrieve(es_url, './data/es.txt')
urlretrieve(vs_url, './data/vs.txt')

%ls -o ./data/*.txt
-rw-r--r-- 1 dockeruser 523525 Jul  8 01:15 ./data/es50.txt
-rw-r--r-- 1 dockeruser 678399 Jul  8 01:53 ./data/es.txt
-rw-r--r-- 1 dockeruser 363485 Jul  8 01:53 ./data/vs.txt

데이터 전처리

In [3]:
lines = open('./data/es.txt', 'r').readlines()
lines = [line.replace(' ', '') for line in lines]
lines[:6]
Out[3]:
['PriceIndices-EUROCurrency\n',
 'Date;Blue-Chip;Blue-Chip;Broad;Broad;ExUK;ExEuroZone;Blue-Chip;Broad\n',
 ';Europe;Euro-Zone;Europe;Euro-Zone;;;Nordic;Nordic\n',
 ';SX5P;SX5E;SXXP;SXXE;SXXF;SXXA;DK5F;DKXF\n',
 '31.12.1986;775.00;900.82;82.76;98.58;98.06;69.06;645.26;65.56\n',
 '01.01.1987;775.00;900.82;82.76;98.58;98.06;69.06;645.26;65.56\n']
  • 3884행부터 semicolon 삭제가 필요
In [4]:
for line in lines[3883:3890]:
    print(line[41:],  end="")
317.10;267.23;5268.36;363.19
322.55;272.18;5360.52;370.94
322.69;272.95;5360.52;370.94
327.57;277.68;5479.59;378.69;
329.94;278.87;5585.35;386.99;
326.77;272.38;5522.25;380.09;
332.62;277.08;5722.57;396.12;
In [5]:
new_file = open('./data/es50.txt', 'w')
    # 새 파일을 연다
new_file.writelines('date' + lines[3][:-1]
                    + ';DEL' + lines[3][-1])
    # 원래 파일의 네 번째 줄을 수정하여 새 파일의 첫번째 줄에 쓴다.
new_file.writelines(lines[4:])
    # 원래 파일의 나머지 줄을 옮겨 쓴다.
new_file.close()
In [6]:
new_lines = open('./data/es50.txt', 'r').readlines()
new_lines[:5]
Out[6]:
['date;SX5P;SX5E;SXXP;SXXE;SXXF;SXXA;DK5F;DKXF;DEL\n',
 '31.12.1986;775.00;900.82;82.76;98.58;98.06;69.06;645.26;65.56\n',
 '01.01.1987;775.00;900.82;82.76;98.58;98.06;69.06;645.26;65.56\n',
 '02.01.1987;770.89;891.78;82.57;97.80;97.43;69.37;647.62;65.81\n',
 '05.01.1987;771.89;898.33;82.82;98.60;98.19;69.16;649.94;65.82\n']

데이터 임포트

In [7]:
es = pd.read_csv('./data/es50.txt', index_col=0,
                 parse_dates=True, sep=';', dayfirst=True)
np.round(es.tail())
Out[7]:
SX5P SX5E SXXP SXXE SXXF SXXA DK5F DKXF DEL
date
2016-04-26 2923.0 3121.0 347.0 330.0 412.0 354.0 9395.0 588.0 NaN
2016-04-27 2927.0 3130.0 348.0 332.0 413.0 354.0 9424.0 589.0 NaN
2016-04-28 2932.0 3125.0 349.0 332.0 414.0 355.0 9459.0 592.0 NaN
2016-04-29 2856.0 3028.0 341.0 324.0 405.0 349.0 9278.0 582.0 NaN
2016-05-02 2852.0 3033.0 341.0 324.0 405.0 348.0 9238.0 579.0 NaN
  • 줄 끝의 세미콜론 삭제
In [8]:
del es['DEL'] 
es.info()
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 7562 entries, 1986-12-31 to 2016-05-02
Data columns (total 8 columns):
SX5P    7562 non-null float64
SX5E    7562 non-null float64
SXXP    7562 non-null float64
SXXE    7562 non-null float64
SXXF    7562 non-null float64
SXXA    7562 non-null float64
DK5F    7562 non-null float64
DKXF    7562 non-null float64
dtypes: float64(8)
memory usage: 531.7 KB
  • read_csv 인수
    • index_col : row index 열 정의
    • parse_dates : 인덱스 열이나 복수 열의 날짜를 파싱할지 여부
    • sep : 필드 분리자 정의
    • dayfirst : 유럽식 날짜 표기 (날짜 표기에서 12 이하의 숫자가 두 개 연속인 경우, 날짜와 월 중에서 날짜를 먼저 표시했다는 의미)
    • header : 헤더 줄의 수. 헤더가 존재하지 않으면 None
    • skiprows : 읽지 않고 그냥 넘어갈 줄의 수
    • names : 열 이름
In [9]:
cols = ['SX5P', 'SX5E', 'SXXP', 'SXXE', 'SXXF',
        'SXXA', 'DK5F', 'DKXF']
es = pd.read_csv(es_url, index_col=0, parse_dates=True,
                 sep=';', dayfirst=True, header=None,
                 skiprows=4, names=cols)
es.tail()
Out[9]:
SX5P SX5E SXXP SXXE SXXF SXXA DK5F DKXF
2016-04-26 2922.57 3121.29 347.31 330.30 411.75 353.79 9395.03 587.73
2016-04-27 2927.31 3130.43 348.32 331.69 413.25 354.41 9424.15 589.23
2016-04-28 2932.00 3125.43 348.90 331.76 413.78 355.47 9459.04 591.95
2016-04-29 2856.28 3028.21 341.48 323.70 404.71 348.87 9278.19 581.67
2016-05-02 2852.10 3032.60 341.24 324.22 404.91 347.90 9238.16 578.79
In [10]:
vs = pd.read_csv('./data/vs.txt', index_col=0, header=2,
                 parse_dates=True, dayfirst=True)
vs.info()
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 4357 entries, 1999-01-04 to 2016-02-12
Data columns (total 9 columns):
V2TX    4357 non-null float64
V6I1    3906 non-null float64
V6I2    4357 non-null float64
V6I3    4296 non-null float64
V6I4    4357 non-null float64
V6I5    4357 non-null float64
V6I6    4340 non-null float64
V6I7    4357 non-null float64
V6I8    4343 non-null float64
dtypes: float64(9)
memory usage: 340.4 KB
In [11]:
vs.tail()  # 2016-02-12 이후의 데이터가 없음
Out[11]:
V2TX V6I1 V6I2 V6I3 V6I4 V6I5 V6I6 V6I7 V6I8
Date
2016-02-08 33.3917 34.7251 33.2189 31.8308 31.4783 30.6941 30.3705 29.8478 29.2559
2016-02-09 33.9664 35.7884 33.7800 32.6220 32.2459 31.5572 31.1659 30.5339 29.9000
2016-02-10 33.4528 34.6020 33.3637 32.1438 32.0018 31.2099 30.9937 30.8174 30.2533
2016-02-11 38.3051 41.2095 38.1347 35.8676 35.1073 33.7847 33.4126 32.2234 31.2124
2016-02-12 35.6846 35.3849 35.6965 34.1404 33.8645 32.9590 32.8208 32.3189 31.8194
In [12]:
import datetime as dt
data = pd.DataFrame({'EUROSTOXX' :
                     es['SX5E'][es.index > dt.datetime(1999, 1, 1)]})
data = data.join(pd.DataFrame({'VSTOXX' :
                     vs['V2TX'][vs.index > dt.datetime(1999, 1, 1)]}))

누락된 데이터의 처리

In [13]:
# 이 부분은 원서에 없는 부분입니다.
# VSTOXX 에서 누락된 데이터를 확인해 봅니다.

missing = data[data.isnull().any(axis=1)]
print(len(missing))
missing
87
Out[13]:
EUROSTOXX VSTOXX
1999-05-13 3693.62 NaN
1999-12-24 4781.16 NaN
2001-12-24 3696.98 NaN
2003-05-01 2324.23 NaN
2003-12-24 2725.07 NaN
2003-12-31 2760.66 NaN
2004-12-24 2951.16 NaN
2004-12-31 2951.01 NaN
2006-05-01 3839.90 NaN
2007-05-01 4392.41 NaN
2007-12-24 4388.38 NaN
2008-05-01 3825.02 NaN
2008-12-24 2377.42 NaN
2008-12-31 2447.62 NaN
2009-05-01 2375.34 NaN
2009-12-24 2957.03 NaN
2010-12-24 2861.94 NaN
2010-12-31 2792.82 NaN
2012-05-01 2306.69 NaN
2012-12-24 2648.53 NaN
2012-12-31 2635.93 NaN
2013-05-01 2711.74 NaN
2013-12-24 3072.88 NaN
2013-12-31 3109.00 NaN
2014-05-01 3198.66 NaN
2014-12-24 3184.66 NaN
2014-12-31 3146.43 NaN
2015-05-01 3615.59 NaN
2015-05-25 3655.41 NaN
2015-12-24 3284.47 NaN
... ... ...
2016-03-22 3051.23 NaN
2016-03-23 3042.42 NaN
2016-03-24 2986.73 NaN
2016-03-25 0.00 NaN
2016-03-28 0.00 NaN
2016-03-29 3004.87 NaN
2016-03-30 3044.10 NaN
2016-03-31 3004.93 NaN
2016-04-01 2953.28 NaN
2016-04-04 2962.28 NaN
2016-04-05 2890.35 NaN
2016-04-06 2909.36 NaN
2016-04-07 2871.57 NaN
2016-04-08 2911.98 NaN
2016-04-11 2924.23 NaN
2016-04-12 2942.09 NaN
2016-04-13 3039.19 NaN
2016-04-14 3060.86 NaN
2016-04-15 3054.34 NaN
2016-04-18 3064.03 NaN
2016-04-19 3112.99 NaN
2016-04-20 3142.52 NaN
2016-04-21 3151.69 NaN
2016-04-22 3141.12 NaN
2016-04-25 3117.62 NaN
2016-04-26 3121.29 NaN
2016-04-27 3130.43 NaN
2016-04-28 3125.43 NaN
2016-04-29 3028.21 NaN
2016-05-02 3032.60 NaN

87 rows × 2 columns

  • 2016-02-12 이후의 데이터는 상당량이 누락되어 회귀 분석에 영향을 미칠 수 있으므로 제외
  • 중간에 일부 누락된 부분은 fillna 메서드로 보정(imputation)
  • inner join을 사용하여 한 번에 null 데이터를 제외하는 방법도 있으나 fillna 메서드 사용법을 보이고자 하는 저자의 의도를 유지하기 위해 두 가지 방법을 사용하였습니다.
In [14]:
notnull = data[data.notnull().all(axis=1)].tail()
notnull
Out[14]:
EUROSTOXX VSTOXX
2016-02-08 2785.17 33.3917
2016-02-09 2736.50 33.9664
2016-02-10 2789.05 33.4528
2016-02-11 2680.35 38.3051
2016-02-12 2756.16 35.6846
In [15]:
data = data[data.index <= notnull.index[-1]]
In [16]:
data = data.fillna(method='ffill')
data.info()
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 4387 entries, 1999-01-04 to 2016-02-12
Data columns (total 2 columns):
EUROSTOXX    4387 non-null float64
VSTOXX       4387 non-null float64
dtypes: float64(2)
memory usage: 102.8 KB
In [17]:
data.tail()
Out[17]:
EUROSTOXX VSTOXX
2016-02-08 2785.17 33.3917
2016-02-09 2736.50 33.9664
2016-02-10 2789.05 33.4528
2016-02-11 2680.35 38.3051
2016-02-12 2756.16 35.6846
In [18]:
missing = data[data.isnull().any(axis=1)]
print(len(missing))
0
In [19]:
data.plot(subplots=True)
plt.show()

지수를 수익률로 환산

In [20]:
rets = np.log(data / data.shift(1)) 
rets.head()
Out[20]:
EUROSTOXX VSTOXX
1999-01-04 NaN NaN
1999-01-05 0.017228 0.489248
1999-01-06 0.022138 -0.165317
1999-01-07 -0.015723 0.256337
1999-01-08 -0.003120 0.021570
In [21]:
rets.plot(subplots=True, grid=True, style='b', figsize=(8, 6))
plt.show()

회귀 분석

  • 현재는 pandas 패키지의 ols 명령을 사용하지만 곧 폐기될 예정
  • 이후에는 statsmodels 패키지의 OLS 명령 사용
In [22]:
xdat = rets['EUROSTOXX']
ydat = rets['VSTOXX']
model = pd.ols(y=ydat, x=xdat)
model
/home/dockeruser/anaconda2/lib/python2.7/site-packages/IPython/core/interactiveshell.py:2885: FutureWarning: The pandas.stats.ols module is deprecated and will be removed in a future version. We refer to external packages like statsmodels, see some examples here: http://statsmodels.sourceforge.net/stable/regression.html
  exec(code_obj, self.user_global_ns, self.user_ns)
Out[22]:
-------------------------Summary of Regression Analysis-------------------------

Formula: Y ~ <x> + <intercept>

Number of Observations:         4386
Number of Degrees of Freedom:   2

R-squared:         0.5431
Adj R-squared:     0.5430

Rmse:              0.0394

F-stat (1, 4384):  5211.9223, p-value:     0.0000

Degrees of Freedom: model 1, resid 4384

-----------------------Summary of Estimated Coefficients------------------------
      Variable       Coef    Std Err     t-stat    p-value    CI 2.5%   CI 97.5%
--------------------------------------------------------------------------------
             x    -2.8461     0.0394     -72.19     0.0000    -2.9233    -2.7688
     intercept    -0.0000     0.0006      -0.02     0.9872    -0.0012     0.0012
---------------------------------End of Summary---------------------------------
In [23]:
model.beta
Out[23]:
x           -2.846059
intercept   -0.000010
dtype: float64
In [24]:
plt.plot(xdat, ydat, 'r.')
ax = plt.axis()  # grab axis values
x = np.linspace(ax[0], ax[1] + 0.01)
plt.plot(x, model.beta[1] + model.beta[0] * x, 'b', lw=2)
plt.grid(True)
plt.axis('tight')
plt.xlabel('EURO STOXX 50 returns')
plt.ylabel('VSTOXX returns')
plt.show()

statsmodels 패키지를 사용한 회귀 분석

In [28]:
import statsmodels.api as sm

model_sm = sm.OLS.from_formula("VSTOXX ~ EUROSTOXX", data=rets)
result_sm = model_sm.fit()
print(result_sm.summary())
                            OLS Regression Results                            
==============================================================================
Dep. Variable:                 VSTOXX   R-squared:                       0.543
Model:                            OLS   Adj. R-squared:                  0.543
Method:                 Least Squares   F-statistic:                     5212.
Date:                Fri, 08 Jul 2016   Prob (F-statistic):               0.00
Time:                        01:55:02   Log-Likelihood:                 7966.3
No. Observations:                4386   AIC:                        -1.593e+04
Df Residuals:                    4384   BIC:                        -1.592e+04
Df Model:                           1                                         
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
Intercept   -9.51e-06      0.001     -0.016      0.987      -0.001       0.001
EUROSTOXX     -2.8461      0.039    -72.194      0.000      -2.923      -2.769
==============================================================================
Omnibus:                     1316.059   Durbin-Watson:                   2.050
Prob(Omnibus):                  0.000   Jarque-Bera (JB):            24623.464
Skew:                           0.951   Prob(JB):                         0.00
Kurtosis:                      14.451   Cond. No.                         66.3
==============================================================================

Warnings:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

상관 계수

  • 원서에는 rolling_corr 를 사용하였으나 곧 폐기 예정.
In [25]:
rets.corr()
Out[25]:
EUROSTOXX VSTOXX
EUROSTOXX 1.00000 -0.73698
VSTOXX -0.73698 1.00000
In [26]:
# 폐기 예정
pd.rolling_corr(rets['EUROSTOXX'], rets['VSTOXX'],
                window=252).plot(grid=True, style='b')
plt.show()
/home/dockeruser/anaconda2/lib/python2.7/site-packages/ipykernel/__main__.py:3: FutureWarning: pd.rolling_corr is deprecated for Series and will be removed in a future version, replace with 
	Series.rolling(window=252).corr(other=<Series>)
  app.launch_new_instance()
In [27]:
# 새로 지원하는 방법
rets['EUROSTOXX'].rolling(window=252).corr(rets['VSTOXX']).plot(grid=True, style='b')
plt.show()