[오늘의 개발] Binance 자산 현황을 파이 차트로 만들기

최근에 내 자산이 어떻게, 얼마나, 어느 비중만큼 있는지 다시 확인하는 작업을 하고 있었다. 사실 지난 고등학교 3년동안 투자는 해놓고선, 정작 자산이 어떻게 변동되는지 살펴보지 않았었다. 체크카드로 긁을 때마다 날라오는 통장 잔고만 보고선, 충분하다 생각하면 넘어가는 경우가 대부분이였다.

이제서야 정신차리고 확인해보니, 암호화폐와 미국 증시는 대폭락장을 겪고 있었다. 세상은 변하는데 내 포트폴리오는 과거에 머물고 있었던 것이다. 당연히 리스크 헷지 같은 것이 될리가 없었고, 그대로 손실을 껴안고 있었다. 더 이상 이럴순 없다는 생각에, 주기적으로 자산 현황을 추적하는 일을 해야겠다는 생각이 들었다.

특히, 가장 시급한건 변동성이 큰 암호화폐였다. 대충 코딩을 해서라도, 내가 어떤 코인을 얼마나 가지고 있는지 시각화 해서 결과를 알아보고 싶었다.

 

Binance API로 데이터 가져오기

올해 봄 즈음, 나름 알고리즘 트레이딩에 관심이 많아 Binance API를 만지작거린 경험이 있다. 이동평균선 크로스와 볼린저 밴드를 이용한, 아주 단순한 알고리즘이었고, 당연히 수익은 내지 못했다. 그래도 잘 보관해놔서, 이번에 빠르게 꺼내다 쓸 수 있었다.

패키지 불러오기 및 설정
Binance API 데이터를 가져오기 위해 python-binance 패키지를 사용했다.패키지 설명서는 여기서 확인할 수 있다. 사실 RESTful API를 통해서 직접 POST해서 가져올 수도 있지만, 이미 좋은 패키지가 있는데 그럴 이유가 전혀 없었다. 아무튼, 필요한 패키지들을 불러오고선 기본적인 설정들을 해줬다.

from binance.client import Client
import warnings
warnings.filterwarnings("ignore")
from matplotlib import pyplot as plt


그다음에는 가장 기본적으로 Client()에 API Key와 Secret 키를 입력해줘서 접근 권한을 얻도록 해줬다. 너무 빨리 타임아웃이 발생하지 않도록 넉넉히 100ms으로 잡아뒀다.

client = Client('Binance API Key', 'Binance Secret Key', {"verify": False, "timeout": 100})


비트코인 최근 가격 불러오기

이제부터 다양한 정보들을 직접 불러오는데, 가장 먼저 비트코인의 최신 가격을 불러왔다. 방법은 간단하게, 1분 단위 OHLCV Kline 과거 데이터 중에서 가장 최근 Close 가격을 가져오도록 해두었다. 즉, 가장 최근의 가격 정보를 쿼리하는 것이다.

BTCPrice = float(client.get_klines(symbol='BTCUSDT',
                            interval=client.KLINE_INTERVAL_1MINUTE,
                            limit=1,
                            requests_params={'timeout':100})[0][4])


참고로 OHLCV 정보가 list in list 형태로 제공되기 때문에, 적절히 리스트에서 골라 써야 한다.
[0]을 통해서 첫번째이자 유일한 값에 접근하고, [4]로 1분 봉 마감가격인 Close에 접근했다.

# get_klines 출력값
[[
1499040000000, # Open time
"0.01634790", # Open
"0.80000000", # High
"0.01575800", # Low
"0.01577100", # Close -> List[0][4]
"148976.11427815", # Volume
1499644799999, # Close time
"2434.19055334", # Quote asset volume
308, # Number of trades
"1756.87402397", # Taker buy base asset volume
"28.46694368", # Taker buy quote asset volume
"17928899.62484339" # Can be ignored
]]


Binance 자산 현황을 불러 데이터 전처리하기

그 다음에는 내 Binance 거래소 지갑에 있는 자산 현황을 불러온다. 간단하게 get_account()를 통해 내가 가지고 있는 코인들과 잔고 수량을 확인할 수 있다. 자산 현황 정보는 dict in list in dict 형태로 주기 때문에, 적절히 내가 보유한 코인들과 잔고 수량을 볼 수 있게 접근해줘야 한다.

AssetBalance = client.get_account()
#get_account 불러오기 샘플
{'makerCommission': 10,
'takerCommission': 10,
'buyerCommission': 0,
'sellerCommission': 0,
'canTrade': True,
'canWithdraw': True,
'canDeposit': True,
'updateTime': 1539110138808,
'balances': [{'asset': 'NEO', 'free': '0.00000000', 'locked': '0.00000000'}, {'asset': 'LTC', 'free': '0.00000000', 'locked': '0.00000000'}...]


간단하게는 Dict[‘balances’]로 먼저 접근하고선, for문으로 개별 딕셔너리를 조회하는 방법이 있다. 여기서 if절로 잔고 수량이 0 이상인 코인들만 거른 다음에 AvailableAsset이라는 리스트에 저장시켜줬다.

AvailableAsset = []
for Asset in AssetBalance['balances']:
    if float(Asset['free']) > 0:
        AvailableAsset.append(Asset)


그 다음에는 for문으로 AltPricePair라는 딕셔너리에 {코인명 : 코인 잔고 수량} 페어를 만들어 저장시켜준다. 이를 위해, PairName에 000BTC 형식으로 환율 거래쌍 이름을 만들어 검색했다. 여기서 문제는 BTCBTC나 USDTBTC와 같은 거래쌍은 없는지라, 그대로 돌리면 BTC 잔고를 검색하려다 ‘그런 거래쌍은 없다’라는 에러가 뿜어져 나온다. 이를 해결하기 위해서, try-except문을 이용해서 에러가 나타나면 바로 패스하도록 만들었다.

이렇게 거래쌍을 통해 OHLCV 값을 불러오면, 해당 코인의 비트코인 기준 가격이 나온다. 이걸 해당 코인 수량만큼 곱해주면, 잔고 금액이 비트코인 기준으로 얼마나 있는지 알 수 있다. 하지만 우리는 비트코인보다 미국 달러가 익숙하기 때문에, 이전에 BTCPrice에 불러온 비트코인 가격을 여기다 곱해주면 미국 달러 기준 잔고량으로 변환 가능하다. 이 값을 AltPricePair딕셔너리에 키-벨류 페어로 저장시켜주면 된다.

AltPricePair = {}
for Asset in AvailableAsset:
    PairName = Asset['asset'] + 'BTC'
    try:
        MinuteKlineRaw = client.get_klines(symbol=PairName,
                                           interval=client.KLINE_INTERVAL_1MINUTE,
                                           limit=1,
                                           requests_params={'timeout': 100})
        CurrentPrice = MinuteKlineRaw[0][4]
        AltPricePair[Asset['asset']] = float(CurrentPrice) * float(Asset['free']) * BTCPrice
    except:
        continue


이전에 BTCBTC나 USDTBTC와 같은 환율 거래쌍은 존재하지 않아 불러올 수 없다고 했다. 그렇다고 비트코인이나 미국 달러 자산 값을 빼고 알트코인들의 가치만 더할 수는 없다. 다행히도, get_asset_balance()라고 개별 코인에 대한 잔고 수량을 불러오는 함수가 있다. 비트코인은 잔고 수량을 불러서, 비트코인의 미국 달러 가격에 곱해줘서 잔고 금액을 MyBTCBalance에 저장한다. 미국 달러화는 당연히 잔고 수량이 잔고 금액과 동일하니, 그대로 사용해 MyUSDTBalance에 저장한다. 그리고 가장 중요한 알트코인의 가격, AltPricePair에 수많은 키-벨류 페어에서 values()를 통해 벨류 값들의 리스트를 구한다. 그리고선 바로 sum()으로 그 합들을 MyAltBalance에 저장한다.

마지막으로 MyBTCBalance, MyUSDTBalance, MyAltBalace 이 셋을 모두 더해주면, 내 Binance 자산의 총 합계를 미국 달러화 기준으로 볼 수 있다.

MyBTCBalance = BTCPrice * float(client.get_asset_balance('BTC')['free'])
MyUSDTBalance = float(client.get_asset_balance('USDT')['free'])
MyAltBalance = sum(AltPricePair.values())


먼저 AltPricePair에 있는 {코인명: 코인의 미국 달러 기준 잔고 금액} 딕셔너리 페어처럼, 비트코인과 미국 달러화도 똑같이 업데이트하여 저장해준다. 그러고선 혼동이 없도록, 이렇게 업데이트된 딕셔너리를 PricePair이라고 다시 이름 지어준다.

AltPricePair.update({'BTC' : MyBTCBalance})
AltPricePair.update({'USDT' : MyUSDTBalance})
PricePair = AltPricePair

 

 

Matplotlib으로 파이 차트 만들기

이제 데이터 전처리가 끝났으니, 자산의 코인별 비중을 시각화 시켜야한다.
PricePair 딕셔너리에 코인명을 담고 있는 키 부분을 keys()를 통해, PriceKey에 리스트 형태로 저장한다. 또한, 코인의 미국 달러 기준 잔고 금액이 담겨있는 벨류 부분은 PriceValue에 values()로 리스트 형태로 저장한다. 그러고선, 다시 PriceValue는 for문으로 소수점 2자리 이하 값으로 반올림을 시켜 이쁘게 만들어준다. 그럼 각각의 PriceKey와 PriceValue가 순서에 맞게 정갈한 리스트를 내놓는다.

PriceKey = PricePair.keys()
PriceValue = PricePair.values()
PriceValue = [round(elem, 2) for elem in PriceValue]
#PriceKey와 PriceValue 리스트
dict_keys(['BNB', 'GAS', 'BAT', 'XRP', 'XLM', 'BTC', 'USDT'])
[0.04, 0.0, 212.14, 219.99, 129.67, 165.62, 0.0]


마지막으로 Matplotlib의 pie()를 통해 플롯해주는데, 그냥 PriceValue만 넣으면 아무 것도 없는 밍밍한 파이 차트가 나온다. 여기에 퍼센테이지를 값과 실제 잔고 금액을 표시하고 싶었다. 퍼센테이지는 autopct에 적당히 설정해주면 되지만, 실제 잔고 금액은 어떻게 넣나 고민이 많았다. 결국 Stackoverflow에 한 함수를 끌어와서, 잘 모르지만 일단 쓰게 됬다.

def make_autopct(values):
    def my_autopct(pct):
        total = sum(values)
        val = int(round(pct*total/100.0))
        return '{p:.2f}%  ({v:d})'.format(p=pct,v=val)
    return my_autopct


이걸 autopct에 넣어주고선, 레전드를 설정해줬더니 투박하지만 제 역할은 하는 파이 차트를 결국 띄울 수 있었다.

Binance 자산의 코인별 비중 현황 파이 차트

 

전체코드

from binance.client import Client
import warnings
warnings.filterwarnings("ignore")
from matplotlib import pyplot as plt

client = Client('A94G89HZGqMF8niazexzsN7Vo8ygzVmvxAFbxtYH6IossrzIAtuPTgoj1k4iAXyq', 'wG18VxuQjGb8F33opQKJQX6EmWMuz0cwZPEh4aYTjxYmOF6NBNasUys5G6sEvOGa', {"verify": False, "timeout": 100})

BTCPrice = float(client.get_klines(symbol='BTCUSDT',
                            interval=client.KLINE_INTERVAL_1MINUTE,
                            limit=1,
                            requests_params={'timeout': 100})[0][4])

AssetBalance = client.get_account()
print(AssetBalance)

AvailableAsset = []
for Asset in AssetBalance['balances']:
    if float(Asset['free']) > 0:
        AvailableAsset.append(Asset)

AltPricePair = {}
for Asset in AvailableAsset:
    PairName = Asset['asset'] + 'BTC'
    try:
        MinuteKlineRaw = client.get_klines(symbol=PairName,
                                           interval=client.KLINE_INTERVAL_1MINUTE,
                                           limit=1,
                                           requests_params={'timeout': 100})
        CurrentPrice = MinuteKlineRaw[0][4]
        AltPricePair[Asset['asset']] = float(CurrentPrice) * float(Asset['free']) * BTCPrice
    except:
        continue



MyBTCBalance = BTCPrice * float(client.get_asset_balance('BTC')['free'])
MyUSDTBalance = float(client.get_asset_balance('USDT')['free'])
MyAltBalance = sum(AltPricePair.values())

AltPricePair.update({'BTC' : MyBTCBalance})
AltPricePair.update({'USDT' : MyUSDTBalance})

PricePair = AltPricePair

PriceKey = PricePair.keys()
PriceValue = PricePair.values()
PriceValue = [round(elem, 2) for elem in PriceValue]

print(PriceKey)
print(PriceValue)

def make_autopct(values):
    def my_autopct(pct):
        total = sum(values)
        val = int(round(pct*total/100.0))
        return '{p:.2f}%  ({v:d})'.format(p=pct,v=val)
    return my_autopct

plt.pie(PriceValue, labels=PriceKey, autopct=make_autopct(PriceValue))
plt.legend(PriceKey)
plt.show()

print(MyAltBalance + MyUSDTBalance + MyBTCBalance)

 

 

배운점

먼저, 코딩도 당분간 두통 때문에 그만둬야겠다. 정말 모니터 앞에 오래 앉아있으면 심한 번아웃이 온다. 블루라이트 때문인가도 싶다. 파이썬의 문법 면에서 배운점은, 딕셔너리는 리스트처럼 + 만으로 합쳐지지 않는다는 것이다. 기존의 딕셔너리에 update()로 새 딕셔너리를 업데이트하여 더해줘야 한다.

마지막으로, 사실 나는 초보자로 데이터 과학을 접하면서 허탈하다는 생각이 종종 든다. 그렇게 데이터 수집과 전처리 과정을 거쳐 내놓은 결과물이 고작 차트와 같은 시각화 자료이다. 물론 데이터 과학에서 시각화는 일부분이고, 무엇보다 시각화된 자료에서 유의미한 상관관계를 찾아 사용자의 의사결정에 도움이 된다면 그 역할을 다한 셈이다. 하지만…개인적으로 짧은 프로젝트를 하면서 내가 데이터 과학이 정말 하고싶은지에 대해 다시 고민하게 된 것 같다.

[오늘의 개발] 워드프레스 설정

워드프레스 설치과정이 개발인가 싶지만, HTML/CSS를 만지작거렸으니 개발이라 퉁치자. 어제 카페24에 워드프레스를 설치한 이후에, 오늘은 도메인 설정을 만져주고 홈페이지를 꾸미는 작업을 했다. 매일 개발이 그렇듯이, 오늘도 삽질 끝에 설정을 끝마쳤다.

워드프레스는 다른 가입형 블로그들과는 많이 다르다.
처음부터 워드프레스는 내 서버에 사이트를 직접 구축해서 돌리는지라, 자율성이 네이버 블로그나 티스토리 같은 서버와는 남다르다. 다른 블로그들은 주어진 환경에서 글만 끄적인다면, 워드프레스는 사용자가 A부터 Z까지 무엇이든 할 수 있다. 하지만, 다른 말로 얘기하자면 그만큼 관리도 힘들고 망칠 확률도 높다는 의미이다.

 

도메인 설정

도메인 주소 설정 오류
먼저, 나는 도메인 보안 프로토콜 설정에서 말아먹었다. 워드프레스를 설치하고나선 접속해보니, 대부분의 브라우저들에서 ‘인증받지 않은 사이트’라는 이름으로 접속 자체를 막아버렸다. 당연한 것이, 애초에 이 사이트는 https 프로토콜을 사용하지도 않은데다가 SSL 인증서도 없었다. 그래서 당연히 도메인 주소를 http://sejoonlim.com에서 https://sejoonlim.com으로 바꾸면 안전하게 연결되 보안 문제가 해결될 것이라 생각했다.

워드프레스 관리자 설정 페이지

그치만 그렇게 편하게 바뀔리가 없다. 도메인 주소명을 바꾸자마자, 406 에러를 띄면서 연결을 거부해버렸다. 내가 내 사이트에 접근권을 잃어버린 것이다. 너무 당황스러웠다. 결국 인터넷을 뒤진 끝에, ftp를 통해 phpMyAdmin을 설치해서 데이터베이스에 접근하는 방법을 알아냈다.

처음에는 php를 만져본 적이 없어서 걱정이 됬지만, GUI 형식으로 되어있어 편하게 설정할 수 있었다. 결국, 다시 http://sejoonlim.com을 통해서 관리자 페이지에 접속할 수 있게 되었다.

Cloudflare을 통한 https 접속 설정
하지만, 여전히 SSL 인증서가 없기 때문에 https 프로토콜을 통해 접속할 수는 없었다. SSL 인증서를 직접 발급받아서 등록하는 방법도 있지만, 비용이 들거나 까다로운 경우가 많아서 꺼려졌다. 그래서 Cloudflare의 CDN을 통해 접속하는 방법을 찾아냈다.

아직 CDN에 대해 잘 알지 못하지만, 내가 이해한 수준은 콘텐츠 서버와 클라이언트 사이에서 중계해주는 역할을 해준다고 알고있다. 즉, 내 사례에서는 CDN은 https로 접속한 클라이언트를 중계하여 http를 접속해, 그 결과를 다시 클라이언트에게 전달해주는 것이다.

CDN 서비스에 대한 이해는 부족했지만, 블로그에 글을 참고해서 사이트에 https를 성공적으로 설정할 수 있었다.

가입형 워드프레스의 도메인 리다이렉트 문제
https를 설정하고선 끝난줄 알았는데, 가끔 브라우저로 접속할 때마다 과거의 가입형 워드프레스로으로 리다이렉트 접속되는 문제가 있었다. 이미 도메인 네임서버를 설치형 워드프레스로 바꿔놓았던지라, 대체 어떻게 그런 일이 생겼는지 의문이 들었다.

다행히 가입형 워드프레스에 24시간 지원 서비스가 포함되어 있어서, 도메인 리다이렉트를 바로 제거할 수 있었다. 다만, 내가 성격이 너무 급해서…브라우저 캐시나 서버 적용시간을 무시한채로 계속 문제가 해결이 안된줄 알고 여기저기 건들이고 다녔다.

 

홈페이지 꾸미기

도메인 관련한 설정을 모두 마친 후에, 홈페이지에 내 프로필을 꾸미기 시작했다. 뭐, 꾸밀 것도 없지만, 나름 폰트나 반응형 CSS 설정을 해서 모바일에도 대응하도록 만들고 싶었다. 기존에는 모바일에서 사진과 문단 규격이 깨지는 현상이 생겨서 더더욱 안예뻤다.

그래도 CSS에 적응했던지라, 이런 설정은 쉽게 할줄 알았다. 근데 정작 워드프레스에서 CSS를 수정하려 하니 뭘 어떻게 해야될지 몰랐다. Style 태그를 삽입하고 CSS 스타일 파일도 건들여보기도 했다. 그렇게 몇시간동안 붙잡고 있다가, 결국 Elementor이라는 플러그인을 쓰니 한번에 해결됬다. 역시 직접 구현하기 힘들때는 툴을 쓰는게 훨씬 마음이 편하다.

 

배운점

처음이고 잘 모른다면, 함부로 설정을 건들지 말아야된다. 세상에는 좋은 가이드라인들이 차고 넘치니, 뭘 하기전에 검색부터 해보는게 답이다. 특히 Stackoverflow에는 없는 정보가 없다. 내가 고민하고 있는 문제는 과거에 누군가는 해결했었고, 그 답들이 대부분 Stackoverflow에 업로드 되어있다.

그리고, 잘 생각해보니 php를 포함한 서버 사이드 언어를 배우는 것도 나쁘지 않다. 대부분 html/css처럼 프론트엔드에 많이 관심을 가지는데, 결국 백엔드에 대한 이해가 있어야 자유자제로 구현이 가능한 것 같다. 물론 나는 Django 이외에 백엔드는 전혀 모르고, html/css도 초보 수준이다. 무엇이 됬든간에, 백엔드에 관한 이해는 크고 작은 일들에 관련해서 웹 서버를 구축하는데 도움이 많이 될 것 같다.

[오늘의 개발] DART 재무제표 공시를 통한 국내 증시 데이터 시각화

데이터 과학에 관심을 가지면서, 증권 데이터를 분석해 직접 퀀트 투자를 해보는 것이 꿈이었다. 특히 문병로 교수나 권용진 퀀트의 책을 보면서, 데이터에 기반해 논리정연한 투자를 하는 모습이 나와 대비되어보였다. 그렇게 계속 꿈에만 그리다가, 파이썬 데이터 분석 패키지에 적응하면서 국내 증권 데이터를 분석해 투자에 적용해보기로 마음을 먹었다.

이를 위해선 다음 세가지 과정을 거쳐야만 했다.
1. 증권 데이터 수집
2. 데이터 전처리 및 계산
3. 데이터 시각화를 통한 투자 참고자료 생성

미리 말해두자면, 나는 데이터 전처리 이후에 각종 투자지표를 계산하기 위해 ‘일자별 시가총액’이나 ‘기간별 발행주식수’ 데이터를 얻지 못해서 실패했다. 이 글은 내가 실패를 깨달기 전까지 진행했던 것들을 정리해둔 프로젝트 기록이다.

 

증권 데이터 수집 

문제점
일단, 국내에서 개인이 증권 데이터를 수집할 수 있는 곳은 한정되어 있다. 대표적으로 금융감독원 DART, 네이버 증권, FnGuide 정도가 데이터를 얻을 수 있는 장소의 전부이다. 그런만큼 문제점들도 많은데, 그중에 2가지 정도를 나열해보고자 한다.

첫째, 일반 투자자를 위한 증권 데이터 부족
투자를 하다보면 과거의 증권 데이터가 필요할 때가 있다. 특히 가격차트로 알 수 없는 재무 데이터의 경우엔 더욱 그렇다. 그럴때면 네이버 증권이나 HTS에서 기본으로 제공하는 FnGuide부터 되고, 실제로 많은 개발자들이 이 부분을 크롤링해서 쓴다.

FnGuide 재무분석 정보

근데, 잘 살펴보면 FnGuide 재무정보에 한계가 보인다. 대표적으로 데이터의 해상도, 분기/연도별 재무 데이터 기간이 각각 1년과 5년에 한정되어 있다. 정말 가볍게 알아보기엔 좋지만, 데이터 분석에 크롤링해 사용하기에는 정보량이 부족하다.

둘째, 금융감독원 DART 오픈 API의 기능 부족
그래서 정확한 장기간의 재무정보를 얻기 위해서는 DART에 분기/연도별 재무제표에서 정보를 얻어야한다. DART는 개발자들을 위해서 오픈 API를 제공하고 있지만, 여기에 또 다른 문제가 있다. API의 기능이 보고서 검색에 한정되어 있다. 즉, 특정 회사의 재무제표를 검색할 수는 있지만 그 재무제표의 수많은 계정항목 당 값들을 알 수가 없다. 사실, 알아낼 방법은 있다. 재무제표의 개별 URL을 따서, 재무상태표와 손익계산서의 표를 크롤링으로 긁어오는 방법이다. 여러 개발 블로그들에서 소개하고 있는 방식들이다. 하지만, 매 종목마다 크롤링하는 방식은 비효율적이기도 하고, 데이터에 오류가 발생할 가능성도 적지 않다.

셋째, 유료 기업정보 제공 서비스의 폭리와 부실함
다 포기하고, 유료 증권 데이터를 구매해서 쓸 생각도 해봤다. 그래서 국내에 KOSCOM, 세종 데이터, 아이투자, FnGuide…모두 찾아봤지만 가격은 한달에 10만원에 가까운데도 불구하고, 기본적인 RESTful API조차 지원하지 않는 모습에 어이가 없었다. 참고로 해외에 IED, SimFin, EODHistoricalData와 같은 회사들은 무료 또는 20달러 선에서 미국 내 상장사의 10년치 펀더멘탈 정보를 제공한다. 이정도 되면 국내 기업정보 제공 업체들의 폭리와 서비스 부실 밖에 탓할 수가 없다.결론적으로, 어떠한 방법을 써서라도 API이나 크롤링으로는 증권 데이터를 받아올 수 없게되었다. 진심으로 이때부터 집에 블룸버그 단말기 한대 두고선, 증권 데이터를 무제한으로 끌어다 쓰는 꿈을 가지게 됬다.해결방안
다행히도, 금융감독원에서 얼마 전부터 CSV형식으로 국내 상장사들의 재무제표를 분기별로 업로드 해두었다. 수많은 개발 블로그들이 DART API를 통해 크롤링하는 방식을 선택한걸 보아, 이 파일이 존재하는지 조차 잘 안알려진 것 같다. 2015년 4분기의 정보부터 제공되어있지만, 기업의 분기별 재무상태표/손익계산서/현금흐름표가 모두 담겨있다. 잘 가공하면 충분히 사용할만한 데이터이다.

 

데이터 전처리 및 계산

데이터 전처리
데이터 수집이 해결된 만큼, raw data를 분석에 사용할 수 있도록 전처리 과정을 거쳐야한다. 여기서 나는 raw data인 CSV 파일을 개별 상장사의 분기별 재무제표들로 구성된 sqlite 파일로 전환하는 것을 목표로 했다. 분명 이렇게 하면 이해가 안될것 같아 설명을 더해본다.

금융감독원에서 업로드한 CSV 파일에는 국내 모든 상장사의 해당 분기의 개별 보고서의 데이터가 모두 들어있다. 다시 말해, ‘2015년_4분기_재무상태표.csv’ 파일에는 국내 모든 상장사들의 2015년 4분기 재무상태표의 데이터가 모조리 담겨있다는 뜻이다. 직접 샘플 데이터를 다운받으면 이해하기 쉬울 것이다.

금융감독원이 제공한 CSV 형식의 재무제표 파일

내가 원하는 것은 하나의 기업 sqlite 파일 아래에 수많은 분기별 개별 보고서 테이블이 있는 것이다. 일례로 ‘AK홀딩스.sqlite’ 아래 ‘AK홀딩스_2015_4Q_BS’, ‘AK홀딩스_2015_4Q_BS_Con’, ‘AK홀딩스_2015_4Q_IS’…으로 이어지는 수많은 테이블들을 만드는 것이다.

데이터 전처리 과정 이후에 개별 기업 Sqlite 파일의 모습

여기서부터 Python을 통해 CSV 파일을 import 해서 개별 기업의 리포트들을 sqlite의 테이블에 저장하는 과정을 진행했다. 사실 소스코드를 공개해 단계별로 소개하고 싶지만, 전혀 주석 정리가 되어있지 않은 스파게티 코드라 소개할 가치조차 없다.

그래도 대략 알고리즘을 설명하자면, CSV 파일을 읽어들여 파일명에 따라 회사명, 분기, 보고서 종류, 연결재무제표 여부를 판독한다. 그리고 읽어들인 CSV 파일을 데이터프레임 형태로 변환한다. 이후에 한 회사명의 sqlite 파일을 생성하고, 그 아래에 분기별 개별 재무제표 데이터프레임을 테이블 형태로 모두 저장한다.

이렇게 말하니 무슨 말인지 잘 이해가 안될 것이다. 나도 지금 코드를 살펴보니 이해가 안된다. 그런데 어째 작동을 한다(…)

그렇게 약 50시간을 갈아넣은 끝에, 모든 상장사들의 2015년 4분기 ~ 2018년 3분기의 재무제표를 데이터베이스화 시키는데 성공했다. 그 파일은 여기서 다운받을 수 있다.

데이터 계산

이제 데이터 전처리가 마무리된 만큼, 기존의 재무정보를 기반으로 새로운 투자지표들의 값을 구하려 했다. 이를 위해서 Alpha Vantage에서 주식 가격을 가져와 데이터프레임으로 리턴하는 함수도 개발하였다. 투자지표를 계산한 후에, 기존 주식 가격 데이터프레임에 합쳐서 PER이나 PBR의 일자별 변동 추이를 살펴보고자 했다.

여기서 내 전체 프로젝트를 무너지게 한 큰 문제점을 깨달게 되었다. 바로 당시 일자의 상장 주식수나 시가총액을 둘다 모른다는 점이었다. PER이나 PBR과 같은 지표들은 주당 000을 따지기 때문에, 상장주식수를 아는 것이 무척이나 중요하다. 그래야 Earning이 됬든 Book이 됬든 간에 상장주식수로 쪼개서 값을 구할 수 있다.

하지만, 우리나라 증권 API나 사이트 어디에도 상장주식수의 변동을 시계열 데이터로 일목요연하게 다룬 곳은 없었다. 하다못해 일자별 시가총액 데이터만 있었어도 계산(일별 시가총액 / 일별 종가 = 일별 상장주식수)이 가능했겠지만, 그런 곳은 없었다.

결국 여기서 내 전체 프로젝트가 멈추게 되었다. 약 70시간 동안 잠을 줄여가며 개발을 했지만, 허무한 문제에서 포기할 수 밖에 없었다. 그래도 무슨 감정이었는지, 프로젝트를 날렸는데도 화가 나기보다는 담담했다.

 

배운점

먼저, Pandas 데이터프레임 사용법이 엄청 늘었다. 하루에도 수십번 StackOverflow에서 데이터프레임 관련 자료들을 찾아본 것 같다. 그래서인지 이제는 데이터프레임 자료 검색이나 인덱스 리스팅과 같은 사소한 부분에서 막히지는 않는 듯 하다. 그리고 알고리즘, 특히 조건문 처리를 더 효율적으로 짤 수 있게된 것 같다. 예상치 못한 결과인데, 워낙 조건문이나 예외처리를 많이 하다보니 자연스래 늘었다. 마지막으로 Sqlite를 접하다보니, SQL 쿼리문에 대해서도 배우게 되었다. 나중에 Django ORM 쿼리에 꽤 잘 써먹을 수 있을 듯하다.

그리고 개인적으로 배운 것이라면, 노력만 한다고 일이 잘 될리가 없다. 아무리 철저하게 계획해도 설계 미스로 훅가는건 한순간이다. 그저 과정에서 많은 것들을 배워간다는 자세로 마음을 비우면서, 현재 하고 있는 일들이 잘되기를 바라는게 정신건강에 이로운 것 같다.