/Quant_Project

퀀트 주식투자 프로젝트 입니다

Primary LanguagePython

Quant_Project

image

1. 프로젝트 개요

1.1. 프로젝트 배경 및 목적

주식에 대한 관심도가 높아지고 주식시장에 돈이 쏠림에 따라 기업의 가치를 파악해 투자하는 것이 중요하다. 재무제표를 어떻게 분석하는지에 따라 기업의 미래 성장 가능성과 현재의 가치를 다르게 나타내기 때문에 재무제표 해석을 하기 위한 서비스가 필요하다고 느꼈다. 따라서 주식에 관심을 가지는 사람들에게 원하는 기업의 재무제표 정보를 제공, 분석하는 방법을 서비스하려고 한다. 그리고 ‘벤저민 그레이엄’, ‘워렌버핏’ 두 사람의 사고를 바탕으로 재무제표의 금액을 재평가하는 수학, 통계 기반의 Quant 프로그램을 만들어 객관적이고 합리적인 투자를 원하는 사람들에게 도움이 되기 위해 웹 서비스 형태로 프로그램을 제작해 배포하려고 한다.


1.2. 개발환경 및 언어

[구동 서버] : AWS Lightsail(2 GB Memory, 1 Core Processor, 60 GB SSD Disk, 3 TB Transfer*), Ubuntu 20.04 LTS

[API 서버] : Python=3.9.0, Django=3.2.8, DRF(Django Rest Framework)=3.12.4

[데이터베이스] : Postgresql:9.6.23

[웹 서버] : Nginx:1.21.1

[컨테이너, 배포] : docker-compose v3.3, Docker swarm service

[프론트엔드] : React v17.0.2, Redux v4.1.1


2. 프로젝트 내용

2.1. 시스템 구성도

2.1.1. 프론트엔드 구성도

image

프론트엔드 프레임 워크인 React와 React의 State 관리를 편리하게 해주는 패키지 Redux를 사용하여 프론트 단을 구성하였다. 유저가 서버에 데이터를 요청하면 AJAX로 처리하여 데이터를 받아오고 서버에서 전달받은 Response의 Data를 Redux의 Reducer 기능을 통해 Store(State를 저장하는 하나의 공간)에 저장, 삭제, 변경 처리를 한다. Store의 State와 연결되어 사용자에게 보여지는 컴포넌트는 새로운 State를 받아 서버에서 받은 데이터에 맞춰서 리렌더링 하도록 만들었다. 

2.1.2. 백엔드/서버 구성도

image

AWS 클라우드 서버 내부에서 Docker를 사용해 Nginx 웹 서버, Django, Postgresql Database 컨테이너를 연결해 기본 서비스 구조를 구성했다. 서버-클라이언트 간 통신은 SSL 인증서를 사용한 HTTPS 통신을 사용한다. 이 과정에서 Nginx가 사용된다. Nginx는 Reverse Proxy Server로서 Client의 Request와 Server의 Response를 중개하는 서버로 동작한다. WAS(Web Application Server)에 속하는 데이터베이스는 Postgresql을 사용했다. Django 프레임워크에서 사용을 권장하는 데이터베이스이고, Django와 어울리는 객체 관계형 데이터베이스이기 때문에 사용하였다. Django 서버를 크롤링 서버와 홈페이지 서버로 나누어 크롤링 중에도 안정적인 서버 운영이 가능하도록 컨테이너를 분리했다. 분리된 컨테이너는 Nginx를 통해서 로드밸런싱 된다. Docker stack을 사용해 전체 시스템을 배포했다. 컨테이너를 일종의 서비스로 격상시켜 지속적인 모니터링과 동적인 조치를 위해 docker-compose 대신 docker-stack을 사용하게 되었다. 모니터링은 sentry.io를 사용한다.

2.2. 주요 기능

2.2.1. 로그인 및 회원가입

image

JWT 토큰을 사용한 인증을 기반으로 로그인 및 회원가입을 구현했다. JWT는 Json Web Token의 약자로 모바일이나 웹의 사용자 인증을 위해 사용하는 암호화된 토큰을 의미한다. JWT 정보를 request에 담아 사용자의 정보 열람, 수정 등 개인적인 작업들을 수행할 수 있다. JWT를 도입한 이유로는 프론트엔드와 백엔드의 완전한 분리를 위해서 도입하게 되었다. Django의 세션기반 로그인을 사용하기 위해서는 Django Template을 사용해야하는데 이는 프론트엔드 개발인원들이 반드시 Django의 문법을 어느정도 이해해야만 페이지를 구성할 수 있다. 하지만 JWT를 사용한다면 클라이언트와 서버의 완전한 분리에 더불어 유저의 로그인 상태를 브라우저에서 간단하게 저장할 수 있어 서버의 부담이 적다는 장점이 있기에 JWT를 사용하기로 하였다.

추가적으로 JWT를 도입함으로서 발생할 수 있는 보안 문제도 예방하는 작업을 하였다. 대표적인 문제로 XSS, CSRF 공격이 있다. 먼저 XSS는 Cross Site Scripting 의 약자로 Code Injection Attack이라고도 한다. 이는 공격자가 의도하는 악의적인 js 코드를 목표 웹 브라우저에서 실행시키는 것으로 요약할 수 있다. 두번째로 CSRF는 Cross Site Request Forgery의 약자로 정상적인 request를 가로채 피해자인 척 하고 백엔드 서버에 변조된 requesst를 보내 악의적인 동작을 수행하는 공격을 뜻한다. 예를 들어 피해자의 정보 수정, 정보 삭제, 무단 열람 등의 공격이 있을 수 있다. 특히 대표적인 예로, 내가 작성하지 않은 해로운 글이 특정 사이트에 게시되는 경우가 있다.

image

위와 같은 문제를 예방하기위해 우리는 JWT 토큰의 만료 시간을 5분으로 설정하고, refresh 토큰을 추가적으로 생성하는 방법을 사용하였다. Refresh 토큰을 httpOnly 쿠키로 설정하고, url이 새로고침 될 때마다 refresh 토큰을 가지고 새로운 JWT 토큰을 요청한다. 발급 받은 JWT 토큰을 js내의 private 변수에 저장한다. 이러한 방식은 refresh 토큰이 CSRF에 의해 사용된다 하더라도 공격자가 새로운 JWT 토큰을 알 수 없기 때문에 안전하고, refresh 토큰이 httpOnly 옵션의 쿠키에 있기 때문에 XSS 공격으로부터 안전하게 된다. 회원가입 방식으로는 1. 사이트 자체에서 가입 2. 구글 로그인을 통한 가입 3. 카카오 로그인을 통한 가입 총 3가지 방법이 있다. 사이트 자체 가입은 원하는 ID와 비빌번호, 이메일을 설정해서 가입할 수 있다.

image

구글, 카카오 같은 소셜로그인은 OAuth2.0을 사용해서 로그인을 진행한다. 사용한 OAuth Flow는 위 그림과 같다.

image

아이디 찾기, 비밀번호 찾기 기능은 일반 회원가입 유저만 사용 가능하다. 아이디는 가입시 제출했던 이메일을 통해서 찾을 수 있다. 비밀번호 찾기 과정은 회원의 아이디와 이메일을 입력하면 인증코드를 발송하게 되고, 이메일로 받은 인증코드를 입력해 인증을 마무리하면 새로운 비밀번호를 입력할 수 있다.

2.2.2. 재무제표 크롤링

image

현재 제작중인 웹은 상장회사의 데이터만 쓸 수 있도록 기획했기 때문에 먼저 웹 스크래핑을 통해 KRX(한국 거래소)에 존재하는 상장회사의 단축코드를 모두 크롤링한다. 그 후 OpenDart에서 제공하는 OpenApi를 이용해 OpenDart에서 key값으로 사용해야하는 상장회사의 고유번호를 가져온다. 가져온 상장회사의 고유번호를 이용해서 상장기업의 존재하는 기업정보와 존재하는 재무제표를 전부 크롤링하였다. 실제적으로 Opendart가 가지고 있는데 데이터가 최소 2015년부터 이기 때문에 그 해 이후의 데이터를 모두 가져온다. 매 3개월 마다 거의 모든 기업들이 이전 분기의 재무제표를 Dart에 제출하거나 정정한 재무제표를 제출하기 때문에 한번 크롤링하고 끝인 코드를 설계하는 것이 아닌 중복 데이터를 받지 않고 바뀐 데이터를 수정하는 로직을 설계했다. 또 기업마다 사업보고서만 내거나 6개월 마다 보고서를 제출하는 경우도 있어서 없는 재무제표의 경우 빈 값만 넣어 두는 조치를 취하였다. 매일 바뀌는 주가의 경우는 pykrx라이브러리를 이용해서 krx웹 스크래핑을 하여 데이터 베이스에 저장하도록 했다.

image

위 그림은 크롤링을 편하게 하기 위해 관리자 계정으로 로그인을 할 경우 프로필 페이지에서 버튼을 클릭하면 서버에 원하는 데이터를 크롤링을 할 수 있도록 요청할 수 있게 제작하였다.

2.2.3. 재무제표 연산

image

워렌버핏은 재무 상태표에서 실제 가치를 장부에 적힌 숫자와 다르게 판단했다. 즉, 유동자산과 비유동자산의 가치를 다르게 책정했는데 이 아이디어를 가져와 웹 서비스에 적용을 하였다. ‘연산’ 페이지에서 원하는 기업의 재무제표를 불러와서 모든 계정명에 대해 계수를 임의로 설정해서 원하는 값을 얻어낼 수 있다. 유저는 기업, 연도, 분기, 연결/일반을 선택해서 원하는 재무제표를 불러올 수 있다. 각 계정명에 대한 계수를 수정해 줄 때마다 새로 바뀐 값에 대한 기업가치(자산 – 부채)와, 벤자민 그레이엄이 정한 보수적 기업가치(유동자산 – 부채)를 보여주도록 하였다. 로그인을 하지 않아도 원하는 재무제표를 불러올 수 있다. 하지만 로그인을 하게 된다면 자신이 선택한 재무제표 정보와 임의로 수정한 계정명에 대한 계수까지 개인 프로필에 저장할 수 있는 기능이 추가된다. 저장을 할 때 자신이 원하는 제목으로 저장할 수 있다.

2.2.4. 주가 그래프

image

원하는 기업에 대한 주가 그래프를 확인할 수 있다. 크롤링을 통해 저장한 주가를 [시간, 종가] 배열로 묶어 DB로부터 모두 가져온다. 그리고 HighChart 라이브러리를 이용해서 웹에 보여준다. 최대 4개의 기업까지 한 차트에 동시에 불러와 비교할 수 있다. 그리고 자신이 원하는 구간을 선택해서 해당 구간의 주가 그래프를 확인할 수 있다.

2.2.5. 순위 나열

image

실질적으로 퀀트 투자에 필요한 부분이다. 재무제표에 저장된 데이터를 이용해 만든 지표들, 예를 들어 부채 비율, ROE, ROA, PER, PBR과 같은 지표들을 크롤링할 때 하나의 모델(하나의 재무제표)마다 각각 지표를 구해 저장해 놓았기 때문에 사용자가 조건을 정하면 바로 표로 보여줄 수 있게 설계했다. 사용자 조건은 전체 기업에 대해 특정 지표의 n이상, n이하, 상위 n%, 하위 n% 그리고 오름차순 내림차순 순위 조건에 대한 종합순위를 보여주도록 하였다. 그리고 사용자가 고른 조건에 대한 값들도 모두 보여준다. 순위 나열에서 모든 재무제표는 가장 최신의 재무제표만 사용한다. 재무제표의 양이 매우 많기 때문에 Pandas DataFrame을 사용해서 데이터를 정제하였다.

3. 향후 목표 및 유지보수 계획

o 정보 공유 게시판 활성화 o 투자 기법 및 설명 추가 o PCR, PSR 지표 추가 o 개인 프로필 커스텀 기능 추가 o 재무제표 크롤링 안정화 o 재무제표 수정 기능 추가

박시형 조현우 허상원
Front-End Back-End UI/UX