< 7. 변동성 돌파 전략과 노이즈 분석 - (3) >


1. 개요

2. 노이즈 계산

3. 평균 노이즈 계산

4. 변동성 돌파 전략과 노이즈 상관 분석

5. 변동성 돌파 전략과 분석 - (1)

6. 변동성 돌파 전략과 분석 - (2)


해당 포스트를 읽기 전에 위 포스트를 읽길 추천한다.


이전 포스트에서 노이즈 특정 범위에 따른 변동성 돌파 매매를 백테스팅해봤다. 모든 경우의 수를 백테스팅해보지 않았지만 약 연 80%의 수익율이 나왔다. 모든 경우에 대한 백테스팅 결과는 다음에 차차 포스팅할 예정이다. 


이번 포스트는 이전 포스트에서 보았던 변동성 돌파 전략 매수/매도 알고리즘에 특정 조건을 추가할 것이다. 이전 포스트에서 사용했던 매수/매도 알고리즘은 아래와 같다.


매수 : ( 전일 고가 - 전일 저가 ) * Scope + 시초가 <= 현재가   [ Scope : 0.2 ~ 1.4 (0.1 간격) ]

* ( 전일 고가 - 전일 저가 )가 0 이상이어야 함.

* 당일 평균 노이즈 값이 사용자가 입력한 특정 노이즈 값 범위에 있어야 함.

매도 :  다음 날 시초가


위 알고리즘에 합집합 또는 교집합 개념을 도입할 것이다. 이 개념은 이동평균선의 정배열 개념에서 도출했다. 정배열은 단기 이동 평균선부터 중/장기 이동평균선 순으로 차례로 나열되어 있는 상태이다. 그렇다면 노이즈 값에도 이를 도입할 수 있지 않을까? 예로 사용자가 설정한 노이즈 값 범위가 40일 평균 노이즈와 60일 평균 노이즈 둘 다 속할 때 매수하는 것이다. 이는 40일 평균 노이즈와 60일 평균 노이즈 집합의 교집합과 같다. 또는 40일 평균 노이즈에 속하거나 60일 평균 노이즈에 속하면 매수 하는 것이다. 이는 합집합과 같다. 이 조건을 추가한다. 


해당 조건을 추가한 매수/매도 알고리즘이다.  


매수 : ( 전일 고가 - 전일 저가 ) * Scope + 시초가 <= 현재가   [ Scope : 0.2 ~ 1.4 (0.1 간격) ]

* ( 전일 고가 - 전일 저가 )가 0 이상이어야 함.

* 당일 평균 노이즈 값이 사용자가 입력한 특정 노이즈 값 범위에 있어야 함.(여러 평균 노이즈 값에 적용)

매도 :  다음 날 시초가


코드를 살펴보자. DB 관련 코드는 이전 포스트에서 봤던 StockDB.py를 사용하면 된다. 여기서 'Code' 데이터를 추가로 불러오는 이유를 설명한다. 만일 40일 평균 노이즈와 60일 평균 노이즈를 사용하고 이들의 교집합에 대해 매수/매도를 한다고 하자. 두 집합의 교집합을 구하기 위해서는 'Date'와 "Code" 값이 필요하다. 이 두 값이 각각의 노이즈를 구분해주기 때문이다. 그래서 두 집합에 동일하게 포함된 ('Date', 'Code') 값을 찾으면 교집합을 구할 수 있다. 합집합도 마찬가지다. 합집합을 구할 때는 두 집합을 더한 다음 그에 대한 교집합을 빼야한다. 따라서 'Code'가 추가로 필요하다.


나머지 DB 관련 코드는 이전 포스트에서 설명했으니 생략한다. 다음은 사용자가 입력한 평균 노이즈들 중 하나에 속할 때(합집합) 매수하여 누적 수익율을 계산하는 코드이다.

import logging as log import pandas as pd import StockDB as db import sys from mpldatacursor import datacursor from matplotlib.dates import DateFormatter import matplotlib.pyplot as plt log.basicConfig(stream=sys.stdout, level=log.DEBUG) market = "kosdaq" profit_type = "6" noise_type1 = "120" noise_type2 = "50" dB = db.StockDB("qhdks12#$") data1 = dB.select_noise_profit_data(market, profit_type, noise_type1) data2 = dB.select_noise_profit_data(market, profit_type, noise_type2) data1['Flag'] = pd.cut(data1['Noise'], bins=[0.3,0.4], include_lowest=True) data1 = data1.dropna() data2['Flag'] = pd.cut(data2['Noise'], bins=[0.3,0.4], include_lowest=True) data2= data2.dropna() data = data1.append(data2) data_group_code=data.groupby([data['Date'],data['Code']]).mean().reset_index() profit_by_date = pd.DataFrame(data_group_code.groupby(data_group_code['Date'])['Profit'].mean()) cul_profit = (profit_by_date + 1 - 0.004).cumprod() axes = cul_profit.plot(grid=True) lines = axes.get_lines() fmt = DateFormatter('%Y-%m-%d') datacursor(lines, formatter=lambda **kwargs: 'Return : {y:.4f}'.format(**kwargs) + '\ndate: ' + fmt(kwargs.get('x'))) plt.show()

먼저 두 개의 평균 노이즈 데이터를 불러온다. 위 예에서는 120일 평균 노이즈와 50일 평균 노이즈를 사용한다. 각 평균 노이즈 데이터를 사용자가 입력한 범위로 자른다. 여기까지는 지난 포스트와 똑같다. 그리고 합집합을 구한다. 방법은 간단하다. "append()" 메서드를 이용해 두 개의 평균 노이즈 집합을 이어준다. 그리고 'Date와 'Code' 값으로 그룹핑해 평균을 구한다. 평균을 구하는 이유는 교집합을 없애기 위해서다. append()를 적용하면 두 개의 집합을 합치기 때문에 두 집합 사이에 중복된 데이터가 존재하게 된다. 이를 하나로 합치기 위해서 먼저 'Date'와 'Code'로 그룹핑한다. 그러면 각 집합의 중복된 데이터가 하나로 묶이게 되고 이에 mean(), 평균을 적용한다. 평균을 적용하면 중복된 데이터가 가지고 있는 값이 되고 중복은 사라진다. n이 m개 있을 경우 이에 대한 평균은 n이다. 이를 이용했다. 

이 과정을 거치면 최종적으로 두 평균 노이즈 집합의 합집합이 도출된다. 이에 대해 이전 포스트에서 설명한 것처럼 'Date'로 그룹핑해 누적 수익율을 구한다. 슬리피지 값은 0.4퍼로 했다.


아래 차트는 위 코드에 대한 최종 결과이다. 아래에서 상위 두 차트는 합집합이 아닌 개별적으로 적용한 차트이다. 하위 차트가 50일 평균 노이즈와 120일 평균 노이즈의 합집합을 적용했다.


합집합을 적용하니 수익율이 많이 증가한 걸 알 수 있다. 약 400배에서 650배로 증가했다. 



다음은 사용자가 입력한 평균 노이즈들 모두에 속할 때(교집합) 매수하여 누적 수익율을 계산하는 코드이다.

import logging as log import pandas as pd import StockDB as db import sys from mpldatacursor import datacursor from matplotlib.dates import DateFormatter import matplotlib.pyplot as plt log.basicConfig(stream=sys.stdout, level=log.DEBUG) market = "kosdaq" profit_type = "6" noise_type1 = "120" noise_type2 = "50" dB = db.StockDB("qhdks12#$") data1 = dB.select_noise_profit_data(market, profit_type, noise_type1) data2 = dB.select_noise_profit_data(market, profit_type, noise_type2) data1['Flag'] = pd.cut(data1['Noise'], bins=[0.3,0.4], include_lowest=True) data1 = data1.dropna() data2['Flag'] = pd.cut(data2['Noise'], bins=[0.3,0.4], include_lowest=True) data2= data2.dropna() data = data1.merge(data2, how='inner', on=['Date','Code']) profit_by_date = pd.DataFrame(data.groupby(data['Date'])['Profit_x'].mean()) cul_profit = (profit_by_date + 1 - 0.004).cumprod() axes = cul_profit.plot(grid=True) lines = axes.get_lines() fmt = DateFormatter('%Y-%m-%d') datacursor(lines, formatter=lambda **kwargs: 'Return : {y:.4f}'.format(**kwargs) + '\ndate: ' + fmt(kwargs.get('x'))) plt.show()

중간 부분까지는 합집합 구하는 코드와 똑같다. 교집합 구하는 방식은 쉽다. 'Date'와 'Code'를 기준으로 내부조인을 하면 된다. 내부 조인은 조인하는 두 집합의 공통 부분만 합친다.


아래 차트는 위 코드에 대한 최종 결과이다. 아래에서 상위 두 차트는 교집합이 아닌 개별적으로 적용한 차트이다. 하위 차트가 50일 평균 노이즈와 120일 평균 노이즈의 교집합을 적용했다.

교집합인 만큼 수익율은 더 적어진다. 하지만 더 안정적이게 된다.

50일 평균 노이즈 집합과 120일 평균 노이즈 집합 사이의 시간 간격 때문에 별 다른 효과가 없어보이지만 다른 데이터를 적용하면 결과가 또 달라진다.


지금까지 노이즈를 토대로 다양한 방식을 사용해 관계를 파악하고 여러 개의 조건을 만들었다. 

다음 포스트에서는 지금까지 포스팅한 분석 로직을 토대로 노이즈와 변동성 돌파 전략의 관계에 따른 수익율을 분석해볼 예정이다. 

< 6. 변동성 돌파 전략과 노이즈 분석 - (2) >


1. 개요

2. 노이즈 계산

3. 평균 노이즈 계산

4. 변동성 돌파 전략과 노이즈 상관 분석

5. 변동성 돌파 전략과 분석 - (1)


해당 포스트를 읽기 전에 위 포스트를 읽길 추천한다.


이전 포스트에서 노이즈 값의 특정 범위에 따라 변동성 돌파 매매가 수익이 있을 확률을 계산해 봤다. 그리고 뚜렷하진 않지만 약간의 경향성을 확인했다. 이번에는 이 경향성을 이용해 변동성 돌파 전략에 따라 매수/매도를 해 백테스팅할 것이다. 


백테스팅할 매수/매도 알고리즘은 아래와 같다.(빨간색으로 된 부분이 기존에서 추가됬다.)


매수 : ( 전일 고가 - 전일 저가 ) * Scope + 시초가 <= 현재가   [ Scope : 0.2 ~ 1.4 (0.1 간격) ]

* ( 전일 고가 - 전일 저가 )가 0 이상이어야 함.

* 당일 평균 노이즈 값이 사용자가 입력한 특정 노이즈 값 범위에 있어야 함.

매도 :  다음 날 시초가


당일 평균 노이즈 값은 이전 포스트에서 계산했던 수익이 날 확률이 높은 범위로 할 것이다. 참고로 슬리피지 값은 0.4%로 한다. 


코드를 살펴보자.

다음은 DB에 접근하는 코드이다. 지난 포스트에서 보았던 코드와 거의 똑같다. 추가 된 것은 'Noise', 'Profit' 외에 'Code'와 'Date'를 불러온다. 'Date'를 불러온 이유는 시간에 의한 누적 수익율을 구해야 하기 때문에 시간별로 그룹핑하기 위해서다. 'Code'는 지금 필요 없지만 다음에 소개할 다른 매수/매도 알고리즘에 사용될 예정이다.

from sqlalchemy import create_engine
import pymysql
pymysql.install_as_MySQLdb()
import pandas as pd
import logging as log

class StockDB(object):
    # 싱글톤 패턴
    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = object.__new__(cls)
            return cls._instance
        return cls._instance


    def __init__(self, password):
        log.info("Connecting database.....")

        try:
            self.engine = create_engine("mysql+mysqldb://root:"+password+"@localhost/stock", encoding='utf-8')
            self.conn = pymysql.connect(host='localhost', user='root', password=password, db='stock', charset='utf8')
            self.cursor = self.conn.cursor()
        except Exception as e:
            log.warning("Connecting database Error : {}".format(repr(e)))


    def select_noise_profit_data(self, market, profit_type, noise_type):
        log.info("Selecting market all data....")

        sql = "select pft.Date , pft.Code, pft.Profit, Nos.Noise from " \
              "(select Code, Date, Profit from "+market+"_profit_"+profit_type+") as pft " \
              "join (select * from "+market+"_noise_"+noise_type+") as Nos on pft.Code=Nos.Code and pft.Date=Nos.Date;"

        try:
            data = pd.read_sql(sql, self.conn)
        except Exception as e:
            log.info("Selecting market all data Error : {}".format(repr(e)))

        return data

다음은 시간에 따른 누적 수익율을 계산하고 차트로 보여주는 코드이다. "select_noise_profit_data()" 메서드로 일자, 일자에 해당하는 수익율과 노이즈 데이터를 불러온다. 데이터를 사용자가 입력한 특정 노이즈 범위에 따라 나눈다. "bins" 변수에 범위를 설정한다. "pd.cut" 메서드를 이용해 나눈다. 나누면 아래 코드에서 노이즈가 0.3 미만이고 0.4 이상인 데이터는 NA 값이 들어간다. 따라서 "dropna()"로 0.3에서 0.4 범위에 없는 데이터를 지운다. 결과적으로 노이즈가 0.3~0.4인 데이터만 추출된다.
추출된 데이터를 'Date'를 기준으로 그룹핑해 수익율의 평균값을 구한다. 그러면 특정 일에 매수한 종목의 평균 수익율이 계산된다. 이는 하루에 매수한 종목들을 동일한 비중으로 샀다는 것을 의미한다. 그리고 0.4% 슬리피지를 적용한 누적 수익율을 구하고 차트로 나타낸다.

import logging as log import pandas as pd import StockDB as db import sys from mpldatacursor import datacursor from matplotlib.dates import DateFormatter import matplotlib.pyplot as plt log.basicConfig(stream=sys.stdout, level=log.DEBUG) market = "kosdaq" profit_type = "6" noise_type = "50" bins = [0.3,0.4] dB = db.StockDB("qhdks12#$") data = dB.select_noise_profit_data(market, profit_type, noise_type) data['Flag'] = pd.cut(data['Noise'], bins=[0.3,0.4], include_lowest=True) data = data.dropna() profit_by_date = pd.DataFrame(data.groupby(data['Date'])['Profit'].mean()) cul_profit = (profit_by_date + 1 - 0.004).cumprod() axes = cul_profit.plot(grid=True) lines = axes.get_lines() fmt = DateFormatter('%Y-%m-%d') datacursor(lines, formatter=lambda **kwargs: 'Return : {y:.4f}'.format(**kwargs) + '\ndate: ' + fmt(kwargs.get('x'))) plt.show()


아래 차트는 위 코드에 대한 결과이다. 코스닥 종목에 50일 평균 노이즈를 적용했고 노이즈 범위는 0.3에서 0.4로 했다. 

그 결과 괜찮다. 등락폭이 크긴 하지만 거의 10년 사이에 400배가 된다. 약 연간 80 퍼센트의 수익율을 보인다. 실제 주식시장에 사용해도 될 것 같다. 


그렇다면 위 차트에 적용한 설정이 아닌 다른 값으로 하면 어떤 수익율을 보일까? 더 많은 수익율을 낼 수 있지 않을 까? 이에 대한 분석은 나중에 차차 설명하겠다. 


다음 포스트에서는 위에서 언급한 변동성 돌파 매매 전략 알고리즘에 조건을 추가해볼 예정이다. 해당 조건은 이동 평균선의 정배열 개념에서 떠올랐다. 이에 대한 자세한 내용은 다음 포스트에서 소개하겠다. 과연 이 조건을 추가했을 때 더 수익율이 날 지 궁금하다. 




참고로 필자는 컴퓨터 공학과를 재학 중인 대학생입니다. 따라서 코드가 완벽할 수 없습니다. 알고리즘이나 코드가 비효율적이거나 오류가 있다면 댓글 달아주세요..



+ Recent posts