: 국내에서 주식 Data를 Stream 형식으로 실시간 받아보기 위해서는 증권사에서 제공하는 API를 이용하는게 일반적이다. Open API를 제공하는 증권회사는 키움, 대신, 하나는 기억이.. 아무튼 이렇게 3개로 알고 있다. 각 증권회사의 API마다 requst per seconds 라든지, API Client를 실행하는 방식이 조금씩 다르기 때문에 장단점이 있지만, 사실 국내 증권사의 Open API는 해외 증권사와 다르게 큰 차이가 없는 것 같다. 

 

아무튼, Open API를 이용할 때, 누구나 한 번 쯤은 겪을 만한 일들을 간단히 정리하고, 해결하는 방법을 적어보고자 한다. 

 

1. Open API 지원 환경

: 현재 기준(20.10.28)으로 키움 증권, 대신 증권 모두, Python 환경에서 Open API를 이용하기 위해서는 Python 32bit 버전이 설치되어 있어야 한다. 이 후, 대신 증권 같은 경우에는 win32com Library도 32bit 버전이 필요하고, 키움 증권도 PyQT 버전이라든지 추가적으로 Library에 대한 버전도 맞춰야 한다. 하지만, 이러한 Library는 쉽게 지우고 설치할 수 있지만, Python 버전을 지웠다 설치했다가 하기에는 무척 번거롭다.

 

보통 Python을 자주 사용하는 사람들은 현재 3.7 또는 3.8 버전에 64bit를 이용하고 있기 때문에, 위와 같은 경우에는 분명히 32bit 환경이 따로 필요한 상황일 것이다. 

 

하지만, 걱정할 필요 없다. Python Conda에서 제공해주는 가상환경 생성 method를 이용하면 내가 원하는 독립된 새로운 Python 환경을 생성 및 삭제할 수 있기 때문이다. 당연하겠지만, 가상 환경이라고 해도 Memory Resource는 기존 Base Python과 마찬가지로 필요하기 때문에, 동시에 실행할 경우에는 Compute Resource의 여유 자원이 있어야 한다.

 

1) 먼저 Conda로 설치할 수 있는 Python Version들을 확인해본다. 

conda search python

Anaconda를 설치했으면, Anaconda Prompt를 실행하여, conda search python을 입력하면, 현재 설치 가능한 python 버전 목록들이 나온다. 현재 기준으로는 py3.7이 가장 최적화되어 있는 버전이기 때문에, py3.7 버전을 선택해서 생성해보도록 할 예정이다.

conda search python

 

 

2) 가상환경 Python 버전 설치

set CONDA_FORCE_32BIT=1
conda create -n py37_32 python=3.7.9 anaconda

우리가 필요한 버전은 python 32bit 버전이다. 만약, set CONDA_FORCE_32BIT=1로 Setting을 하지 않는다면, Default로 설정되어있는 64bit 버전이 설치될 것이다. 그런 일이 발생하지 않도록 꼭 설치하기 전에 Setting을 하도록 하자.

 

다음, conda create -n <가상환경 이름> python=<version> anaconda 명령어를 실행하여, 원하는 python version을 설치하도록 한다. 여기서, 명령어 맨 마지막에 있는 anaconda가 의미하는 것은, 가상환경이 설치될 때, 함께 설치할 기본 Library를 의미한다. anaconda라고 입력할 경우에는 anaconda에서 제공하는 기본 Library들이 함께 설치된다. 만약, 가상환경에서 모든 Library가 아니라, numpy와 pandas만 필요하다고 할 경우에는 conda create -n <가상환경 이름> python=<version> numpy pandas 이런 방식으로 필요한 Library만 입력해서 설치할 수도 있다.

 

이 후, 설치가 정상적으로 진행된다면 다음과 같은 메세지를 볼 수가 있다.

설치 이후 메세지

 

3) 가상환경 Python 변환

conda activate py37_32

위의 명령어를 시행하면, base(기존 환경)에서 우리가 방금 설치한 py37_32 환경으로 변경해준다. 그리고, 다음과 같이 변경된 사항을 확인해 볼 수 있다.

python 환경 변경

위와 같이 (base)와 (py37_32)으로 변환이 잘 되는 것을 확인했으면, 가상 환경 설치가 잘 되었다고 볼 수 있다. 그리고 만약 기존의 base 버전으로 돌아가고 싶다면 다음 명령어를 실행하면 된다.

conda deactivate

base 환경으로 변경

뭔가 길게 적어놔서 복잡해 보이지만, 무척 간단하게 가상환경을 설치하고 연동했다가 해제할 수도 있다.

 

 

4) 가상환경 Python과 Jupyter Lab 연동

 

이제 가상환경은 설치가 되었으니, 코딩 작업을 해야하는데 본인은 Jupyter Lab을 자주 이용하기 때문에 가상환경과 Jupyter Lab Kernel을 연동하는 과정을 소개하고자 한다.

 

먼저, 위에서 생성한 가상환경(py37_32)에 Jupyternotebook, jupyterlab, ipykernel library가 설치되어 있는지 확인해야한다. 만약, 위의 library가 설치되어 있지 않다면, pip install로 해당 환경(py37_32)에서 설치하고 진행해야 한다.

ipykernel, jupyter library

 

모든 준비가 끝났다면 다음 명령어를 실행한다.

python -m ipykernel install --user --name py37_32 --display-name "py37_32"

위의 명령어는 앞서 생성한 py37_32라는 가상환경을 Jupyter Lab에는 "py37_32"라는 이름으로 보여지도록 연동을 하겠다는 의미이다. 만약, 위와 같이 명령어를 실행하지 않으면 죽어다 깨나도 jupyter lab에서 py37_32라는 새로운 kenenl을 알 수 없다. 

 

그렇다면 잘 연결되었는지 확인해보자.

 

Jupyter Lab에서 가상 환경 커널이 연동 가능한 것을 보면 잘 연결된 것 처럼 보인다. 그러면 실제로 실행하면 잘 돌아갈까? 아마 정상적으로 실행이되는 사람들도 있겠지만, 새로운 환경의 Kernel로 연결될 때, Kernel이 죽었다는 Error Message를 보는 사람도 많을 것이다.

 

위와 같은 Error가 발생할 경우에는 다음과 같은 조치를 취한다.

pip uninstall pyzmq
pip install pyzmq

pyzmq library를 지웠다가 새로 설치한다. 처음에 본인도 반신반의했지만, 위와 같이 하니깐 잘 실행된다.. 실제로 삭제하고 새롭게 설치된 pyzmq 버전 모두 동일한 것으로 확인했는데, anaconda 설치 시, 자동으로 설치되는 pyzmq와 repository에서 직접 호출해서 설치하는 것과 뭔지는 모르겠지만, 다른 부분이 있나보다..

 

 

5) 가상환경 & Jupyter Lab 연동 해제 및 가상환경 삭제

 

가상환경의 목적이 달성되면, 자원 확보 차원에서도 삭제를 하는게 좋다. 삭제하는 방법도 무척 간단하다.

jupyter kernelspec list
jupyter kernelspec uninstall py37_32

위와 같이 list 명령어로 현재 jupyter lab에 연동된 kernel list를 확인하고, 불필요한 가상환경 연동을 해제할 수 있다. 그리고, 실제 물리적 가상환경을 제거하는 방법은 다음과 같다.

 

conda remove --name py37_32 --all

이로써, py37_32 가상환경은 목적을 달성하고, 삭제가 된다. 

 

 

아무튼! 결과적으로 Open API 연동을 위한 새로운 python 32bit 환경을 만들어 보았고, 필요한 사람들이 있을 것 같아 정리해보았다. 

 

 

: 지금까지 언급했던 보조 지표는 모두 가격과 연관되어 있었다. 가격 Data에서 추세와 시장 강도를 예측하는 것도 중요하지만, 거래량에서 얻을 수 있는 정보도 중요하긴 마찬가지다. 거래량은 가격과 함께 투자자들의 시장 심리를 반영한 흔적이기 때문에 미래의 가격을 예측하는데 도움이 될 수 있다. 

 

보통 거래량도 추세를 보인다. 거래량의 급등이 시장 가격의 추세 방향을 결정하지는 않지만, 추세의 급격한 변화를 암시하고 있기 때문에, 거래량이 증가하는 추세를 보인다면 분명 조만간 가격 추세에 대한 변화가 발생할 수 있을 것이라고 생각하고 대비를 할 수 있다.

 

1. On Balance Volume (OBV)

: OBV는 "거래량은 가격보다 선행한다"라는 관점에서 시작한 지표로 가격의 추세를 반영한 거래량의 흐름이라고 보면 된다. OBV의 계산은 단순하다. 가격이 상승하는 날에는 그 날의 거래량을 더해주고, 하락하는 날에는 거래량을 빼주면 된다. 이렇게, 누적된 거래량이 OBV이다.

 

초기 OBV = 0

가격 상승 : OBV = OBV + 당일 거래량

가격 하락 : OBV = OBV - 당일 거래량

 

OBV를 해석하는 방법은 무척 다양하다. OBV가 증가한다는 것은 그만큼 가격 상승 구간이 많다는 것이고, 반면에 하락한다는 것은 가격의 하락 구간이 빈번하다는 의미이기 때문이다. 그래서 보통 OBV는 변동폭이 작은 구간에서 매집 또는 분산에 대한 판단 지표로 사용한다. OBV가 상승한다면, 매집이 이루어지고 있다는 의미이고, 반대로 하락한다면 물량이 분산되고 있다고 판단한다.

위의 그래프를 보면, OBV의 지속적인 상승(매집) 이후, 급격한 가격 상승이 이루어졌다. 이렇게 일반적으로 OBV로 세력의 매집 Signal을 판단한 뒤, 가격 상승에 대한 기대로 매매를 하는 기법이다. 보통 매집도 세력의 공격적인 매집이고 방어적인 매집이 있기 마련인데, 공격적인 매집은 위와 같이 OBV의 상승이 보이지만, 방어적인 매집은 가격 하락에 따라 나오는 개인 물량을 매집하기 때문에 OBV에서 발견하기 어려운 부분도 있다.

 

일단 이 OBV를 학습 Data에 추가하기에는 애매한 부분들이 많이 있다. 물론 Volume에 대해 Transformation을 취하여 Scaling을 할 수 있지만, 개인적으로 이 OBV가 현재 시점에 대해 가격에 대한 예측을 하는데 있어서 영향력이 있는지가 궁금하다. OBV의 가장 큰 단점이라고 생각하는 부분은 계산식이다. 단순하게 가격의 방향에 따라 그 날 모든 거래량을 더하고 빼는 연산식이 마음에 들지 않을뿐이다. 그 이유는 실제로 당일 대부분의 거래가 매도세 우세로 형성된 거래량이라고 할 지라도, 종가 시점에서 급격하게 매수세가 치고 올라오면 해당 거래량은 모두 +가 된다는 점이다. 우리가 알고 싶은 것은 당일 거래량 중에서 매도세 우세에 대한 거래량 비중과 매수세 우세에 대한 거래량 비중임에도 말이다. 

 

이 때문에 OBV는 학습 Data에 추가하지 않는 방향으로 갈 예정이다.

 

 

2. Relative Strength Index (RSI)

: RSI는 상대 강도 지수라고 하며, 가격의 상승세와 하락세 간의 상대적인 강도를 나타내는 지표이다. 보통 RSI는 0~1 사이의 값을 가지며, 0.5 일 경우에는 상승세와 하락세가 균형을 이룬다고 볼 수 있다. 0.5보다 클 경우에는 상승세가 우세하다고 보며, 0.5보다 작을 경우에는 하락세가 우세하다고 판단하여 가격의 하락 추세를 가늠할 수 있다.

 

RSI는 오래된 지표라 현재 시장 반영이 잘 안된다는 사람들도 있지만, 여전히 많은 사람들이 유효하게 판단하는 지표이며, 이번 글에서는 거래량과 관련된 지표만 설명한다고 했음에도, 추가해보았다. RSI의 계산 공식은 조금 복잡하다.

 

1) Up = 전날 종가보다 당일 종가가 상승했을 때, 종가의 상승 폭

2) Down = 전날 종가보다 당일 종가가 하락했을 때, 종가의 하락 폭

3) AU = 일정 기간(n)에 대한 Up의 평균 

4) AD = 일정 기간(n)에 대한 Down의 평균 

5) RS = AU / AD

6) RSI = AU / (AU + AD) = RS / (1 + RS)

7) RSI Signal = RSI의 Moving Average

위의 공식만 보면, AU와 AD에 대해서 잘 이해가 안될 수도 있기 때문에, 예시를 들어 AU와 AD를 직접 구해보았다. 즉, AU와 AD는 동일한 일정 기간 동안 각각 상승 또는 하락이 발생 일자에 상승분을 합하고 나머지는 0으로 간주하여 평균을 낸 값이다.

 

계산식을 살펴보면, RSI는 일정 기간 동안의 AU와 AD의 총 변동폭 대비 상승폭(AU)에 대한 비중을 의미한다. 그리고 AU와 AD는 완전하진 않지만, 날짜에 서로 종속되어있기 때문에 일반적으로 AU가 커지면 AD는 작아지는 반비례 관계를 갖고 있다. 물론 상승 또는 하락폭이 클 경우에는 이러한 관계를 따르지 않는다. 그리고 다른 보조 지표들과 마찬가지로 RSI의 Signal은 RSI의 이동평균선을 의미한다. 

 

직관적으로 RSI를 해석해보자. RSI는 AU / (AU + DU) 이므로, 일정 기간 동안의 상승 및 하락폭에 대한 상승폭의 비중이다. 그렇다면 처음 언급했듯이, RSI가 0.5인 경우에는 정말로 상승세와 하락세가 균형을 이루고 있다고 할 수 있을까?

 

AU를 상승세, DU를 하락세로 간주한다면 그렇다고 볼 수 있다. 왜나하면, AU = DU일 경우에는 RSI 가 0.5이기 때문이다. 그리고 AU(상승세) > AD(하락세)일 경우, RSI는 항상 0.5 이상의 값을 가지며, 반대로 AU(상승세) < AD(하락세)일 경우에는 0.5보다 작은 값을 갖는다.

 

RSI 지표를 단순하게 해석해본다면 다음과 같을 것이다. 

1) RSI가 0.5 근방일 경우에는 매수세와 매도세가 균형을 이루고 있다.

2) RSI가 0.7 이상일 경우에는 초과 매수에 진입하여, 가격 하락에 대비를 해야한다.

3) RSI가 0.3 이하일 경우에는 초과 매도에 진입하여, 가격 상승을 기대할 수 있다.

4) RSI가 RSI Signal을 상향 돌파할 경우, 단기 매수세가 강하다.

5) RSI가 RSI Signal을 하향 돌파할 경우, 단기 매도세가 강하다.

 

해석하는 방법은 다양하지만, 해석하기 전에 RSI 지표가 어떤 계산식으로 산출이 되었는지 이해하고 있다면, 좀 더 예리한 판단을 할 수 있지 않을까 생각해본다. 

위의 그래프는 RSI 지표를 적용해본 결과이다. RSI(주황색)와 RSI Signal(녹색)이 0.5를 기준으로 반복해서 움직이는 양상을 확인할 수 있다. 이러한 변동만으로 높은 가격에 대한 예측률을 기대할 수는 없지만, 가격 차트에서는 볼 수 없었던 매수세와 매도세에 대한 우세를 어느 정도 가늠할 수 있기 때문에, 가격 차트만 볼 때 보다 다음 추세에 대한 예측이 좀 더 수월해질 수 있다. 

 

위의 RSI 차트를 참고하면, 매도세가 우세한 9/22일 근처 구간에서 매수세에 대한 상승이 감지되었기 때문에, 추가적인 가격 하락보다는 상승에 대한 확률이 높기 때문에 매수를 해도 나쁘지 않았을 것 같다. 물론, 이러한 매도세가 우세한 시점에서 매수세가 상승한다고 하더라도, 가격에 대한 상승과 반드시 연결되는 것은 아니지만 반대로 가격 하락에 대한 확률은 가늠할 수 있기 때문에, 괜찮은 보조 지표라고 생각한다.

 

개인적으로, RSI가 다른 보조 지표에 비해 단일 지표로 보기에는 강력한 보조 지표라고 생각되지 않지만, 매수세와 매도세를 가늠할 수 있는 지표이고, 다른 지표와 함께 봤을 경우에는 더 좋은 결과를 기대할 수 있다고 생각하기 때문에 학습 Data에 추가해 볼 예정이다.

 

 

3. Chaikin Money Flow(CMF)

: Chaikin Money Flow(CMF)는 가격과 거래량을 모두 고려한 보조 지표이다. 사실 앞서 언급한 OBV나 RSI 보다 CMF만 보고 매수세와 매도세에 대한 판단을 하는 사람들도 많다. 다양한 보조 지표를 갖고 판단하면 보다 높은 적중률을 기대할 수 있지만, 굳이 성능이 더 좋거나 비슷한 내용(추세, 강도, 모멘텀 등)을 포함하는 보조 지표를 중복해서 볼 필요는 없기 때문이다. 

 

CMF는 거래량에 대한 Moving Average 대비 Accumulation& Distribution(AD)에 대한 Moving Average 비율이다. 뭔가 복잡해 보일 수 있으니, 계산식을 먼저 설명하면 다음과 같다.

 

1) Accumulation& Distribution(AD) =  (( (Close - Low) - (High - Close) ) / (High - Low)) * Volume

2) CMF = (AD의 일정 기간 MA) / (Volume의 일정 기간 MA)

 

위의 계산식을 볼 때, 아래에 그림과 함께 보면 좀 더 이해하기 쉬울 듯 싶어서 추가해보았다. 계산식을 볼 때, 예리한 사람들은 "왜 Close - Low가 앞에 있지?" 라고 생각할 수 있는데, 그 이유는 그림에서 보듯이 Close - Low가 상승세에 대한 비중이기 때문이다. 즉, AD는 가격의 변동 내에서 우세한 추세에 대한 순수한 비중을 거래량에 가중치로 부여한 수치라고 볼 수 있다. 좀 더 쉽게 설명하면, AD는 당일 순수하게 발생한 추세에 해당하는 거래량만을 의미한다.

 

그렇다면 우리는 이제 CMF를 해석해 볼 수 있다. CMF는 전체 거래량에 대한 실제 추세 반영 거래량(AD)이기 때문에 1에서 -1 사이의 값을 가질 수 있고, CMF가 0일 경우, 상승세(매수세)와 하락세(매도세)가 균형을 이루고 있다고 판단할 수 있다. 

 

그리고 CMF가 0보다 클 경우(CMF>0)에는 최근 거래량이 매수세가 우세한 거래량이 이루어졌다고 볼 수 있고, 0보다 작을 경우(CMF<0)에는 최근 거래량이 매도세가 우세했다고 판단할 수 있다. 그 강도는 절대값의 크기가 1에 가까워질수록 세다는 것을 알 수 있다. 

https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/cmf

위의 차트를 보면 CMF가 기준선(CMF=0)을 넘어 증가하면서 상승세가 붙고, 이에 따라 가격도 상승 추세로 흘러가는 것을 확인할 수 있다. 그리고 CMF가 점점 줄어들면서 매수세가 약해지는 것과 함께 가격의 하락을 동반하는 것을 확인 할 수 있다. 즉, CMF는 OBV와 마찬가지로 "거래량은 가격보다 선행한다" 라는 명제를 잘 따르고 있다는 것을 볼 수 있다. 물론, 모든 차트가 그렇다는 것은 아니지만 말이다. 

 

천천히 살펴보면 RSI 지표를 해석하는 것과 유사하다. CMF도 RSI와 마찬가지로 매수세와 매도세를 판단해서 가격 추세를 예측할 수 있다. 다만, CMF는 거래량에 대한 보조 지표이기 때문에, RSI의 흐름과는 다른 양상을 보이기 때문에 매수세&매도세를 판단할 때, CMF와 RSI를 함께 살펴보면 적중률이 높아질 것으로 기대한다. 

 

결론적으로, CMF도 좋은 보조 지표 중 하나로써 판단이되고, 학습 Data에 추가함으로써 학습이 잘 되길 기대해 본다. 이렇게 CMF를 마지막으로 학습 Data에 추가할 보조 지표는 어느 정도 마무리가 된 것 같다. 추후에, 필요할 경우 보조 지표가 추가될 수도 있지만, 당분간은 이번 학습 Data로 자동 매매 프로그램을 만들어보고자 한다.

 

그리고, Rule-Base (조건식) 기준으로 자동 매매하는 프로그램도 만들 예정인데, 이럴 경우에는 보조 지표의 역할이 무척 중요하므로, 그 때 다시 한 번 건드려보지 않을까 생각한다.

: 지난 번과 마찬가지로 학습 Data에 추가할 보조 지표를 알아볼 예정이다. 추후, 학습 Data에 추가할 지표들이 있을 수 있겠지만, 오늘 소개하는 것을 마지막으로 학습 Data에 대한 정리는 마무리하고자 한다.

 

사실 이쯤되니깐, "실제로 학습 Data에 추가적인 보조 지표를 넣었을 경우, 학습이 잘될까?" 라는 의문이 생긴다. 기본적으로 우리가 추출한 보조 지표는 실제로 가격에 대한 예측을 할 때, rule-base(조건식)로 많이 사용한다. 그리고, 단순히 현재 시점만의 정보를 가지고 가격을 예측하는 것 보다는, 보조 지표와 함께 판단을 내려야 정확도가 높은 것도 사실이다. 

 

최근 계속해서 보조 지표에 대해서만 설명을 했기 때문에, 우리들의 진짜 목적이 조금 잊혀졌을 수도 있겠지만, 주 목적은 "Policy Network를 잘 학습시키는 것"이다. 그리고, 일반적으로 사람이 직접 Data를 보고 어느 정도 분석을 할 수 있는 수준이라면, Data 자체만으로도 Insight를 도출할 수 있다는 의미이고, 이는 Neural Network의 학습에도 긍정적인 결과로 연결된다고 볼 수 있다.

 

이렇게 보조 지표를 학습 Data에 추가함으로써, 강화 학습의 성능 향상은 충분히 기대할 수 있고, 이 모든 과정은 Policy Network를 잘 학습시키 위한 목적이라고 보면 될 것 같다.

 

1. Stochastic Oscillator

: Stochastic Oscillator는 가장 많이 사용되는 모멘텀 보조 지표 중 하나로 추세의 변경점을 찾을 때, 유용하며 주로 횡보장이나 박스권에서 적중률이 높다. Stochastic Oscillator는 일정 기간 동안의 가격에 대한 최고가와 최저가 범위 내에서 현재 종가가 위치하고 있는 비율에 대한 지표이다. 만약 시장이 상승 추세에 있다면, 종가는 최고가에 근접하여 가격대가 형성될 것이고, 반대로 하락 추세에 있다면, 종가는 최저가에 근접하여 가격대가 형성될 것이라는 기본적인 이론이 포함되어 있다. 

 

또한, 당일을 포함하여 일정 기간 내의 최고가와 최저가에 대한 당일 종가의 비율이기 때문에, 이 값은 항상 0과 1 사이의 값을 지니게 된다. 그리고 가격과 거래량에 의존하지 않고, 오직 가격 추세에 대한 모멘텀을 나타내기 때문에 가격 추세를 예측하는데 상당히 유용한 보조 지표이다. 

 

일반적으로 이 값이 0.8(또는 80)보다 클 때는 과매수 구간, 0.2(또는 20) 미만일 때는 과매도 구간으로 간주하여 매수/매도에 대한 시점을 정하기도 한다. Stochastic Oscillator을 구하기 위해서는 시장에서 발생되는 Fake Signal을 평활화하는 단계를 거쳐야한다. 먼저, K Line과 D Line으로 구분되는 Stochastic Oscillator의 계산법을 살펴보면서 의미를 파악해보자.

 

Fast K = ( Current Close Price - The Lowest Price During N days ) / (The Lowest Price During N days - The Highest Price During N days )

Slow K = Fast D = ( Current Close Price - The Lowest Price During N days ) MA3 / (The Lowest Price During N days - The Highest Price During N days ) MA3

Slow D = Fast D MA3

 

K와 D는 모두 0~1사이의 값을 가지며, Fast K는 상대적으로 빠르게 반응하는 지표이며, Slow D로 갈 수록 평활화 단계가 진행되기 때문에 덜 민감한 추세선을 나타낸다. Stochastic Oscillator는 시장에 민감하게 반응하는 K Line과 시장 심리가 느리게 반영되는 D line에 대한 Convergence & Divergence를 나타내는 지표로 이해할 수 있다. K Line은 실시간에 가까운 주가 전환에 대한 변화를 확인할 수 있고, 주가 전환에 대한 Noise Signal을 제외한 D Line과 함께 보면서 "진짜 추세 전환"에 대해 가늠할 수 있다. 다음 그래프를 살펴보자.

위의 그래프는 가격 차트이며, 아래는 Fast K Line(파란색)과 Slow D Line(노란색)이다. 그리고, 빨간 선은 0.8 선을 의미하며, 파란 선은 0.2 선을 의미한다. 그리고 D Line이 K Line보다 느리게 반영되는 것도 확인할 수 있다. D Line이 K Line보다 반응이 느리지만, Noise가 많이 제거된 시장 심리에 대한 추세가 잘 반영되어 있기 때문에 가격 추세를 판단할 때, 좋은 기준이 될 수 있다.

 

Stochastic Oscillator에 대한 매수/매도 방법은 개인마다 다르지만, Rough하게 K Line과 D Line이 각각 0.8, 0.2 선에 근접해서 교차할 경우, 매수/매도를 했을 때, 위와 같은 결과를 살펴볼 수 있고, 실제로 수익이 꽤 발생하는 것을 확인할 수 있다. 물론 Stochastic Oscillator 지표만을 가지고 매수, 매도를 하기보다는 다른 보조 지표들과 함께 판단했을 때, 더 높은 적중률을 보여줄 수 있다. 그러나, 본인이 생각했을 때, Stochastic Oscillator는 Bollineger Band(볼린져 밴드)와 같이 강력한 보조 지표 중 하나라고 생각한다.

 

2. Commodity Channel Index (CCI)

: CCI(Commodity Channel Index)는 이름에서 알 수 있듯이, 처음에는 상품 가격에 대한 추세를 알아보기 위해 개발된 보조 지표이다. 이 후, 주식 가격에도 적용함으로써, 추세 변화에 대한 시점을 파악하기 위해 많이 사용되는 모멘텀 보조 지표로 자리 잡아왔다. 

 

CCI는 간단하게 보면, 현재 가격과 Moving Average(MA)에 대한 차이를 보는 지표이다. 일반적으로 현재 가격을 MA와 비교하여, MA보다 클 경우에는 현재 가격이 상승 추세에 대한 힘을 받고 있다고 판단하고, 반대로 MA보다 작을 경우에는 현재 가격이 하락 추세에 대해 힘이 눌리고 있다고 판단한다.

 

또한, 주식 가격이 MA보다 일정 범위 이상 벗어날 경우에는 과매수 또는 과매도 구간으로 간주하기도 하며, 매매 시점으로 활용하기도 한다. CCI에 대한 공식은 다음과 같다.

 

p(t) = (p.low(t) + p.high(t) + p.close(t) ) / 3

MA(p(t)) = p(t)의 Moving Average

MD(p(t)) = |p(t) - MA(p(t))|의 Moving Average

 

* CCI = (p(t) - MA(p(t))) / (0.015 * MD(p(t)))

 

위의 공식에서 분모에 0.015를 곱해준 이유는 CCI의 값을 +100 에서 -100 사이로 조정하기 위한 parameter이다. CCI +100보다 클 수도 있고, -100보다 작을 수도 있다. 그리고, 0.015 말고, 다른 값을 곱해줌으로써 CCI를 해당 가격의 추이에 알맞게 조정하기도 한다. 

 

위의 그래프(위키피디아)를 보면,  CCI는 가격이 증가함에도 -100에서 +100 사이를 움직이는 것을 확인할 수 있다. 또한, CCI 값이 +100선 보다 높거나, -100선 보다 낮을 때 각각 과매수, 과매도로 간주할 수 있다. 과매수는 실제 가격 추이에 비해 과도하게 매수가 이루어져 가격이 형성되었으며, 이는 곧 매도 우세로 이어져 가격의 하락을 암시한다고 볼 수 있다. 마찬가지로, 과매도는 실제 가격 추이에 비해 과하게 매도가 이루어졌기 때문에, 곧 가격 상승이 있을 것이라고 기대할 수 있다.

 

이를 학습 Data에 적용해보았다. 이번 전처리 Data에서 사용하는 학습 Data는 1시간 기준의 차트이기 때문에, 시장 심리를 어느 정도 반영하기 위해, 기간을 일 자(Day) 기준으로 계산할 때보다, 더 넓게 잡아 계산하였다. 그리고, 0.015 대신 다른 값으로 값을 조정하여 다음과 같은 CCI 차트를 구해보았다. 

CCI를 학습 Data에 추가하기 위해, CCI의 범위를 +1에서 -1 사이로 Scaling했다. 위의 CCI를 보면, 실제로 과매도, 과매수 구간을 기점으로 실제로 가격의 추세가 전환이 되는 것을 확인할 수 있다. CCI가 학습 Data에 포함됨으로써, Policy Network의 학습이 잘되길 기대해봐도 좋을 것 같다.

 

 

3. Disparity (이격도)

: Disparity(이격도)는 다른 보조 지표에 비해 계산식이나 개념이 무척 간단하다. 현재 가격과 이동평균선과의 괴리 정도를 보여주는 지표인데, 계산식은 다음과 같다.

 

Disparity = (Close Price / n days Moving Average) * 100

 

이격도가 100을 넘을 경우에는 현재 가격이 이동평균선에 비해 높다는 의미이므로, 현재 가격이 상승 추세에 있다는 의미이고, 반대로 100보다 작을 경우에는 하락 추세에 있다고 볼 수 있다. 너무 간단한 지표라서 많은 설명이 필요하지 않겠지만, 보통 이격도는 단기, 장기로 구분하여 함께 본다. 

 

위의 그래프는 단기(파란색), 장기(노란색) 이격도에 대한 그래프이며, 1로 Scaling한 그래프이다. 보통 기간이 긴 MA 같은 경우 반응이 느리기 때문에, 단기 매매를 할 경우에는 보다 짧은 기간을 적절하게 선택해서 매수&매도를 하는 것이 중요하다. 이격도를 활용하여 매매 시점을 잡을 경우, 보통 이격도가 1보다 클 경우(>1) 매도, 작을 경우(<1)에는 매수를 한다.

 

즉, 올랐을 때 팔고, 내렸을 때 사면 된다. 왜냐하면, 결국 가격은 상승과 하락이 있는 한, 이동평균선에 수렴하기 마련이기 때문이다. 하지만 대부분은 오를 때 사고, 내릴 때 팔기 마련이다.

 

이격도를 학습 Data에 추가하기에는 뭔가 애매한 부분이 있다. 단순한 계산식일 뿐더러, 이미 Moving Average는 학습 Data에 추가가 되어있기 때문이다. 아무래도, 이격도는 이번 학습 Data에서는 제외하는 방향으로 가야할 것 같다.

 

 

4. Bollinger Band (볼린져 밴드)

: Bollinger Band는 시장 가격 추세에 대한 전환점과 강도를 확인하는데 가장 많이 사용하는 보조 지표 중 하나이다. Bollinger Band에 대한 자세한 설명은 이미 많은 사람들이 적어놓았기 때문에 특징과 계산식만 간단하게 언급하고자 한다. Bollinger Band는 이동평균선을 기준으로, 동일 기간 이동평균선에 대한 Standard Deviation(표준편차)도 함께 구한다음, 2 sigma 수준에 해당하는 신뢰구간을 구한다. 우리는 이 신뢰 구간에 대한 하나의 띠를 Bollinger Band라고 부른다.

 

통계학을 공부한 사람은 알겠지만, 2 sigma는 95% 수준의 신뢰구간을 의미한다. (3 sigma는 99.7% 이다.) 예를 들어 2 sigma(95%)수준의 신뢰구간이라고 했을 때, 이것이 의미하는 바는 다음과 같다. 임의로 100 번의 이동평균을 산출했을 때, 실제로 신뢰구간에 들어오는 이동평균은 95개 정도이고, 나머지는 이 구간을 벗어난 값을 보일 것이라는 의미이다.

 

간단하게 요약하면, 실제 가격이 Bollinger Band 구간을 넘길 확률은 상당히 낮고, 이를 넘길 경우에는 추세의 급격한 변화가 발생했다고 간주할 수 있다는 것이다. 실제로 대부분의 주식 가격의 추세는 Bollinger Band 구간을 크게 벗어나지 않기 때문에 예측 범위를 설정할 수 있고, 이를 이동평균선과 함께 볼 경우에는 보다 높은 적중률을 기대할 수 있다. Bollinger Band의 계산식은 다음과 같다.

 

MA = n days Moving Average

sd = n Days Standard Deviation

Upper Bound = MA + 2 * sd

Lower Bound = MA - 2 * sd

 

위의 식에서 2를 곱한 것은 보통 2 sigma를 기준으로 보기 때문이다. 그리고 다음은 학습 Data에 적용해본 결과이다. 

빨간 동그라미를 친 부분은 Bollinger Band의 경계선을 넘어갔을 때의 가격 구간이다. 차트를 보면 실제로 대부분의 가격 추세는 Bollinger Band 안에서 움직이고 있으며, 경계선을 넘었을 경우에는 대부분 추세 전환이 일어나는 것을 확인할 수 있다. 물론 100% 정확한 보조 지표는 없지만, Bollinger Band는 가격의 추세 전환 및 시장 강도를 가늠하는데 강력한 보조 지표 중 하나라고 볼 수 있다. 

 

학습 Data에서는 Normalization을 진행한 가격에 대한 Bollinger Band의 Upper Bound, MA, Lower Bound를 추가할 예정이다. 

 

 

후... 이번 글을 마지막으로 전처리에 대한 부분은 마무리 지으려고 했지만, 아직 거래량과 관련된 보조 지표가 몇 개 남아있다. 글이 너무 길어지면 지루해질 수 있으므로, 다음 글에서는 정말로 마무리 짓고 본격적으로 강화 학습 모델을 건드려 보도록 하겠다.

 

: 이번에는 가장 많이 사용하는 보조 지표에 대해서 간략하게 정리해보고자 한다. 대부분의 보조 지표를 이번 기회에 알게 되었는데, 공부하면서 상당히 유용하다고 생각되는 지표들이 많이 있었다. 물론 결과론적인 접근이 대부분이지만, 확실히 주식 차트에는 투자자들의 심리가 반영되었기 때문에, 과거와 비슷한 이벤트가 다시 반복적으로 발생할 확률이 상당히 높은 것 같다. 이러한 보조 지표를 잘 활용하면, 가격의 추세를 예측하는데 좀 더 도움이 될 수 있을 것이라고 생각한다.

 

일반적으로 보조 지표의 종류는 추세 지표, 변동성 지표, 모멘텀 지표 그리고 시장강도 지표로 구분된다.

 

1) 추세지표란 가격이 진행하는 방향, 즉 가격에 대한 추세를 알아보는 지표이다. 이전 글에서 언급한 이동평균선(MA)을 포함해서, MACD, ROC, DMI & ADX 등이 대표적인 추세 지표이다. 그리고 단순히 추세 지표만을 가지고 미래 가격에 대한 예측을 하는 것은 위험한 행동이다. 추세 지표는 과거부터 현재에 대한 추세를 의미하는 것이지, 미래에도 지속적으로 이러한 추세가 유효할지는 알 수 없기 때문이다. 그렇기 때문에 다음과 같은 지표들을 함께 고려해야 한다.

 

2) 변동성 지표란 가격에 대한 변동성, 즉 얼마나 빠르게 상승 또는 하락을 할 것인지 알아보는 지표이다. 변동성 지표는 보통 짧은 기간 동안 주가의 등락 폭이 어떻게 변할지에 대해 초점을 맞춘다. 이러한 특성을 이용하여, 매매 타이밍을 잡는데 유용하고 단기매매에서도 자주 활용된다. 대표적인 변동성 지표로는 볼린저밴드(Bollinger Band), Envelope 등이 있다. 

 

3) 모멘텀 지표는 투자 심리나 현재 추세에 대한 유효성을 확인하는 지표로 많이 사용된다. 즉, 현재 가격이 상승 추세를 보이고 있는데, 이러한 상승 추세에 대한 "힘"이 아직 붙어있으니 앞으로 추가적인 상승을 보일 것인지, 아니면 "힘"이 없으니 하락으로 전환을 할 것인지 판단할 수 있다. 대표적인 모멘텀 지표는 Monetum, Stochastic, CCI, MACD Oscillator 등이 있다. 

 

4) 시장강도 지표는 모멘텀 지표와 유사하게 추세나 변동성에 대한 강도를 나타내는 지표이다. 그러나, 모멘텀 지표와는 다르게 주로 거래량을 포함시켜 가격을 예측하는 것이 특징이다. 대표적인 예로는 거래량에 대한 MA, OBV, RSI 등이 있다. 

 

이 외에도 정말 많은 보조 지표들이 존재한다. 확실히 아무것도 모르고 주식에 뛰어드는 것 보다는 이러한 지표들을 어느 정도 이해하고, 볼 줄 안다면 투자에 대한 성공률을 높일 수 있을 것이라고 생각한다. 본인도 이러한 보조 지표를 보는 방법을 익히고 싶지만, 이번에는 AI에게 맡겨서 학습을 시켜보기로 했으니, 다음 기회에 배워보도록 하겠다.

 

1. MACD ( Moving Average Convergence and Divergence)

: MACD는 검색만 해도 친절하게 설명해놓은 글들이 정말 많기 때문에 여기서는 간단하게 개념만 짚고 넘어가려고 한다. MACD는 이동평균수렴&확산법이며, 기존의 MA에서 한 단계 더 나아간 지표이다. 보통 MACD는 MACD 뿐만 아니라 Signal 및 Oscillator을 함께 계산하고, 계산식은 다음과 같다.

 

[ MACD 계산식 ]

1) MACD : 12일 EMA - 26일 EMA

2) Signal : MACD 9일 EMA

3) Oscillator : MACD - Signal

 

계산식을 보면 무척 단순하다. MACD는 12일 지수이동평균 - 26일 지수이동평균이고, Signal은 앞에서 구한 MACD에 대한 9일 지수이동평균을 한 번 더 구한 값이다. 마지막으로 Oscillator는 MACD와 Signal의 차이 값이다. 그럼 "이 지표를 어떻게 이용하냐?" 라는 질문을 던질 것이다. 

위의 그래프는 일 자별 가격 차트에 대한 MACD, Signal, Oscillator 그래프이다. 아래 그래프들 중에서 검은 선은 MACD, 빨간 선은 Signal 그리고 아래의 Histogram들은 Oscillator을 의미한다. 보통 Oscillator가 (-)에서 (+)로 전환될 때, 매수를 하고 반대일 경우에는 매도를 한다. 위의 차트에서 Oscillator 기반의 매수/매도를 충실히 이행했더라면, 큰 수익을 봤을 것이라고 생각한다.

 

MACD 지표에 대한 매수, 매도 기법은 정말 다양하다. 각 개인마다 다르다고 할 정도로 해석하는 방법에는 수 많은 방법들이 있다. 이러한 방법들은 다른 전문가들이 설명한 글들이 많이 있으니 궁금하다면 검색해서 참고해보길 바란다. 

 

그렇다면 1시간 단위의 차트에도 같은 기준으로 적용하면 잘 나올 것까? ( MACD 12, 26, 9 ) 일단 본인이 1시간 차트에도 동일한 기준 (12, 26, 9)로 MACD를 그렸을 때, 결과는 썩 좋지 않았다. 어찌보면 당연하겠지만, 시간 단위의 차트에서 투자 심리를 반영하기에 12시간, 26시간은 상당히 짧은 것 같다. 이 때문에 시간 단위 차트에서의 MACD를 구할 때는 적정한 기간을 찾아내야 했고, 나름 열심히 돌려봤을 때 적정한 기간을 찾아보았다.

사실 MACD를 통해 기대하는 것은 모델을 학습시킬 때, Oscillator가 Agent로 하여금 매수 또는 매도에 대한 Action을 판단하는 데, 있어서 하나의 중요한 Data로 적용할 수 있을 것이라는 점이다. 즉, Oscillator가 (-) 값을 가지거나 가지려고 할 때는 매도를 지향할 것이고, 반대로 (+) 값을 가질 때는 매수를 지향하는 방향으로 학습이 될 수 있다는 점이다. 그리고 앞으로도 소개할 보조 지표들에 대해서는 현재 차트에 맞도록 기간 또는 Parameter 조정이 빈번하게 필요하다. 이러한 경우, 학습 모델의 목적에 알맞게 최대한 방어적으로 Agent가 행동할 수 있도록 보조 지표를 변환할 예정이다.

 

 

2. Rate of Change (ROC) & Momentum

: 일반적으로 모멘텀(Momentum)현재 가격에서 일정 기간 전의 가격, 또는 이동평균가격을 차감함으로써 추세를 확인하는 지표이다. 이 값이 0보다 클 경우 상승 추세를 의미하고, 반대일 경우에는 하락 추세를 나타낸다고 볼 수 있다. 모멘텀에서 주의해야할 점은 기준일부터 어느 시점 이전의 가격을 차감하거나 이동평균에 대한 기간으로 설정할 것인지가 중요하다. 그 이유는 차트마다 유효하게 적용되는 기준 값이 다르기 때문이다. 

 

이러한 기준을 잡기 위해서는 다음과 같은 특징이 모멘텀에서 나타나야한다. Momentum의 값이 0보다 크고 점차 커지고 있을 경우, 가격 또한 상승 추세를 보여야 하며, 반대도 마찬가지이다. 그리고, 가장 중요한 부분은 이러한 추세가 가격에 반영되기 이전에 나타나야 한다. 다음 차트를 예로 들어보자.

위의 차트는 일자별 가격 차트와 14일 이전의 Momentum 차트이다. 결론만 얘기하면, 위의 차트는 사실 의미없는 차트라고 봐도 무방하다. Momentum 차트 내에서의 추세 변환 시점과 차트 가격의 추세 변환 시점이 일치하기 때문에, Momentum 차트를 보고 매수, 매도에 대한 대응을 하기 힘들기 때문이다. 

 

다음은 ROC에 대한 설명을 해보자. ROC는 무척 단순하다. 과거 시점 대비 현재 시점에 대한 가격 변동률을 의미한다. 공식도 간단하다. 

 

ROC = P(t) / P(t-n1) * 100

 

100을 곱해주는 이유는 100을 기준으로 가격에 대한 추세를 판단하기 위해서다. (단, 학습 Data에 추가할 때는 100을 곱하지 않을 예정이다.) ROC가 100보다 클 경우, 이전에 비해 가격이 상승한 것을 의미하고 이하일 경우에는 하락한 것을 의미한다. 현재 가격에 대한 변동률을 보여주는 지표이긴 하지만, 위의 값만으로 가격을 예측하는데는 부족한 부분이 많기 때문에 보통 Momentum을 계산해서 흐름을 판단한다. 

 

Momentum = ROC - ROC n2일 EMA

 

ROC와 ROC에 대한 n2일 기간의 지수이동평균의 차이를 Momentum으로 보통 계산한다. 이에 적절한 n1과 n2를 찾는 작업이 필요하겠지만, 만약 찾는다면 좋은 Data가 될 수 있을 것이라고 기대한다. 그리고 다음 그래프는 1시간 봉 차트에서 구한 Momentum 차트이다.

지속적으로 언급하지만, 동일한 차트에 대해서도 각 개인별로 매수, 매도 포지션은 상이하다. 위의 매수, 매도는 본인이 Momentum 차트를 보고 취한 포지션이다. 아주 단순하게 Momentum이 0보다 작고, 상승에 대한 추세가 보일 경우 매수를 취했고, 0보다 크고, 하락으로 전환되었을 때 매도를 취했다. 실제로 위와 같이 했으면 꽤나 수익이 났을 것 같다. 그리고 위에서도 언급했듯이, 시간 차트 같은 경우에는 일반적으로 적용하는 일자별 기간보다 기준일을 길게 설정하는 것이 확실히 더 잘 반영되는 것 같다. 

 

3. DMI & ADX

: 아마 이번 글에서 다루게 될 마지막 추세 지표가 될 것 같다. DMI(Directional Movement Index)ADX(Average Directional Movement Index)는 각각 독립적으로도 사용하는 보조 지표이지만, 일반적으로 함께 볼 때, 추세에 대한 적중률이 높아진다고 알려져있다. 

 

먼저, DMI란 추세 지표의 일종으로 전일 대비 현재 시점의 고가와 저가, 종가의 최고값을 이용하여, 추세를 판단하는 지표이다. 

위에 차트는 ADX(검), PDI(녹), MDI(빨)을 의미한다. 여기서 PDI와 MDI만 살펴보면, PDI는 주가의 상승을 나타내며, MDI는 하락을 나타낸다. DMI를 계산하기 전에는 DM과 TR을 먼저 알아야하는데, 간략하게 설명하고 넘어가도록 하겠다. (자세한 설명 : layhope.tistory.com/236)

 

DM을 구할 때, 다음과 같은 가정을 한다. "상승 추세일 경우, 금일의 고가는 전일의 고가보다 반드시 높고, 하락 추세일 경우에는 반드시 금일의 저가는 전일의 저가보다 낮아야 한다"는 부분이다. 이럴 경우, 전일과 금일의 고가 & 저가를 비교하면 다음과 같다.

 

1) 금일 고가 > 전일 고가 : +DM

2) 금일 저가 < 전일 저가 : -DM

3) 금일 변동폭 < 전일 변동폭 : DM = 0

4) 금일 고가 상승폭 > 금일 저가 하락폭 : +DM

5) 금일 저가 하락폭 > 금일 고가 상승폴 : -DM

 

다음은 TR을 구하는 공식인데, TR은 3개의 값 중에서 Max 값을 취한다.

 

1) 금일 고가 - 금일 저가

2) 금일 고가 - 전일 종가

3) 금일 저가 - 전일 종가

 

결국 직관적으로 보면 DM은 가격의 상승과 하락에 대한 단일 추세를 알 수 있고, TR은 이러한 추세의 비중이 어느 정도인지 기준을 잡기 위해, 구하는 값이라고 이해하면 될 것 같다. 이렇게 구한 DM과 TR을 이용하여 PDI와 MDI를 구하면 다음과 같다.

 

PDI = (+DM의 MA) / (TR의 MA)

MDI = (-DM의 MA) / (TR의 MA)

 

즉, PDI는 상승 추세에 대한 지속성을 파악할 수 있고, MDI는 하락 추세에 대한 지속성을 가늠할 수 있는 지표로 사용될 수 있다. 그리고, 보통 PDI와 MDI가 교차하는 시점으로 부터 추세를 파악하기도 하며, 아래에 보여주는 그래프와 같이 추세를 어느 정도 가늠할 수 있다.

이처럼 DMI 지표만으로도 어느 정도 추세를 가늠할 수 있지만, PDI와 MDI를 독립적으로 두고 단순히 차이 값으로만 예측을 하게된다면, 잘못된 판단을 할 가능성이 높다. 예를 들어, 다른 두 시점에서 PDI와 MDI의 차이가 10으로 같을 때, 10이라는 값이 두 시점에서 의미하는 게 동일하냐는 것이다. 기준이 없기 때문에 우리는 대답을 할 수 없었을 것이다. 이러한 이유로, 차이 값에 대한 적절한 가치 판단을 해줄 기준을 제시해 준게 ADX이고, 그 기준은 PDI와 MDI를 더한 값이다.

 

ADX = | PDI - MDI | / (PDI + MDI) * 100

 

ADX에 대한 매수/매도 포인트를 잡는 방법은 정말로 다양하다. ADX와 PDI가 교차 상승하는 구간에 매수를 하는 사람이 있는가 하면, 단순히 ADX가 상승으로 전환하는 시점을 매수 시점으로 잡는 사람들도 있다. 이처럼 ADX 지표를 바라보는 관점은 사람마다 상이하지만, ADX & DMI 그래프가 가격에 대한 추세를 잘 반영한다는 것은 본인도 강하게 동의한다. 실제로, ADX & DMI 차트만 가지고도 수익을 본 사람들이 주변에 꽤나 많다. 

 

이번 글에서는 보조 지표를 분석해서 매수/매도 포인트를 잡는 법을 배우는 글이 아니기 때문에, 이 부분은 쿨하게 패스하도록 하겠다. 대신 학습에 넣을 Data에 ADX & DMI가 유효한지 판단해보았다.

위의 차트는 1시간 차트이며, 밑에는 ADX & DMI 그래프이다. 매수/매도는 간단하게 ADX가 PDI를 교차하는 지점을 매수 시점으로 잡았고, ADX가 MDI를 교차하면서 하락할 때, 매도 시점으로 잡았다. 

 

음.. 사실 이 글을 작성하면서 보조 지표를 처음 접해봤는데, 이쯤되니깐 아무 생각없이 매수/매도를 했던 본인이 너무 한심하고 부끄럽게 느껴졌다. 이렇게 좋은 보조 지표들이 있는데도 MA 한 번 보지도 않고 투자(분석하지 않고 샀으니 투기라고 해도 무방하다...)했기 때문에 손실이 크지 않았나 싶다.

 

결과적으로 ADX와 DMI도 학습 Data의 한 Input Feature로 추가할 예정이며, 이렇게 추세 지표에 대한 보조 지표는 EMA, MACD, ROC & Momentum 그리고 ADX & DMI를 새롭게 추가할 예정이다.

: Chart Data 전처리와 관련하여 올리는 3번 째 글이다. "학습시킬 Data가 이렇게 중요한가? 그냥 넣으면 되지 않나" 라고 생각할 수도 있지만, 그 동안 본인의 경험으로는 Data를 분석에 알맞게 전처리 하는 작업이, 좋은 모델을 사용하는 것보다 중요하다고 느꼈다. GIGO (Garbage In, Garbage Out). 쓰레기를 넣으면 쓰레기가 나올 확률이 높기 때문이다.

 

또한, 그만큼 가격 차트 데이터에 대한 보조 지표가 정말 많다. 모든 보조 지표를 이번 학습에서는 사용하지 않겠지만, 유의미하다고 판단되는 보조 지표는 될 수 있으면 많이 추가할 예정이다. 그리고, 이번 글에서는 지표에 대한 상세 설명은 생략하도록 하겠다. 대신 Data의 의미를 하나씩 짚어가면서 넘어가는데 초점을 맞추고자 한다.

 

1. EMA (Exponetial Moving Average)

: EMA(지수이동평균)를 설명하기 전에 먼저 MA(단순이동평균)을 언급하자면, MA는 정말 단순하게 현재 시점을 기준으로 특정 시점 이전까지에 대한 값의 평균을 의미한다. 예를 들어 "오늘 기준 MA10"(Day 단위)이라고 하면, 오늘부터 10일 전 까지의 가격에 대한 평균을 나타낸다. 그렇다면 MA는 왜 보는 걸까?

 

MA는 앞에서 살짝 언급을 했지만, 현재 시점의 가격만으로 미래를 예측하기에는 정보가 너무 부족하기 때문에, 과거에 대한 정보도 함께 녹아있는 Data가 추가된다면, 미래를 예측하는데 있어서 정확도를 높일 수 있기 때문이다. 현재 시점의 가격과 거래량도 무척 중요하지만, MA를 함께 고려한다면 현재 가격과 거래량에 대한 흐름을 어느 정도 파악할 수 있다. 

 

예를 들어, 현재 삼성전자 주식 가격이 100원이라고 하자. 그렇다면, 앞으로 가격이 오를 것인가? 떨어질 것인가?라는 질문을 받았을 때, 어떻게 판단을 해야할까? 뭐... 삼성전자는 무조건 오를거니깐 일단 사자!!라고 하면서 살 수도 있겠지만, 우리는 삼성전자만 살게 아니니깐 다르게 생각해야 한다. 정답은 오르든지, 떨어지든지, 아니면 그대로인지 셋 중에 하나다. 결국 찍는 수 밖에 없다는 것이다.

그렇다면 여기에 추가로 현재 가격은 100원이고, MA5는 150원이라고 하자. 5일 치의 평균이 150원이라는 의미는 5일 동안 최소 150원 이상의 가격이 있었던 적이 있고, 과거에는 대체적으로 100원보다는 높은 가격대를 형성했다고 판단할 수 있다. 즉, 이는 곧 가격이 하락하는 추세였다고 판단할 수 있다. 반대로, MA5가 50원일 때는, 가격이 최근 5일동안 전반적으로 상승했다고 짐작할 수 있다. 결과적으로, MA를 함께 비교할 경우에는 대략적인 가격의 추세를 짐작할 수 있게 된다.

 

다시 한 번 질문을 해보자. 삼성전자 주식 가격이 100원이고 MA5가 50원이라고 하자. 그렇다면, 앞으로 가격이 오를 것인가? 떨어질 것인가?라는 질문에 어떤 대답을 해야할까? 정답은 오르든지, 떨어지든지, 아니면 그대로인지 셋 중에 하나다. 이럴 경우, 앞서 100원 밖에 모를 때와 상황이 다를게 뭐가 있겠냐라고 하겠지만, 단순히 추세가 상승을 보인다고 해서 다음 주식 가격이 오를거라고 판단하는 것은 섣부른 행동이다. MA로 판단할 수 있는건 과거부터 현재까지의 가격에 대한 추세이지, 이 추세만으로 다음 가격을 예측하기에는 부족한 부분이 많다. 

 

추후 언급하겠지만, 이러한 이유 때문에 많은 분석가들은 현재 추세가 아직 유효한지 판단할 수 있는 모멘텀 지표를 함께 분석함으로써, 의사결정에 대한 불확실성을 낮추고자 노력한다. 

 

다시 본론으로 돌아와서, EMA는 날짜가 현재에 가까울수록 더 많은 가중치를 부여하여, 최근의 가격을 좀 더 중요하게 바라보는 보조 지표이다. 이 때, 모든 날짜에 대한 가중치가 동일하면, MA와 같다. 추가적으로 설명하면, 가중이동평균(WMA)은 가중치가 일정한 비율로 감소하는 것과 달리, EMA는 이 가중치가 지수적으로 감소하는 가중치를 적용한다. 그리고 EMA는 WMA과 비교했을 때, 조금 더 현재에 대한 비중을 높이는 경향이 있고, 본인은 현재의 가격에 대한 비중이 높은 가격이 미래 가격을 예측하는데, 조금 더 유효할 것 같아서 EMA로 변환을 진행했다.

windows = [5, 10, 20, 60, 120]

for window in windows:
	# Normalization된 종가
    chart_data['close_norm_ema{}'.format(window)] = \
        chart_data['close_norm'].ewm(span = window, min_periods = window, adjust=False).mean()
    
    # log 변환되기 전의 거래량
    chart_data['volume_ema{}'.format(window)] = \
        chart_data['volume'].ewm(span = window, min_periods = window, adjust=False).mean()
    
    # log 변환
    chart_data['volume_transform_ema{}'.format(window)] = chart_data['volume_ema{}'.format(window)].apply(lambda x : \
    	math.log10(x/1000) if x/1000 > 1 else  0 )

windows에 있는 숫자들은 EMA에 대한 연산 기간을 의미한다. 일 자를 기준으로 한다면, 각각 5일, 10일, 20일 등을 의미한다. 여기서 .ewm 메소드에 대해 간단히 설명하면, ewm은 pandas dataframe의 한 method로 EMA를 쉽게 계산해주는 함수이다. span은 기간을 의미하며, min_periods는 해당 Dataframe에 요구하는 최소 기간이며, adjust는 EMA를 계산할 때, 분모를 무한급수를 적용하여 계산할지 안할지에 대한 여부이다. 

 

사실 adjust가 True일 때와, False일 때 결과값 차이가 크지 않지만, adjust를 False로 할 경우, 등비수열의 합으로 이루어져 있는 분모를 일일이 계산하는게 아니라 무한등비급수의 공식을 적용하여 간단하게 계산하기 때문에, Data의 크기가 클 경우 adjust = False로 해놓으면 속도가 더욱 빠르다.

 

종가(Close Price) 차트를 한 번 살펴보자.

plt.figure(figsize=(20,10)) 
plt.plot(chart_data['close_norm'], label = 'Norm')
for window in windows:
    print(window)
    plt.plot(chart_data['close_norm_ema{}'.format(window)], label = window)
plt.legend()
plt.show()

전문가들은 위의 차트만 보고도 다음 추세를 어느 정도 예측할 수 있겠지만, 본인은 아니기 때문에 그래프를 어떻게 해석해야할 지 감이 안잡혔다. 그러나, 해석은 못하더라도 EMA 그래프에서 나타나는 특징들은 발견할 수 있었다.


1) 급격한 하락, 상승 구간에서 각 기간에 대한 EMA의 간격이 벌어진다.
2) 상승 구간에서는 EMA5가 EMA120을 항상 추월하는 교차점이 있다. 

3) 반대로 하락이 있기 전에는 항상, EMA5가 EMA120 보다 떨어지는 교차점이 존재한다.

 

위의 특징들을 모든 차트에 일반화시킬 수는 없지만, 위의 가격 차트만 봤을 때, 지극히 단순하게 2), 3)만 따라서 매수, 매도를 했었도 수익이 났을 것이다. 이렇듯, 일반 차트에서는 보기 힘든 특징들을 EMA 차트와 함께 살펴봄으로써, 인사이트를 도출할 수 있다. 이는 곧, 학습 데이터를 통해 긍정적인 기대와 이어질 수 있다.

 

거래량(Volume) 차트를 한 번 살펴보자.

plt.figure(figsize=(20,10))
plt.plot(chart_data['volume_norm'], label = 'Norm')
for window in windows:
    print(window)
    plt.plot(chart_data['volume_norm_ema{}'.format(window)], label = window)
plt.legend()
plt.show()

위의 차트를 보고 당황할 수도 있겠지만, 위의 거래량은 지난 글에서 언급했듯이 거래량에 대해 log(10) 변환을 취한 그래프이다. 다시 한 번 언급하자면, log변환을 취함으로써 거래량의 선형적인 성격을 유지하는 동시에, 거래량 상승분에 대한 Scale을 줄임으로써 보다 안정적인 학습이 진행될 수 있도록 하기 위함이다. EMA는 변환을 하기 전에 대한 거래량에 적용했고, 변환 뒤 EMA를 계산하게 되면 완전히 다른 결과가 나오기 때문이다. ( log(mean(x)) != mean(log(x)) )

 

음.. 위의 차트만을 보고 직관적으로 해석하기에는 어려움이 좀 있는 것 같다. 그러나, 거래량 차트는 가격 차트와 함께 분석했을 때, 보이지 않던 특징들을 도출해 낼 수 있을 것이다. 

 

이렇게 EMA에 대한 설명이 어느 정도 끝난 것 같다. 공식이나 이론적인 부분은 최대한 설명을 안하긴 했는데, 고작 EMA 하나 하면서 글을 왜 이렇게 길게 썼나 싶다.. 아직 MACD Oscillator, ROC, DMI &  ADX 등 설명할 보조 지표가 많이 남긴 했다. 그래도 급하게 가는 것 보다 차근차근 제대로 진행하는 것이 목표이기 때문에 속도를 조절해가면서 진행하고자 한다.

: 주식 차트 데이터에 대한 보조 지표는 이미 많은 사람들이 사용하고 있으며, 기존의 방식에 더하여 자신만의 지표를 개발하는 사람도 많다. 이번 차트 데이터에 대한 전처리는 지난 번과 마찬가지로 코인 시장의 한 종목에 대한 차트 데이터를 이용하였으며, 1시간 봉을 기준으로 설정했다.

 

: 위의 데이터는 KRW-XRP의 1시간 봉 차트이며, 날짜는 9/2~10/12, 약 한 달 조금 넘는 기간을 대상으로 했다. 딱 차트만 봐도 위의 차트는 수익을 내기 굉장히 어려운 차트인 것 처럼 보인다. 매수를 해야하는 시점이 얼마 없기 때문이다. 그러나, 이러한 차트에 대해서도 수익을 내거나 손실을 최소화시키는 모델을 만드는 것이 목적이기 때문에, 학습을 하기에는 좋은 차트임에는 틀림없다.

 

먼저, 본인은 기간 및 종목, 기준 봉을 입력하면 그에 해당하는 Data를 가져오는 Data Loader을 미리 만들어 놨다.

from lib.data_loader import DataLoader
import matplotlib.pyplot as plt
import pandas as pd

dataLoader = DataLoader()

q, p = dataLoader._make_query('20200902000000', '20201012000000', 'minute60', 'KRW-XRP')
training_data, chart_data = dataLoader._run(q, p)
chart_data.describe()

위의 모듈을 적용하면, 미리 세팅해 놓았던 Training Data와 Chart Data를 반환하게끔 만들었으며, Moving Average(MA)를 구하면서 발생하게되는 Null 값을 처리하기 때문에 실제 기간보다 조금 더 줄어들게 된다.

 

chart_data.describe()는 pandas dataframe의 한 method로 Data에 대한 전반적인 정보를 살펴볼 수 있다. 

여기서 open, high, low, close는 각각 시가, 고가, 저가, 종가를 의미하고 volume은 거래량을 의미한다. 또한, 좌측의 index를 살펴보면 count는 Null 값(결측치)이 아닌 실제 값들에 대한 개수를 의미하며, 이 값이 현재 데이터의 총 row 개수인 960보다 작으면 결측치가 있다고 판단할 수 있다. 그리고 각 column에 대한 기본 통계 수치도 확인할 수 있다.

 

일단 애초에 본인이 만들어 놓은 Local DB에 데이터를 적재할 때, 결측치가 발생하지 않게끔 실시간 적재를 하고 있지만, 그래도 혹시나 결측치가 있을 경우, 이에 대한 처리를 하는 과정을 추가했다.

 

1. Null 값 처리

: Data를 다루는 사람들이라면, 한 번 쯤은 시행했을 전처리이다. 결측치의 처리는 Data를 분석할 때, 결과를 좌지우지할 수 있을 정도로 중요한 부분이면서, 가장 기본적인 절차다. 이번에도 예외는 없다.

chart_data.index = pd.to_datetime(chart_data.date) 
chart_data = chart_data.iloc[:, 1:]

a = chart_data.isnull().sum(0)
nullCol = [x for x in a.index if a[x] != 0]

if len(nullCol) > 0:
    for c in nullCol:
        print('{} has Null'.format(c))
        chart_data[c] = chart_data[c].interpolate(method='time')

코드에 대한 설명은 불친절할 수도 있다. 왜냐하면, 코드를 하나씩 설명해가며 글을 적기에는 너무나 많은 시간이 걸리기 때문에 코드와 관련해서는 나중에 기회가 있을 때, 포스팅할 예정이다. 

 

위의 코드는 Null 값(결측치)이 있을 경우, Interpolation (보간법)을 시행한다. 결측치에 대한 처리 방법은 다양하게 있지만, .fillna(method='bfill') or .fillna(method='ffill') 같은 경우에는 결측치의 구간이 길어질 때 오히려 noise로 작용할 우려가 있어, 사용하지 않았다. 대신에, chart_data는 시간을 인덱스로 사용하기 때문에, 시간에 Weighted한 interpolation을 적용하기로 했다. (의미를 모를 경우, 구글에서 interpolate with time method 검색)

 

2. 종가 (Close Price) Normalization

: 종가라고 적어놨지만, 사실상 이번 Data 분석에서 사용할 가격에 대한 Data를 정규화시키는 가장 가장 가장 중요한 부분이다. 본인도 어떻게 하면 강화학습의 Policy Network (정책신경망)을 잘 학습시킬 수 있을지 많은 고민을 했다. 

 

먼저, 종가의 Normalization을 언급하기 전에, Normalization, Standardization, Generalization 에 대한 구분을 모른다면, 한 번 쯤 검색해서 읽고 오길 바란다.

 

결론적으로, 종가에 대해서는 min-max normalization을 적용하기로 결정했다. Standardization과 Normalization 사이에서 어떤 방법을 적용하는게 가장 Robust한 모델을 만들 수 있을지에 대해 초점을 맞춰 고민했다. 여기에 모든 과정을 적을 수는 없지만, 간단한 이유는 다음과 같다.

 

Starndardization은 특정 기간에 대한 가격의 평균과 표준편차를 구하여, 이를 평균이 0, 표준편차가 1인 분포로 표준화를 시키는 과정이다. 물론, 많은 논문에서도 Data를 그대로 사용하는 것보다 표준화를 시켜서 학습하는 것이, 성능이 더 좋다는 결과가 많기 때문에 이미 많은 사람들이 알고 있을 것이다. 

 

Outlier에 대해서도 min-max Normalization과 비교했을 때, 안정적인 전처리 방식이기도 하다. 그럼에도, Standardization을 선택하지 않은 이유는 "평균 변화에 대한 민감도"가 "최고 - 최저가의 변화에 대한 민감도" 보다 더욱 크기 때문이다. 

 

예를 들어, 최근 3개월의 가격 평균을 기준으로 표준화시키고, 이를 학습 데이터로 학습을 시켜 모델을 만들었다고 하자. 만약 한 달이 지난 시점 가격에 대한 상승 변동이 있다고 하면, 전체적인 가격에 대한 평균이 더욱 올랐을 것이다. 즉, 현재 시점의 가격을 한 달 전에 계산한 평균과 분산을 가지고 표준화를 시켰을 때, 그 영향도가 실제로 같지 않을 것이라는 의미이다. 

 

조금 더 쉽게 설명하면, 280원이라는 가격이 한 달 전에는 30원 정도의 가치였다면, 현재 280원의 가치는 20원 정도로 줄어들었다는 얘기이다. 그럼에도 과거의 평균과 표준편차를 이용하여 표준화시킨다면, 실제 가치에 대한 반영이 잘 안될 수 있다는 의미이다. 또한, 이를 현재 기준으로 평균과 표준 편차를 구하여 표준화를 했을 시에도 문제는 발생한다. 학습시킨 모델이 한 달 전에 표준화시켰던 데이터에 Fitting(적합)이 되어있기 때문에, 현재 Data에 대해서도 잘 반영이 될 수 있을지 장담할 수 없다는 것이다.

 

그.래.서. 표준화(Standardization)는 종가에 대한 평균이 계속 바뀌다보니, 이에 대한 변동성을 최대한 줄이고자 Min - Max Normalization을 선택했다. 물론, Min-Max Normalization에 대한 가장 큰 문제는 범위를 초과하는 가격이 있을 시, 이에 대한 Scaling이 엉망으로 된다는 점이다. 그러나, 적정한 Min-Max 범위를 잡는다면, 변동에 대한 걱정은 훨씬 덜하기 때문에 Min-Max를 크게 벗어나지 않는 이상, 한 번 학습한 모델을 지속적으로 사용할 수 있다는 장점이 있다. 

 

다시 본론으로 돌아오면, min-max의 범위를 안정적으로 잡기 위해서 올 해 7월 부터 현재까지의 최고가, 최저가를 기준으로 잡고 Normalization을 진행했다.

dataLoader = DataLoader()
q, p = dataLoader._make_query('20200701000000', '20201013000000', 'day', 'KRW-XRP')
chart_data_day = dataLoader._run_only_query(q, p)

_max = max(chart_data_day.high)
_min = min(chart_data_day.high)

chart_data['close_norm'] = chart_data.close.apply(lambda x : (x-_min)/(_max-_min))

 

3. 시가, 저가, 고가 (Open, Low, High)변환

: 시가, 저가, 고가에 대한 정보도 미래에 어떠한 행동을 결정하는데, 중요한 정보인 것은 사실이다. 그러나, 코인 같은 경우에는 주식 처럼 휴장이 존재하지 않기 때문에, 가격 차트가 더욱 연속적이다. ( 종가 = 다음 시가 ) 그렇다면, 시가, 저가, 고가를 어떤 식으로 변환을 하면 좋을 지 고민해봤다. 

 

사실 종가, 시가, 고가, 저가를 변환없이 가격 그대로를 학습 데이터로 사용할 수 있다. 그러나, Neural Network의 특성상 각 Data는 연속적인 선형 결합들에 대해 정보를 계속 변환시키기 때문에 좀 더 고민을 해 보았다. 위의 그림을 참고하여, 좀 더 과장되게 설명한다면, 신경망 학습을 시킬 때, 종가의 가격이 각각 250, 500으로 다름에도 선형적인 결합으로 인해 단순히 종가와 고가의 차이인 "30" 대한 정보만 Focusing이 될 확률이 높을 수 있다는 것이다. 

 

하지만 본인은 종가와 고가의 차이인 30이라는 값 자체보다, 30이라는 값이 실제 종가 대비 얼만큼의 비중을 차지하는지가 앞으로의 가격 흐름에 더 중요하다고 생각했기 때문에, 시가, 저가, 고가를 종가에 대한 비율로 변환했다. 

chart_data['open_close_ratio'] = (chart_data.open - chart_data.close) / chart_data.close
chart_data['low_close_ratio'] = (chart_data.low - chart_data.close) / chart_data.close
chart_data['high_close_ratio'] = (chart_data.high - chart_data.close) / chart_data.close

사실 그냥 Data 그대로 넣어도 Network 내에서 더 좋은 특징을 뽑아낼 수도 있겠지만, 굳이 종가에 대한 비율이라는 좋은 정보를 안 넣을 이유도 없기 때문에 학습 데이터에 추가해보기로 했다.

 

 

4. 거래량 변환 ( Volume Transformation )

: 거래량은 거래량 자체만으로도 모델을 학습시킬 수 있을정도로 중요한 정보이다. 가격 차트는 거래량의 발자취라고 할 정도로 중요한 변수인데, 이를 학습에 어떻게 녹여내야할 지 무척 고민스러웠다. 가장 큰 문제는 거래량의 분포를 보면 알 수 있다.

 

대부분의 거래량은 0.01 이하에 분포되어 있지만, 그에 비해 수 십에서 수 백배에 달하는 거래량도 함께 분포되어 있다. 이럴 경우 min-max normalization을 한다고 하더라도 대부분의 값들이 0에 가까운 값을 갖기 때문에, 이를 그대로 사용할 경우 평소의 구간에서 거래량은 매수, 매도 행동을 결정하는데 영향을 미치기 힘들게 된다. 

 

그렇다고 Standardization을 진행한다고 하더라도 평균 값이 위와 같이 실제 분포되어 있는 값들과 비교했을 때, 상당히 괴리감이 있기 때문에 제대로된 표준화가 어려울 뿐더러, 이 데이터를 가지고 학습시킨 뒤, 위에 종가 Normalization에서 언급한 것 처럼, 현 시점 거래량에 대한 가치와 차이가 크기 때문에 적용하기가 곤란하다.

 

그.래.서. 최대한 거래량에 대한 정보를 살리면서 학습하기에 좋은 방향으로 변환(Transformation)하는 방향으로 생각해보았다. 실제로 Data Transformation은 통계분석을 할 때, 많이 사용된다. Normal성을 보이지 않는 Raw Data를 특정 방식으로 변환을 시킨 뒤, 분석을 진행하면 Normal성을 따르기 때문에 기존의 통계적인 기술을 적용할 수 있기 때문이다. 주의할 점은, 보통 많은 사람들이 변환한 뒤 통계적인 기술을 적용하고 원래의 방향으로 역변환시킬 때, 통계적인 기술을 적용한 결과도 역변환을 시킨 뒤, 최종 결과로 도출하는데, 이는 항상 성립하는 관계가 아니라 조정 절차가 필요하다.

 

아무튼 거래량 Data도 변환을 시키기로 했다. 바로 Log Transformation이다. 대단한 것처럼 보이지만, 사실 거래량 Data에 Log를 취했다고 보면된다. 그 이유는 거래량은 항상 0보다 큰 값을 갖고, 거래량이 수 백 ~ 수 천 배 이상 차이가 나는 Scale에 대해서는 Log(10)를 취할 경우, Scale의 차이를 10의 자리를 1로 Scaling 할 수 있기 때문이다. 예를 들어, log10으로 변환할 경우, 100은 2로 100,000은 5로 Scaling을 할 수 있다는 의미이다. 또한, Log 함수는 Linear하게 증가하는 함수이므로, 거래량에서 갖는 Linear한 성격도 갖고 있기 때문에, 거래량을 잘 대변해 줄 수 있을 것이라고 판단했다. 

 

첨언하자면, 이러한 Log + Power Transformation에 General한 변형 방식이 있긴 하다. 특히, 값이 항상 0 이상인 Data에 대한 변환은 Box-Cox Transformation을 많이 이용하는데, 이번 변환에서는 이를 사용하지 않고, log(10) 변환을 이용했다. 그 이유는 Box-Cox는 Log 변환 시, 자연상수 e를 밑으로 사용하기 때문에, Scale의 Spectrum이 여전히 컸기 때문이다.

 

import numpy as np

volArr = np.array(chart_data.volume).reshape(-1,1)
volArr /= 10000
volArr = np.apply_along_axis(math.log10, 1, volArr)

여기서, volArr /= 10000을 한 이유는 거래량의 최소 값이 40,000 이었기 때문에 천의 자리는 보지 않기 위해 처리를 했다. 만약 위와 같이 처리하지 않을 경우, 거래량은 4 ~ 8 사이의 값을 지니겠지만, 위와 같은 처리를 할 경우 1 ~ 5 사이의 값을 지닐 수 있기 때문이다. (사실 log의 특성상 나중에 3을 빼주는 것과 동일하긴 하다)

직관적으로 비교해보면, 거래량에 대한 영향력이 w라고 했을 때, 기존의 Raw Data 같은 경우에는 Neural Network 상에서 최소 거래량 = 1*w ~ 최대 거래량 = 40,000,000*w 으로 입력이 되기 때문에 w가 거래량 자체에 무척 민감하게 학습될 것이다. 그러나 Log 변환을 했을 경우에는 최소 거래량 = 1*w ~ 최대 거래량 = 4*w 이므로 거래량에 좀 더 덜 민감하게 학습될 수 있고, 이는 w에 대한 최적점을 찾기 훨씬 수월해질 수 있을 것이다.

: 먼저 학습에 사용되는 Chart Data 부터 살펴보기로 했다. 현재 학습에 사용되고 있는 Training Data는 기본적인 Chart Data에서 MA(Moving Average)를 구하고, 이에 대한 Ratio 값으로 변환을 했다. 실제 많은 차트 분석가들이 주가 차트에서 많은 지표를 추출하여 분석에 이용하지만, 본인은 처음 모델을 개발할 때, 이 부분에 대해서 신경을 써서 개발하지 못했다. (가장 중요한 부분인데도 말이다..)

 

이 때문에, 이번 기회에 하나씩 살펴보면서 Chart Data를 어떻게 전처리를 진행하여, Training Data로 변환할지 고민해보기로 했다. 

 

위의 Data는 Upbit의 KRW-XRP 15분봉 차트로 일자는 9/15 ~ 10/10 차트이다. Chart Data는 해당 시점에 대해 시가, 종가, 최고가, 최저가, 거래량에 대한 정보 밖에 없기 때문에, 몇 가지 지표를 추가하여 부족한 정보를 추가해주었다.

 

이러한 정보를 추가하는 이유는 9월 20일의 시가, 종가, 최고가, 최저가, 거래량과 10월 1일의 것과 동일하다고 가정할 때, 해당 시점에서 차트에 대한 정보는 같지만, 실제 앞으로 벌어질 이벤트는 과거의 사건들에 따라 다르기 때문이다. 즉, 현재 시점의 데이터를 과거에 독립적인 정보를 갖는 데이터로부터, 과거의 정보를 포함하고 있는 Data로 변환하는 과정이 필요한 이유이다.

 

반면에.. 나중에 다시 언급하겠지만, "LSTM을 이용하여 학습할 때, <기본 Chart Data>와 <지표를 추가한 Data>에 대한 학습 성능은 어떤게 더 좋을까..?" 라는 질문을 던졌을 때, 본인은 사실 잘 모르겠다. 어차피 Network 자체 내에서 기본적으로 Feature Extraction에 대한 효과가 깔려있기 때문에, 지표를 추가한 Data를 이용하겠지만, LSTM 자체가 Time-Step에 종속적인 Network이기 때문에, 해당 시점의 Data만이 아닌 과거의 정보도 내포하고 있는 정보를 추가한다면 오히려 Noise가 될 가능성도 있기 때문이다. 

 

예를 들어, LSTM 모델 Time Step을 60으로 했을 때, 60일 이전의 Data에 MA120 Data가 있을 경우, 이 Data는 현 시점에서 너무나 먼 과거의 정보를 담고 있는데, 이게 실제로 현재 가격에 미치는 영향이 없음에도, 해당 정보를 담고 있다면 값 자체는 Noise로 적용할 수도 있다는 점이다. 조금 더 과장을 하면, 삼성 전자 주식을 예측하려고 하는데, 10년 전의 가격까지 끌고와서 예측을 하는 것과 비슷하다고 보면 될 것 같다.

 

 

* Training Data의 중요성

먼저, 기존 모델에 적용한 방법은 "딥러닝/강화학습 주식투자" 책에서 사용한 전처리 방식이며, 이에 대해 개선해야할 부분을 이론적으로 접근하여, 살펴보기 위함이다.

실제 Chart Data의 가격 추이를 보면 변동률이 10% 내외 정도이다. 이에 대한, 추가 지표 Data를 추출하고, 분포를 살펴보았다.

 

가격(Price)에 대한 MA Ratio의 값은 0.00X 단위의 범위를 갖고 있으며, 거래량(Volume)에 대한 Ratio 값은 1 단위의 범위 값을 갖고 있다. 이를 다시 한 번 Re-Scaling 해서 비교해보면, 가격에 대한 값은 0~3사이 값을 가질 때, 거래량에 대한 값은 0~3000 사이 값을 갖게 된다는 것을 확인할 수 있다. 

 

일반적으로, 위와 같이 Training Data의 각 Input Feature에 대한 상대적인 Spectrum의 차이가 클 경우, Spectrum이 큰 Feature에 Bias(편향)되어 모델이 학습될 확률이 높다. 그 이유는 보통 Model의 초기 Parameter(Weight, Bias etc)에 대해서, 초반에 학습될 때, 각 Weight의 변화량에 대해 민감하게 반응하기 때문이다.

 

단순하게 예를 들면, Input 값에 대해 P(Price) = 1, V(Volume) = 1000이라고 가정했을 때, 각각 동일한 Weight 1이 주어진다면 1 * W + 1000 * W = 1*1 + 1000*1 = 1001 이다. 이 값이 Activation Function = A(1001)을 통해 나오는 Output 값은 이미 다음 Layer에 넘어가면서 Volume에 상당히 편향되어 반영되어 있는 것을 알 수 있다. 물론, 지속적인 학습을  통해 Weight의 값이 조정이 되겠지만, 이러한 Specturm의 차이는 학습되는 Weight에 대한 Spectrum에도 영향을 준다. Loss 값에 대해 Backpropagation이 진행될 때, 각 Parameter에 대한 유효한 Learning Rate의 차이가 상당히 클 경우, 실제 학습 모델에서 Learning Rate를 각 Parameter에 대해 일일이 적용할 수 없기 때문에, 제대로 된 학습을 기대하기 어려울 것이다. 

결론적으로, Input에 대한 Normalization, Standardization, Generalization(이건 조금 다르지만..) 등을 진행하는 가장 큰 이유는 일괄적으로 적용되는 Learning Rate에 대해 Optimal한 Point를 좀 더 찾기 쉽게 할 수 있도록 Metric Space의 Scale을 조정하는 과정이다. 

towardsdatascience.com/why-data-should-be-normalized-before-training-a-neural-network-c626b7f66c7d

 

Why Data should be Normalized before Training a Neural Network

And Why Tanh Generally Performs Better Than Sigmoid

towardsdatascience.com

즉, Training Data만 가지고 모델이 어떻게 학습될 지 예측해보면, 위의 Training Data를 그대로 사용할 경우 LSTM 정책 신경망은 가격정보에 대해서 어떤 Action을 취하기 보다는 거래량에 지나치게 치우쳐서 Action을 취하는 방향으로 학습이 될 확률이 높을 것으로 예상할 수 있다. 그리고 실제로 그렇게 되었다.

위의 에피소드를 살펴보면 거래량이 가장 많았던 두 구간에서만 매도/매수를 하고 나머지 구간에서는 거래를 거의 하지 않았다. 만약, 가격 정보도 잘 반영되어 학습이 되었더라면 최저점 구간에서도 매수가 이루어지는게 나왔을 것이라고 예상한다. 참고로, 앞 쪽 구간은 에피소드 시점을 Random으로 시작하기 때문에 실제 에피소드에서 포함되지 않은 구간이라 아무런 Action이 이루어지지 않았다.

 

마지막으로, 학습에 이용한 Policy Network 구조는 4개의 Unit으로 이루어져 있고, Time Step은 60으로 설정했다. 그리고 Hidden Layer는 각각 (256, 128, 64, 32)로 구성했다. Network의 구조도 생각보다 깊고, 가격 정보에 대한 Data 값도 1 e-3 수준이었으니... 어찌 보면 잘 학습되기를 바랐던게 이상했을 수도 있을 것 같다. 

(위의 그래프는 구조 이해를 위해 대충 그렸기 때문에, Cell State, Hidden State에 대한 Numbering이 제대로 되어 있지 않습니다...)

 

 

* 그렇다면 어떻게 해야할까?

 

: 우선, 1차적으로 진행해볼 사항은 가격 지표에 대한 공부이다. 사실 주식 차트에 대한 지식이 깊지가 않기 때문에, 유효한 지표가 어떤 것인지 잘 알지를 못한다. 이 때문에, 다음에는 전문가들이 주요하게 보는 지표에 대해 정리해볼 계획이다.

 

: 그리고 이를 바탕으로 Training Data에 대한 변환 또는 정책 신경망 Layer마다 Normalization을 추가..? 하는 방식으로 진행해보아야 할 것 같다. 사실 LSTM의 각 Layer Normalization 모델은 적용해봤지만, LSTM 성격상 Hidden 및 Cell State의 정보를 이용하여 일일이 Normalization을 해야하는데.. GPU 까지 얹히려다보니 자꾸 꼬여서 결국 해당 부분은 제외하고 진행을 했었다.

 

 

: 평소에 강화학습을 이용하여, 주식 또는 코인에 대한 자동 매매 프로그램을 개발하고자 했다. 평일에는 본업에 충실.. 해야하기 때문에, 퇴근 후 또는 연휴에 틈틈이 진행해보았다. 먼저, 24시간 대응을 할 수 있는 코인 시장에 대한 매매 프로그램을 개발할 예정이고, 현재 어느 정도 개발을 진행했다.

 

그러나, 개발을 하면서 생각보다 많은 고민 사항들이 있었고, 이를 블로그에 적어가며 기록해보고자 했으며, 이를 토대로 좀 더 Fancy한 매매 프로그램을 개발해보고 싶었다. 지금까지 개발 진행 사항은 다음과 같다.

 

1) 실시간 Chart Data 적재

2) MultiLayer-LSTM 기반 A2C RL Training Model 

 

 

* 실시간 Chart Data 적재

 

먼저, 실시간 Chart Data는 국내에서 가장 유명한 거래소인 Upbit의 API를 이용하여 Data를 적재하고 있다. 종목에 대해서는 우선 가장 거래량이 많은 BTC, ETH, XRP, LINK, EOS, HBAR 종목에 대해서만 Data를 Local DB에 실시간으로 적재하고 있다.

Local DB는 무료로 사용할 수 있는 MySQL을 이용하고 있으며, 각 open, close, high, low, accVolume, accPrice 는 시가, 종가, 최고가, 최저가, 누적 거래량, 누적 거래금액을 의미한다. 여기서 interv는 각 기간에 대한 기준을 의미하며, month, week, day, 240min, 60min, 15min, 5min, 3min, 1min 총 9개의 카테고리가 있다. 코인 같은 경우는 아무래도 24시간 유효한 시장이므로, 분 단위의 Data를 가지고 요리해보고자 했다.

 

특히, 주의할 부분은 Upbit API에서 Data를 호출할 수 있는 Limit Count 때문에, Day 기준의 Data는 1~2년 전의 Data도 가져올 수 있지만, 분 단위 Data는 하루가 지나면 호출할 수 없기 때문에 실시간으로 Data를 가져오는 Batch Job을 만들어 실행 중에 있다.

 

분 단위의 Data를 적재하다보니, DB 용량에 대한 우려를 할 수 있을텐데, 현재 약 한 달 정도 Data를 적재했을 때, 용량은 다음과 같다.

물론, 종목을 늘리면 Dramatic하게 올라가겠지만, 현재 본인 컴퓨터의 리소스를 고려했을 때는 적은 용량이 분명하다. 추후, 기회가 된다면 어떻게 API로 부터 Data를 호출하고, 이를 DB에 적재했는지에 대한 과정을 포스팅할 예정이다.

 

 

* MultiLayer-LSTM 기반 A2C RL Training Model 

 

학습 모델 개발은 기본적으로 "Python"으로 진행했다. 특히, 학습을 위해서 사용한 Deep Learning Library는 torch를 이용했으며, Single GPU를 이용하여 학습시켰다. 최근 강화학습 관련한 방법론 중에서 알파고로 유명했던 Actor-Critic 기반의 RL(Reinforcement Learning)과 비교했을 때, 성능이 더 좋은 방법론들도 많이 나왔지만, Actor-Critic이 구현하기도 쉽고 일반적으로 성능도 기존의 DQN보다 좋았기 때문에 Advantage Actor-Critic(A2C)로 진행해 보았다. 강화학습 기법에 대해서는 어느 정도 개발이 진전되고, 안정화가 되면 다양하게 시도해볼 예정이다.

 

보통 A2C보다 A3C가 성능이 더 좋지만, A3C 같은 경우에는 여러 개의 A2C Model로 학습하고, Global Network를 Update해야하는데, GPU가 1개 밖에 없기 때문에 효율성을 따져서 A2C로 진행했다. 이것도 추후에 GPU가 추가로 더 생기면 진행해볼 예정이다.

 

현재 Agent, Enviornment, Network, Learner 등 모델을 학습하기 위한 코드는 어느 정도 진행되었고, 모델을 학습시키면서 성능을 향상시키기 위한 고찰을 위주로 포스트에 업로드할 예정이다. 

 

강화학습 모델을 만들기 위해서는 Visualization이 무척 중요한데, 주식투자에 대한 Visualization은 DL/RL 주식투자 (김문권 지음) 책에서 보여준 방법이 본인이 생각할 때, 현재로서는 가장 나은 방법인 것 같아 참고를 하여, 적용해보았다.

 

그리고 일반적으로 주식차트를 학습시킬 때, 대체적으로 주가가 상승세를 보이면 어떻게 하든 돈을 잘 버는 모델로 학습이 된다. 그 이유는 어느 시점에 언제 어떻게 사든지, 결국 상승세를 보이기 때문에 해당 모델은 학습이 잘되는 것 처럼 보일 수도 있다. 

 

그러나, 이러한 자동 매매 프로그램 같은 경우에는 하락, 상승을 반복하며 횡보를 하거나 아니면 하락장일 때, 수익을 극대화시키거나, 손실을 최소화시키는 모델을 만드는 것이 더욱 중요하다.  즉, 하락이 예상될 때는 기존에 갖고 있던 주식을 모두 팔거나 매수를 홀딩하게 끔 학습이 되어야하며, 상승이 예상될 때는 매수를 적극적으로 하게 만드는 것이 목표이다.

 

예를 들어, 현재 학습하고 있는 모델의 한 에피소드를 살펴보자. 간단하게 빨간색은 매수, 파란색은 매도, 녹색은 홀드를 했다고 보면 되고, 맨 위의 차트는 실제 주식 차트이며, 두 번째 차트는 Agent가 주식을 보유하는 추이를 보여주며, 위의 경우에는 가격이 최저점 부근부터 점차 매수를 진행한 것으로 보면 된다. 그리고, 마지막 차트는 백만원으로 시작했을 때, 손익에 대한 차트이며, 해당 모델은 10% 수익이 나면 에피소드를 종료하는 것을 Target으로 했기 때문에 10만원을 번 시점 이후에는 끝이 났다. 위의 차트는 15분 봉이기 때문에, 실제 3일 동안 10%의 수익이 발생한 것으로 볼 수 있다. 

그러나, 이러한 모델을 바로 매매 프로그램에 적용하기 어려운 고민점들이 있기 때문에, 이번 기회에 이를 하나씩 파헤쳐보고 개선을 해보자 한다. 

+ Recent posts