본문 바로가기
AyoProject/Ayotera-Trade

[AT] 13. 이동평균선 구현 및 매매시점 예측 (1)

by 청양호박이 2020. 3. 8.

이번에는 지난번에 가져온 일봉차트 데이터를 기반으로 이동평균선을 구현하고 해당 이동평균선의 정보를 기반으로 매매시점을 예측하는 부분을 구현해 보도록 하겠습니다. 주식에는 여러가지 데이터를 가지고 해당 주식의 매매시점을 판단하는데, 이동평균선은 그 시점을 판단하는데 정보를 제공하는 대표적인 요소라고 말할 수 있습니다.

 

분석법 중 기술적 분석은 주식시장에서 데이터를 분석하고 예측하는 방법중에 하나입니다. Wikipedia에 따르면... 

 

" 기술적 분석은 시장의 가격 그 자체에만 관심을 갖고, 그런 가격 움직임의 원인에는 관심을 두지 않는다. 몇가지 가정과, 과거와 현재의 가격 움직임에 따라 미래의 가격이 어떻게 움직일지를 예측한다. 기술적 분석은 이론적인 뒷받침이 거의 없으며, 분석의 유효성은 과거의 시장 경험에 의존한다. "

 

이 이동평균선이라는 지표가 바로 기술적 분석의 한개의 항목에 속합니다. 단기 특정기간의 데이터를 기준으로 그 정보를 수학적으로 가공하여 그 속에서 insight를 도출하는데 활용되 됩니다. 이동평균은 Moving Average라고 하여 짧게 MA라고 표현하고, 일정 기간 동안의 가격을 평균을 내어 수치화하고 이를 그래프로 그린것이 이동평균선이 됩니다. 

 

예를들어, 오늘을 기준으로 10일 이평값은(MA10) 오늘의 주식종목의 종가부터 9일전까지의 종가의 평균을 구한 값입니다. 그렇다면 10일 이평선은 이런 이평값을 그래프로 그린 것이되는거구요. 이런 이동평균은 해당 종목의 추세를 수치적으로 표현하기 위해 개발된 아주아주 기본적이고 많이 사용되는 도구입니다. 기술적인 분석이던 아니던 매매시점을 예측하기에는 가장 손쉽게 사용되는 것이지요.

 

아참!! 여기서 중요한 부분은 이평값을 산출할때 사용하는 종가는 수정종가가 되겠습니다. 그래서 이전에 종목별 일봉차트를 가져올때, 수정종가 여부에 True값을 선택한 이유도 여기에 있습니다. 그럼 이제 지금까지 만든 프로그램에 이동평균선에 대한 로직을 갈아넣어보겠습니다. 진행은 아래의 단계로 하겠습니다.

 

  • 이동평균선을 구하기 위한 Pandas 적용
  • 특정 주식종목에 대한 결과 이평선 시각화
  • 이동평균선을 활용한 주요 매매법 알고리즘 적용
  • 특정 주식종목에 알고리즘 적용한 결과 분석

 

 

1. 이동평균선 Basic


위에 이동평균선에 대한 정의에 대해서 알아보았습니다. 하지만 글로만 가지고는 참으로 이해하기가 어렵습니다. 오늘을 기준으로 구하고자 하는 이평값은 해당 기간의 데이터의 평균을 구한다는데... 뭐지?? 그래서 한번 표로 알아보겠습니다.

 

아래와 같이 10일간의 수정종가 데이터를 가지고 있다고 해보겠습니다.

일자 수정종가 5일 이동평균값
20200306 150 (150 + 100 + 150 + 50 + 200) / 5
20200305 100 (100 + 150 + 50 + 200 + 150) / 5
20200304 150 (150 + 50 + 200 + 150 + 100) / 5
20200303 50 (50 + 200 + 150 + 100 + 150) / 5
20200302 200 (200 + 150 + 100 + 150 + 200) / 5
20200301 150 NaN
20200228 100 NaN
20200227 150 NaN
20200226 200 NaN

그렇다면 2020-03-06의 5일 이동평균값은 위에 기재된 방식과 같이 2020-03-02 ~ 2020-03-06까지의 수정종가의 합을 다시 5로 나눈 평균값이 됩니다. 왜 내 자신까지 포함하냐면, 이동평균값은 수정종가를 사용합니다. 따라서 해당일자의 주식시장이 이미 종료가 된 상황이기 때문에, 해당일의 이동평균값은 해당일을 포함하게 됩니다.

 

이 데이터가 기술적분석의 지표로 향후에 사용된다고 하니, 이동평균값에 대한 정의만 알고 우선은 통계량을 산출하여 나의 DB에 넣는 방법을 알아보겠습니다. (나중에 언젠가는 사용이 되겠죠...ㅎㅎ)

 

 

2. Pandas Rolling


지금까지 구현한 부분을 되짚어보면, 일봉차트 데이터를 mysql(mariadb)에 효과적으로 넣기위해 to_sql( )을 사용하는데 이는 Pandas에서 제공하는 DataFrame의 메서드였습니다. 따라서 모든 일봉차트 데이터는 종목별로 Pandas의 DF객체로 생성이 되게 됩니다. 

 

그렇다면, 해당 데이터를 기준으로 특정범위까지의 통계치를 구하기 위한 방법도 Pandas에 있는지 찾아봅니다. 왜냐하면, Pandas란 기존에 알아봤듯이 "데이터 조작 및 분석을 위해 Python 프로그래밍 언어로 작성된 소프트웨어 라이브러리" 이기 때문에 뭔가가 있지 않을까 하는 믿음에서죠.

 

Pandas에서는 rolling이라는 메서드를 제공합니다. 해당메서드는 window size를 정하고 그 크기만큼 현재 위치를 기준으로 앞의 데이터 집합을 가지고 통계치를 구할 수 있습니다. 예를들면 sum( ), max( ), mean( )과 같이 말이죠. 그럼 한번 테스트를 해볼까요??

b = {'a':[1,2,3,4,5,6], 'b':[1,2,3,4,5,6]}
b_df = pd.DataFrame(b)
print(b_df.rolling(window=3).mean())

###################################
     a    b
0  NaN  NaN
1  NaN  NaN
2  2.0  2.0
3  3.0  3.0
4  4.0  4.0
5  5.0  5.0

b를 dict로 객체를 생성하고, 바로 DataFrame으로 변환을 해줍니다. DataFrame에서는 rolling사용이 가능하기 때문에 window size를 3으로 해고 평균치를 구해보겠습니다. 해당 위치를 기준으로 앞의 데이터를 하나의 집합으로 인식하기 때문에 0, 1의 경우 앞의 데이터가 window size인 3보다 적기 때문에 연산이 불가능하고 NaN(Not a Number)를 출력하게 됩니다.

 

이렇게 하고 생각해보면... 우리가 지금까지 만든 DataFrame객체의 결과를 살펴보면...

          date start  high   low finish   mount
0     20200221  7330  7350  7090   7180  218425
1     20200220  7520  7540  7260   7330  139692
2     20200219  7420  7480  7390   7450   43486
3     20200218  7520  7670  7380   7390  256144
4     20200217  7600  7610  7510   7550   53442
...        ...   ...   ...   ...    ...     ...
9314  19850109  1649  1649  1649   1649    3828

다음과 같이 기준일이 가장최근일 수록 앞의 데이터 입니다. 따라서 현재 상태로 그대로 rolling을 사용하게되면, 정작필요한 최근데이터는 NaN이 되고 뒤의 데이터만 자신보다 나중에 일어날 주가를 가지고 이동평균값을 구하게 되는 것이죠. 따라서 해당기준일을 가지고 그보다 이전의 데이터를 window size만큼 rolling할 수 있는 방법을 찾아야 합니다.

 

여러가지가 있겠지만, 저는 DataFrame 자체를 거꾸로 뒤집고 이에대한 rolling을 그냥 적용해 보도록 하겠습니다. 그렇게 되면 앞으로 2단계로 동작하는 로직이 필요하게 됩니다. 

  1. DataFrame 으로 만들어진 일봉차트를 거꾸로 뒤집기
  2. 그 결과를 rolling하고 만들어진 결과를 DataFrame에 합치기

[일봉차트 뒤집기]

DataFrame을 겨꾸로 뒤집기는 사실 별 일이 아닙니다.

print(opt10081_res_pd)
print(opt10081_res_pd[::-1])

#############################
9314  19850109  1649  1649  1649   1649    3828
...        ...   ...   ...   ...    ...     ... 
4     20200217  7600  7610  7510   7550   53442 
3     20200218  7520  7670  7380   7390  256144 
2     20200219  7420  7480  7390   7450   43486  
1     20200220  7520  7540  7260   7330  139692 
0     20200221  7330  7350  7090   7180  218425 

다음과 같이, DataFrame변수를 slicing할때, 전체 범위를 가지고 step을 -1로 하면 뒤에서부터 쭉 가져올테니까요.

 

[rolling + DataFrame에 합치기]

그럼 이렇게 변경된 DataFrame 신규 객체를 가지고 rolling을 한 후 신규 객체에 column을 추가해서 확장된 DataFrame 객체를 만들어 보겠습니다.

opt10081_res_pd = pd.DataFrame(kd.opt10081_res)
opt10081_res_pd = opt10081_res_pd[::-1]

# 5이평값 rolling
ma5 = opt10081_res_pd['finish'].rolling(5).mean()

# DataFrame에 결과 추가
opt10081_res_pd.insert(len(opt10081_res_pd.columns), 'ma5', ma5)
opt10081_res_pd['ma5_2'] = ma5

print(opt10081_res_pd)

#################################################################
          date start  high   low finish   mount     ma5   ma5_2
9318  19850104  1598  1598  1598   1598     471     NaN     NaN
9317  19850105  1596  1598  1596   1598    3769     NaN     NaN
9316  19850107  1611  1633  1611   1633    6242     NaN     NaN
9315  19850108  1664  1664  1649   1649    2650     NaN     NaN
9314  19850109  1649  1649  1649   1649    3828  1625.4  1625.4
...        ...   ...   ...   ...    ...     ...     ...     ...
4     20200217  7600  7610  7510   7550   53442  7600.0  7600.0
3     20200218  7520  7670  7380   7390  256144  7548.0  7548.0
2     20200219  7420  7480  7390   7450   43486  7512.0  7512.0
1     20200220  7520  7540  7260   7330  139692  7462.0  7462.0
0     20200221  7330  7350  7090   7180  218425  7380.0  7380.0

다음과 같이, 뒤집은 아이를 기존 DataFrame으로 교체하고, 바로 수정종가부문 필드를 rolling해줍니다. 이때, 5일 이평값을 위해서 window size는 5로 해주고, 평균을 구하는 메서드인 mean( )을 적용해줍니다. 그리고 기존 DataFrame에 신규로 계산된 ma5를 넣어주는 2가지 방법을 사용해서 추가해주면 됩니다.

 

원하는대로, 가장 오래된 데이터들은 ma5값이 존재하지 않고, 나머지 데이터들은 자신을 기준으로 window size만큼 오래된 데이터를 가지고 평균을 구해서 자신의 위치에 리턴하게 됩니다. 이렇게 되면 남은 것은 내 DB에 추가된 Data Frame을 저장해 주는 일이 되겠습니다.

 

[변경코드]

    # 사용자 관심 주식종목 전체 일봉차트 DB저장
    engine = create_engine('mysql://root:' + '@tedd40@' + '@localhost/at_project', encoding='utf-8')
    now = time.strftime('%Y-%m-%d')
    for i in sList:
        # opt10081 TR요청
        kd.opt10081_res = {'date': [], 'start': [], 'high': [], 'low': [], 'finish': [], 'mount': []}
        kd.SetInputValue("종목코드", i[0])
        kd.SetInputValue("기준일자", now)
        kd.SetInputValue("수정주가구분", "1")
        kd.CommRqData("AT_opt10081", "opt10081", "0", "0101")

        while kd.haveNext:
            time.sleep(kd.sleepDuration)
            kd.SetInputValue("종목코드", i[0])
            kd.SetInputValue("기준일자", now)
            kd.SetInputValue("수정주가구분", "1")
            kd.CommRqData("AT_opt10081", "opt10081", "2", "0101")

        opt10081_res_pd = pd.DataFrame(kd.opt10081_res)
        # 기준일 변경을 위한 DataFrame Reverse
        opt10081_res_pd = opt10081_res_pd[::-1]
        # 이동평균값을 구하기 위한 rolling적용
        ma5 = opt10081_res_pd['finish'].rolling(5).mean()
        # 기존 DataFrame에 column 추가
        opt10081_res_pd['ma5'] = ma5
        
        opt10081_res_pd.to_sql('stock_'+i[0], engine, if_exists='replace', index=False, index_label=None, chunksize=500)

[결과]

이렇게 이동평균값을 구하는 방법을 알아보았습니다. 다음에는 나에게 필요한 모든 이동평균값을 구해서 저장하고 저장된 데이터를 가지고 이동평균선을 그려보겠습니다. 이를 키움증권의 영웅문을 통해서 일치하는지 여부도 같이 확인해 봐야겠지요.

 

-Ayotera Lab-

댓글