M2 평탄화

이 페이지에서 말하는 M2란 광의통화를 의미한다. 작년 2020년 코로나가 전세계로 급속도로 번지면서 경제가 급격히 얼어붙었다. 그래서 2020년 3월 미국의 FED에서 돈을 엄청나게 많이 뿌리며 금융 충격에 대비하였고 이에 대부분의 자산은 급격한 반등을 이루게 되었다.

이 과정에서 M2 통화량 증가분을 제거하면 어느정도 수준으로 반등이 이루어졌는지 파악해볼수 있지 않을까? 하는 아이디어에서 M2 영향도를 제거하는 차트를 그려보는 간단한 개발을 하였다. 이는 작년 9~10월 정도에 python으로 개발하였다.

M2flat Demo사이트

데이터 수집하기

먼저 데이터가 필요한데 구할 데이터는 명확하다. M2, 그리고 각 지수 데이터이다. M2는 FRED 사이트에서 지수데이터는 Yahoo finance에서 구한다. CSV형태로 바로 다운받을 수 있게 제공하고 있다.

M2 구하기

구글에서 fred m2 라고 검색하면 된다. FRED M2 사이트가 나오는데 M2의 대한 그래프가 나온다.

M2 img (2021.05.04 현재 데이터 제공 중단. 한달간격의 데이터로 변경됨)

2020년 3월에 급격히 증가한 M2를 보면 유동성 공급이 얼마나 어마어마 했는지 알수 있다.

위에서 Download->CSV-Data 에 링크를 얻어보자

TXT
https://fred.stlouisfed.org/graph/fredgraph.csv?bgcolor=%23e1e9f0&chart_type=line
&drp=0&fo=open%20sans&graph_bgcolor=%23ffffff
&height=450&mode=fred&recession_bars=on&txtcolor=%23444444&ts=12&tts=12&width=1168&nt=0&thu=0
&trc=0&show_legend=yes&show_axis_titles=yes&show_tooltip=yes&id=M2&scale=left
&cosd=1980-11-03&coed=2021-02-01&line_color=%234572a7&link_values=false
&line_style=solid&mark_type=none&mw=3&lw=2&ost=-99999&oet=99999&mma=0
&fml=a&fq=Weekly%2C%20Ending%20Monday&fam=avg&fgst=lin
&fgsnd=2020-02-01&line_index=1&transformation=lin&vintage_date=2021-05-04&revision_date=2021-05-04&nd=1980-11-03

엄청나게 긴 URL이 나온다. 저런식으로 ? 뒤에 key=value& 식으로 계속 붙는 모양을 query string 이라고 한다. 서버에서는 Key,value값을 이용하여 적당히 가공을 하여 요청에 응답을 주게 된다. 이중에 1980년도의 일자는 왠지 from 날짜 같고 2021년도 일자들은 to날짜에 가까워 보인다. 따라서 요즘 날짜들을 구하여 포맷팅으로 넣을수 있도록 날짜를 %s 로 대치한다.

PY
furl = 'https://fred.stlouisfed.org/graph/fredgraph.csv?b...긴URL'
tdy = datetime.datetime.today().strftime('%Y-%m-%d')
url = furl%(tdy,tdy,tdy,tdy)

with urllib.request.urlopen(url) as response:
	html = response.read()
	with open('M2.csv', 'wb') as save_file:
		save_file.write(html)

이런식으로 오늘일자를 넣어 조립하게 되면 항상 최신날짜로 url이 생성될 것이다. python의 with문은 syntax sugar에 가깝다. __enter__, __exit__ 메소드를 구현하면 시작될 때와 끝날 때 자동으로 호출되게 된다. IO 작업시 열고-닫는 한 과정을 감싸는 형태의 패턴으로 많이 사용한다. URL open IO 그리고 파일IO 두개를 사용한다.

TXT
DATE,M2
1980-11-03,1591.4
1980-11-10,1592.9
1980-11-17,1596.3
1980-11-24,1597.2
1980-12-01,1596.1
1980-12-08,1597.0
1980-12-15,1599.5
1980-12-22,1599.3
1980-12-29,1595.6
1981-01-05,1596.6
...

M2.csv 파일은 위와 같은 내용이 담겨지게 된다. 일자가 7일 간격인 것을 볼수 있다.

지수 구하기

Yahoo finance에서 IXIC(나스닥) Historical Data 눌러서 보면 Download 링크가 있다.

TXT
https://query1.finance.yahoo.com/v7/finance/download/%5EIXIC?
period1=1588599485&period2=1620135485&interval=1d
&events=history&includeAdjustedClose=true

period1, period2 가 From, To 역할을 하는것으로 보이며 시간이 숫자형태로 나와있는데 저런 경우는 십중팔구 Unix-time 이다.

PY
stime = datetime.datetime.now()
etime = datetime.datetime.now() - datetime.timedelta(days=365)
print(stime, etime)

ustime = int(stime.timestamp())
uetime = int(etime.timestamp())
print(ustime, uetime)

furl = 'https://query1.finance.yahoo.com/v7/finance/download/%%5E%s?period1=%s&period2=%s&interval=1d&events=history'

print(furl%(symbol, uetime, ustime))

with urllib.request.urlopen(furl%(symbol, uetime, ustime)) as response:
	html = response.read()
	with open(symbol + '.csv', 'wb') as save_file:
		save_file.write(html)

현재시간을 구하고 현재시간에서 365일전 시간을 구한뒤에 각각의 timestamp를 구하고 이를 url 에 넣어 조립한다. 그뒤 URL Open , 파일저장 순이다.

Yahoo에서는 지수인 경우는 심볼 앞에 ^ 문자를 붙이는데 이는 URL encode가 되면 %에 5E 가 붙은것을 볼수 있다. 5E는 아스키코드 참조하면 된다. 주의해야할 점은 파이썬의 String에서 % 문자는 포맷팅을 지시하는데 쓰이므로 %문자 자체로 보여지게 하려면 %를 escape시켜줘야한다. 간단하게 %% 두번 쓰면 % 하나로 인식되게 된다.

TXT
Date,Open,High,Low,Close,Adj Close,Volume
2019-10-31,8314.379883,8321.799805,8248.809570,8292.360352,8292.360352,2258270000
2019-11-01,8335.049805,8386.750000,8326.559570,8386.400391,8386.400391,2057310000
2019-11-04,8445.500000,8451.370117,8421.299805,8433.200195,8433.200195,2149910000
2019-11-05,8446.620117,8457.389648,8421.049805,8434.679688,8434.679688,2316710000
2019-11-06,8426.570313,8426.570313,8379.330078,8410.629883,8410.629883,2332650000
2019-11-07,8455.110352,8483.160156,8415.870117,8434.519531,8434.519531,2393950000
2019-11-08,8422.669922,8475.570313,8405.889648,8475.309570,8475.309570,1974190000
2019-11-11,8431.259766,8467.290039,8425.480469,8464.280273,8464.280273,1715470000
2019-11-12,8471.070313,8514.839844,8462.990234,8486.089844,8486.089844,1987820000
...

csv파일은 위와 같은 내용이 담겨진다. 일자는 Daily이다. 하지만 금융데이터이므로 주말이나 공휴일은 비어있게 된다.

M2평탄화 하기

두개의 데이터를 구했으므로 이론상 평탄화 한 값을 구할 수 있지만 몇가지 문제가 있다.

  1. M2는 1980년부터 시작, 지수는 2019년부터 시작
  2. M2는 Weekly , 지수는 Daily 간격이 다름

사실 위를 구하기 위해 늙은 프로그래머인 본인은 반복문을 짜고 M2의 일자들을 사용하여 Weekly 기준 지수 ‘시고저종’를 구하고 매핑해서 데이터를 구한 구식 프로그램을 짯다. 하지만 파이썬을 이용하면서 이렇게 구닥다리 노가다를 하는 것이 맞는지 의문이 들었고 조사결과 Pandas 라는 걸출한 라이브러리를 발견하게 되었다.

데이터 Merge

Pandas는 정말 엄청나고 멋있고 위대한 라이브러리이다. 두개의 다른 데이터를 합쳐야 할 때 어떻게 할 것인가? 의문이 들었을때 늙은 프로그래머인 본인은 SQL에서 Left Outer Join 방식이 떠올랐다. 비슷한게 Pandas에 있다.

PY
m2 = pd.read_csv('M2.csv')
m2.columns = ['Date', 'M2']

ixic = pd.read_csv('IXIC.csv')

# 일봉 기준으로 병합 
merged = pd.merge(ixic, m2, on='Date', how='left')

그냥 직관적으로 merge를 하면 된다. 이때 기준점은 컬럼 Date이며 이를 위해 M2의 컬럼명을 IXIC의 컬럼명과 일치 시켰다. 그리고 how=left 를 통해 ixic 기준으로 m2가 합쳐지도록 했다. ixic가 더 조밀한 데이터이므로 m2는 더 드물게 나타난다.

TXT
          Date         Open  ...     Volume       M2
0   2019-09-23  8106.490234  ... 1780160000  15045.7
1   2019-09-24  8147.229980  ... 2302600000      NaN
2   2019-09-25  7990.660156  ... 2015270000      NaN
3   2019-09-26  8070.120117  ... 1832800000      NaN
4   2019-09-27  8047.109863  ... 2037720000      NaN
5   2019-09-30  7964.089844  ... 1805820000  15086.4
6   2019-10-01  8026.830078  ... 2243650000      NaN
7   2019-10-02  7851.129883  ... 2495160000      NaN
8   2019-10-03  7787.020020  ... 2141490000      NaN
9   2019-10-04  7908.439941  ... 1736890000      NaN
10  2019-10-07  7956.410156  ... 1739450000  15110.5
11  2019-10-08  7898.270020  ... 1933580000      NaN
12  2019-10-09  7895.959961  ... 1553900000      NaN
13  2019-10-10  7904.560059  ... 1778740000      NaN
14  2019-10-11  8047.339844  ... 2176080000      NaN
15  2019-10-14  8044.350098  ... 1419730000  15116.9
16  2019-10-15  8074.850098  ... 1836650000      NaN
17  2019-10-16  8119.810059  ... 1886720000      NaN
18  2019-10-17  8176.910156  ... 1861570000      NaN
19  2019-10-18  8149.850098  ... 2012930000      NaN
...

결과는 위처럼 병합하여 나오게 된다. M2가 예상대로 중간중간 NaN으로 구멍이 나있는 것을 볼수 있다.

Interpolation

M2가 중간 결측값이 있으므로 이를 채워주는 방법이 필요하다. 이를 보간처리 한다.

PY
# 결측값은 Nan이므로 보간처리한다.
merged['M2'] = merged['M2'].interpolate(method='values')

여기서 Pandas의 위대함이 드러난다. 단지 M2 ‘열’에 대해서 보간하라고 알려주었을 뿐이다. 이 라이브러리는 알아서 사이와 사이에 보간값을 채워준다.

TXT
          Date         Open ...     Volume        M2
0   2019-09-23  8106.490234 ... 1780160000  15045.70
1   2019-09-24  8147.229980 ... 2302600000  15053.84
2   2019-09-25  7990.660156 ... 2015270000  15061.98
3   2019-09-26  8070.120117 ... 1832800000  15070.12
4   2019-09-27  8047.109863 ... 2037720000  15078.26
5   2019-09-30  7964.089844 ... 1805820000  15086.40
6   2019-10-01  8026.830078 ... 2243650000  15091.22
7   2019-10-02  7851.129883 ... 2495160000  15096.04
8   2019-10-03  7787.020020 ... 2141490000  15100.86
9   2019-10-04  7908.439941 ... 1736890000  15105.68
10  2019-10-07  7956.410156 ... 1739450000  15110.50
11  2019-10-08  7898.270020 ... 1933580000  15111.78
12  2019-10-09  7895.959961 ... 1553900000  15113.06
13  2019-10-10  7904.560059 ... 1778740000  15114.34
14  2019-10-11  8047.339844 ... 2176080000  15115.62
15  2019-10-14  8044.350098 ... 1419730000  15116.90
16  2019-10-15  8074.850098 ... 1836650000  15124.70
17  2019-10-16  8119.810059 ... 1886720000  15132.50
18  2019-10-17  8176.910156 ... 1861570000  15140.30
19  2019-10-18  8149.850098 ... 2012930000  15148.10

중간값들이 채워진 것을 볼수 있다.

새로운 열 구하기

첫 행을 기준점으로 잡은 뒤에 현재의 M2값에서 기준점의 M2가 얼마의 비중을 차지하는지 계산 후 각 가격에 곱하여 영향도가 제거된 차트를 구한다.

PY
# M2 Flatting 
base_m2 = merged.loc[0]['M2']

merged['Open_1'] = (merged['Open'] * base_m2 / merged['M2']).round(2)
merged['High_1'] = (merged['High'] * base_m2 / merged['M2']).round(2)
merged['Low_1' ] = (merged['Low' ] * base_m2 / merged['M2']).round(2)
merged['Close_1'] = (merged['Close'] * base_m2 / merged['M2']).round(2)

단지 dictionary에 key value 추가하는 것처럼 쉽게 된다. 실제로는 ‘열’이 추가가 되었으며 모든 행에 대해서 계산되게 된다.

TXT
          Date         Open         High          Low  ...   Open_1   High_1    Low_1  Close_1
0   2019-09-23  8106.490234  8135.810059  8085.339844  ...  8106.49  8135.81  8085.34  8112.46     
1   2019-09-24  8147.229980  8158.830078  7969.649902  ...  8142.82  8154.42  7965.34  7989.31     
2   2019-09-25  7990.660156  8095.000000  7935.569824  ...  7982.02  8086.25  7926.99  8068.65     
3   2019-09-26  8070.120117  8072.109863  7991.020020  ...  8057.04  8059.03  7978.07  8017.65     
4   2019-09-27  8047.109863  8051.830078  7890.279785  ...  8029.73  8034.44  7873.24  7922.49     
5   2019-09-30  7964.089844  8012.160156  7949.629883  ...  7942.60  7990.54  7928.18  7977.76     
6   2019-10-01  8026.830078  8062.500000  7906.290039  ...  8002.62  8038.18  7882.44  7884.83     
7   2019-10-02  7851.129883  7852.700195  7744.959961  ...  7824.95  7826.51  7719.13  7759.29     
8   2019-10-03  7787.020020  7872.259766  7700.000000  ...  7758.58  7843.50  7671.87  7843.50     
9   2019-10-04  7908.439941  7986.620117  7899.390137  ...  7877.04  7954.91  7868.02  7950.77     
10  2019-10-07  7956.410156  8013.310059  7942.080078  ...  7922.29  7978.95  7908.02  7922.17     
11  2019-10-08  7898.270020  7921.879883  7823.729980  ...  7863.73  7887.24  7789.52  7789.57     
12  2019-10-09  7895.959961  7930.919922  7873.520020  ...  7860.77  7895.57  7838.43  7868.51     
13  2019-10-10  7904.560059  7982.839844  7899.810059  ...  7868.66  7946.59  7863.93  7914.67     
14  2019-10-11  8047.339844  8115.799805  8046.799805  ...  8010.12  8078.26  8009.58  8019.77     
15  2019-10-14  8044.350098  8069.850098  8036.410156  ...  8006.46  8031.84  7998.56  8010.74     
16  2019-10-15  8074.850098  8166.180176  8071.810059  ...  8032.67  8123.53  8029.65  8106.15     
17  2019-10-16  8119.810059  8146.149902  8103.379883  ...  8073.23  8099.42  8056.90  8077.58     
18  2019-10-17  8176.910156  8183.640137  8131.250000  ...  8125.82  8132.51  8080.44  8105.88     
19  2019-10-18  8149.850098  8157.359863  8045.370117  ...  8094.76  8102.22  7990.98  8034.86  
...

차트 그리기

사실 데이터를 구하고 Pandas를 이용해 데이터를 가공하는 데까지는 30분도 안 걸려서 개발을 했었다. 하지만 이를 표현하는 차트를 그리는데 시간이 훨씬 더 걸렸다. 이것은 라이브러리 조사하고 사용방법을 공부해보고 내가 원하는 기능이 있는지 조사하고 없으면 다른것을 찾고 복잡하면 다른거 찾아보고..

결국 Ploty 라이브러리를 선택하여 차트를 그렸지만 사실 지금와서 생각해보면 TradingView의 lightweight chart 를 이용해서 그리는게 더 나을 것 같다는 생각이 든다.

PY
tr1 = go.Candlestick(x=df['Date'],
				open=df['Open_1'],
				high=df['High_1'],
				low=df['Low_1'],
				close=df['Close_1'],
				name='M2-Flat')

tr2 = go.Scatter(x=df['Date'],
				y=df['M2'],
				name='M2',
				yaxis='y2')

tr3 = go.Candlestick(x=df['Date'],
				open=df['Open'],
				high=df['High'],
				low=df['Low'],
				close=df['Close'],
				name='original',
				increasing_line_color= 'gray', decreasing_line_color= 'magenta')


fig = make_subplots(specs=[[{'secondary_y': True}]])
fig.add_trace(tr1)
fig.add_trace(tr2, secondary_y=True)
fig.add_trace(tr3)

##
fig.update_layout(
	xaxis_rangeslider_visible=False,
	title_text='IXIC-M2'
)
fig.layout.xaxis.type = 'category'
fig.write_html('IXIC_M2.html', auto_open=False)

굳이 차트라이브러리를 익힐 필요는 없는 것 같다. 필요할 때 가져다가 응용할수 있을 정도면 될것 같다.

개인적 생각

위 모든 내용이 반영된 레포는 doongchoong/m2flat 이다.

완성된 차트를 보자.

m2 chart

빨간선은 M2 이고 보라색 캔들차트는 실제 지수차트, 녹색빨간색 캔들차트는 보정된 지수차트이다.

사실 이 프로그램을 만든 이유는 유동성 유입은 기본적으로 점진적인 상승을 이끈다는 생각 때문이였다. 하지만 M2를 평탄화 한 것은 그 생각에 대한 나의 주장일 뿐이고 뒷받침하는 근거가 되진 않는다.

사실 M2평탄화는 통화가치하락을 보정한 것이다. 코로나 충격이후 많은 돈이 유입되었고 그외 많은 노력으로 지수상승을 이끌어 냈는데 보정된 차트와 실제차트 사이에 사람들의 노력이 반영된 Real값 차트가 있을 것이다.

적어도 이런 지속적인 유동성 유입으로 하나는 알 수 있다. ‘시장을 역행하는 인버스 베팅은 불리한 게임’ 이라는 것이다.